Merge branch 'master' into es17.05

This commit is contained in:
Jocelyn Fiat
2017-10-06 08:18:50 +02:00
18 changed files with 252 additions and 53 deletions

View File

@@ -10,6 +10,9 @@ It provides simple routine to perform http requests, and get response.
- Eiffel Net library
- and optionally Eiffel NetSSL library to support `https://...`
* Note: set ciphers settings is supported only with libcurl implementation for now, net implementation
set all the ciphers as part of the OpenSSL initialization.
This means on Windows, do not forget to copy the libcurl.dll (and related) either in the same directory of the executable, or ensure the .dll are in the PATH environment.
It is possible to exclude the libcurl implementation xor the Eiffel Net implementation:

View File

@@ -16,8 +16,19 @@ feature -- Access
deferred
end
get (a_url: READABLE_STRING_8; ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT): HTTP_CLIENT_RESPONSE
do
Result := new_session (a_url).get ("", ctx)
end
custom (a_method: READABLE_STRING_8; a_url: READABLE_STRING_8; ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT): HTTP_CLIENT_RESPONSE
-- Response for `a_method' request based on `a_url' and optional `ctx'.
do
Result := new_session (a_url).custom (a_method, "", ctx)
end
note
copyright: "2011-2015, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
copyright: "2011-2017, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
source: "[
Eiffel Software

View File

@@ -31,7 +31,11 @@ feature {NONE} -- Initialization
-- This can be used to reset/reinitialize Current with new url
-- in the case of redirection.
do
url := a_url
if a_url.starts_with ("http://") or a_url.starts_with ("http://") then
url := a_url
else
url := session.url (a_url, Void)
end
headers := session.headers.twin
if ctx /= Void then
context := ctx

View File

@@ -272,6 +272,15 @@ feature -- Authentication
-- Associated optional credentials value.
-- Computed as `username':`password'.
ciphers_settings: detachable READABLE_STRING_8
-- SSL cipher preference lists
-- examples: DEFAULT, ALL, TLSv1
-- check https://www.openssl.org/docs/man1.1.0/apps/ciphers.html
--Warning At the moment only used for LIB_CURL_HTTP_CLIENT
--Warning Net implementation set all the ciphers using the OpenSSL at
--Warning initialization time.
feature -- Status setting
set_is_debug (b: BOOLEAN)
@@ -401,6 +410,14 @@ feature -- Element change
chunk_size := a_size
end
set_ciphers_settings (a_ciphers_settings: READABLE_STRING_8)
-- Set 'ciphers_settings' with 'a_ciphers_settings'.
do
create {STRING_8} ciphers_settings.make_from_string (a_ciphers_settings)
ensure
cipher_settings_set: attached ciphers_settings as c_settings and then c_settings.same_string (a_ciphers_settings)
end
note
copyright: "2011-2017, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"

View File

@@ -372,6 +372,11 @@ feature -- Execution
curl_easy.setopt_integer (curl_handle, {CURL_OPT_CONSTANTS}.curlopt_ssl_verifypeer, 0)
end
--| Cipher List
if attached session.ciphers_settings as c_list then
curl_easy.setopt_string (curl_handle, {CURL_OPT_CONSTANTS}.curlopt_ssl_cipher_list, c_list )
end
--| Request method
if request_method.is_case_insensitive_equal ("GET") then
curl_easy.setopt_integer (curl_handle, {CURL_OPT_CONSTANTS}.curlopt_httpget, 1)

View File

@@ -113,6 +113,7 @@ feature -- Access
-- Get URL data
l_is_https := url.starts_with_general ("https://")
create l_uri.make_from_string (url)
check valid_url: l_uri.is_valid end
l_port := l_uri.port
if l_port = 0 then
if l_is_https then

View File

@@ -55,12 +55,19 @@ feature -- Secure connection Helpers
end
set_secure_protocol_to_ssl_2_or_3
-- Set `ssl_protocol' with `Ssl_23'.
do
set_secure_protocol ({SSL_PROTOCOL}.Ssl_23)
end
-- Set `ssl_protocol' with `Ssl_23'.
-- Protocol not supported anymore.
obsolete
"Use set_secure_protocol_to_tls_1_2 [2017-06-23]."
local
err: DEVELOPER_EXCEPTION
do
create err
err.set_description ("SSL_2 or SSL_3 are not supported anymore, upgrate to TLS set_secure_protocol_to_tls_1_2")
err.raise
end
set_secure_protocol_to_tls_1_0
set_secure_protocol_to_tls_1_0
-- Set `ssl_protocol' with `Tls_1_0'.
do
set_secure_protocol ({SSL_PROTOCOL}.Tls_1_0)
@@ -176,7 +183,14 @@ feature -- Output
end
note
copyright: "2011-2013, Javier Velilla, Jocelyn Fiat and others"
copyright: "2011-2017, Jocelyn Fiat, Javier Velilla, Olivier Ligot, Colin Adams, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
source: "[
Eiffel Software
5949 Hollister Ave., Goleta, CA 93117 USA
Telephone 805-685-1006, Fax 805-685-6869
Website http://www.eiffel.com
Customer support http://support.eiffel.com
]"
end

View File

@@ -0,0 +1,36 @@
note
description: "Summary description for {JWT_MISMATCHED_ALG_ERROR}."
date: "$Date$"
revision: "$Revision$"
class
JWT_MISMATCHED_ALG_ERROR
inherit
JWT_ERROR
create
make
feature {NONE} -- Initialization
make (a_alg, a_header_alg: READABLE_STRING_8)
do
alg := a_alg
header_alg := a_header_alg
end
feature -- Access
alg: READABLE_STRING_8
header_alg: READABLE_STRING_8
id: STRING = "ALG_MISMATCH"
message: READABLE_STRING_8
do
Result := "Header alg [" + header_alg + "] does not match given alg [" + alg + "]!"
end
end

View File

@@ -59,6 +59,8 @@ feature -- Status report
do
if attached claimset.issuer as iss then
Result := a_issuer = Void or else a_issuer.same_string (iss)
else
Result := a_issuer = Void
end
end
@@ -66,6 +68,8 @@ feature -- Status report
do
if attached claimset.audience as aud then
Result := a_audience = Void or else a_audience.same_string (aud)
else
Result := a_audience = Void
end
end
@@ -118,6 +122,11 @@ feature {JWT_UTILITIES} -- Error reporting
l_errors.extend (err)
end
report_mismatched_alg_error (alg, a_header_alg: READABLE_STRING_8)
do
report_error (create {JWT_MISMATCHED_ALG_ERROR}.make (alg, a_header_alg))
end
report_unsupported_alg_error (alg: READABLE_STRING_8)
do
report_error (create {JWT_UNSUPPORTED_ALG_ERROR}.make (alg))

View File

@@ -266,11 +266,10 @@ feature {NONE} -- Implementation
base64_hmacsha256 (s: READABLE_STRING_8; a_secret: READABLE_STRING_8): STRING_8
local
hs256: HMAC_SHA256
ut: JWT_UTILITIES
do
create hs256.make_ascii_key (a_secret)
hs256.update_from_string (s)
Result := hs256.base64_digest --lowercase_hexadecimal_string_digest
create ut
Result := ut.base64_hmacsha256 (s, a_secret)
end
end

View File

@@ -1,8 +1,8 @@
note
description: "Summary description for {JWT_LOADER}."
author: ""
description: "Loader and verifier to JWT token."
date: "$Date$"
revision: "$Revision$"
EIS: "name=Known Critical vulnerabilities in JWT libs", "protocol=URI", "src=https://auth0.com/blog/critical-vulnerabilities-in-json-web-token-libraries/"
class
JWT_LOADER
@@ -12,9 +12,13 @@ inherit
feature -- Access
token (a_token_input: READABLE_STRING_8; a_secret: READABLE_STRING_8; ctx: detachable JWT_CONTEXT): detachable JWT
-- Decoded token from `a_token_input` given the secret `a_secret`, and optional context `ctx`
token (a_token_input: READABLE_STRING_8; a_alg: detachable READABLE_STRING_8; a_verification_key: READABLE_STRING_8; ctx: detachable JWT_CONTEXT): detachable JWT
-- Decoded token from `a_token_input` given the verification key `a_verification_key` and optional (but recommended) signature algorithm `a_alg`, and optional context `ctx`
-- used to specify eventual issuer and various parameters.
-- WARNING: passing Void for `a_alg` is not safe, as the server should know which alg he used for tokens,
-- leaving the possibility to use the header alg is dangerous as client may use "none" and then bypass verification!
require
a_valid_alg: a_alg /= Void implies is_supporting_signature_algorithm (a_alg)
local
jws: JWS
i,j,n: INTEGER
@@ -29,20 +33,27 @@ feature -- Access
l_enc_payload := a_token_input.substring (i + 1, j - 1)
l_signature := a_token_input.substring (j + 1, n)
create jws.make_with_json_payload (base64url_decode (l_enc_payload))
alg := signature_algorithm_from_encoded_header (l_enc_header)
jws.set_algorithm (alg)
if alg = Void then
-- Use default
alg := alg_hs256
if a_alg /= Void then
if alg /= Void and then not alg.is_case_insensitive_equal_general (a_alg) then
jws.report_mismatched_alg_error (a_alg, alg)
else
alg := a_alg
end
else
if alg = Void then
-- Use default
alg := alg_hs256
end
end
jws.set_algorithm (alg)
check alg_set: alg /= Void end
if ctx = Void or else not ctx.validation_ignored then
if not is_supporting_signature_algorithm (alg) then
jws.report_unsupported_alg_error (alg)
alg := alg_hs256
end
if not l_signature.same_string (signature (l_enc_header, l_enc_payload, a_secret, alg)) then
if not l_signature.same_string (signature (l_enc_header, l_enc_payload, a_verification_key, alg)) then
jws.report_unverified_token_error
end
if

View File

@@ -61,7 +61,33 @@ feature -- Encoding
do
create hs256.make_ascii_key (a_secret)
hs256.update_from_string (s)
Result := hs256.base64_digest --lowercase_hexadecimal_string_digest
-- if Version >= EiffelStudio 17.11 then
-- Result := hs256.base64_digest --lowercase_hexadecimal_string_digest
-- else
Result := base64_bytes_encoded_string (hs256.digest)
-- end
end
feature {NONE} -- Implementation
base64_bytes_encoded_string (a_bytes: SPECIAL [NATURAL_8]): STRING_8
-- Base64 string from `a_bytes`.
--| Note: to be removed when 17.11 is not latest release anymore.
local
s: STRING
i,n: INTEGER
do
from
i := 1
n := a_bytes.count
create s.make (n)
until
i > n
loop
s.append_code (a_bytes[i - 1])
i := i + 1
end
Result := (create {BASE64}).encoded_string (s)
end
feature -- Decoding

View File

@@ -54,7 +54,14 @@ feature -- Test
create jwt_loader
if attached jwt_loader.token (tok, "secret", Void) as l_tok then
-- Use header alg!
if attached jwt_loader.token (tok, Void, "secret", Void) as l_tok then
assert ("no error", not l_tok.has_error)
assert ("same payload", l_tok.claimset.string.same_string (payload))
end
-- Use given alg!
if attached jwt_loader.token (tok, jwt.algorithm, "secret", Void) as l_tok then
assert ("no error", not l_tok.has_error)
assert ("same payload", l_tok.claimset.string.same_string (payload))
end
@@ -96,21 +103,21 @@ feature -- Test
create jwt_loader
-- Test with validation + exp
if attached jwt_loader.token (tok, "secret", Void) as l_tok then
if attached jwt_loader.token (tok, jwt.algorithm, "secret", Void) as l_tok then
assert ("no error", not l_tok.has_error)
assert ("same payload", l_tok.claimset.string.same_string (payload))
end
create ctx
ctx.set_time (now)
if attached jwt_loader.token (tok, "secret", ctx) as l_tok then
if attached jwt_loader.token (tok, jwt.algorithm, "secret", ctx) as l_tok then
assert ("no error", not l_tok.has_error)
end
dt := duplicated_time (now)
dt.hour_add (5)
ctx.set_time (dt)
if attached jwt_loader.token (tok, "secret", ctx) as l_tok then
if attached jwt_loader.token (tok, jwt.algorithm, "secret", ctx) as l_tok then
assert ("exp error", l_tok.has_error)
end
@@ -122,7 +129,7 @@ feature -- Test
tok := jwt.encoded_string ("secret")
ctx.set_time (now)
if attached jwt_loader.token (tok, "secret", ctx) as l_tok then
if attached jwt_loader.token (tok, jwt.algorithm, "secret", ctx) as l_tok then
assert ("has nbf error", l_tok.has_error)
end
@@ -130,7 +137,7 @@ feature -- Test
dt.second_add (15)
ctx.set_time (dt)
if attached jwt_loader.token (tok, "secret", ctx) as l_tok then
if attached jwt_loader.token (tok, jwt.algorithm, "secret", ctx) as l_tok then
assert ("has nbf error", l_tok.has_error)
end
@@ -138,31 +145,51 @@ feature -- Test
dt.minute_add (45)
ctx.set_time (dt)
if attached jwt_loader.token (tok, "secret", ctx) as l_tok then
if attached jwt_loader.token (tok, jwt.algorithm, "secret", ctx) as l_tok then
assert ("no error", not l_tok.has_error)
end
-- Test Issuer
ctx.set_issuer ("urn:foobar")
if attached jwt_loader.token (tok, "secret", ctx) as l_tok then
if attached jwt_loader.token (tok, jwt.algorithm, "secret", ctx) as l_tok then
assert ("has iss error", l_tok.has_error)
end
ctx.set_issuer ("urn:foo")
if attached jwt_loader.token (tok, "secret", ctx) as l_tok then
if attached jwt_loader.token (tok, jwt.algorithm, "secret", ctx) as l_tok then
assert ("no error", not l_tok.has_error)
end
-- Test Audience
ctx.set_audience ("urn:foobar")
if attached jwt_loader.token (tok, "secret", ctx) as l_tok then
if attached jwt_loader.token (tok, jwt.algorithm, "secret", ctx) as l_tok then
assert ("has aud error", l_tok.has_error)
end
ctx.set_audience ("urn:foo")
if attached jwt_loader.token (tok, "secret", ctx) as l_tok then
if attached jwt_loader.token (tok, jwt.algorithm, "secret", ctx) as l_tok then
assert ("no error", not l_tok.has_error)
end
end
test_mismatched_alg_jwt
local
jwt: JWS
payload: STRING
tok: STRING
do
payload := "[
{"iss":"joe","exp":1300819380,"http://example.com/is_root":true}
]"
create jwt.make_with_json_payload (payload)
jwt.set_algorithm ("none")
tok := jwt.encoded_string ("secret")
if attached (create {JWT_LOADER}).token (tok, "HS256", "secret", Void) as l_tok then
assert ("no error", not jwt.has_error)
assert ("same payload", l_tok.claimset.string.same_string (payload))
end
end
test_unsecured_jwt
local
jwt: JWS
@@ -177,7 +204,11 @@ feature -- Test
jwt.set_algorithm ("none")
tok := jwt.encoded_string ("secret")
if attached (create {JWT_LOADER}).token (tok, "secret", Void) as l_tok then
if attached (create {JWT_LOADER}).token (tok, "none", "secret", Void) as l_tok then
assert ("no error", not jwt.has_error)
assert ("same payload", l_tok.claimset.string.same_string (payload))
end
if attached (create {JWT_LOADER}).token (tok, Void, "secret", Void) as l_tok then
assert ("no error", not jwt.has_error)
assert ("same payload", l_tok.claimset.string.same_string (payload))
end

View File

@@ -76,13 +76,13 @@ feature -- Initialization
a_http_authorization /= Void implies http_authorization /= Void
end
make_basic_auth (u: READABLE_STRING_32; p: READABLE_STRING_32)
make_basic_auth (u: READABLE_STRING_GENERAL; p: READABLE_STRING_GENERAL)
-- Create a Basic authentication.
do
make_custom_auth (u, p, Basic_auth_type)
end
make_custom_auth (u: READABLE_STRING_32; p: READABLE_STRING_32; a_type: READABLE_STRING_8)
make_custom_auth (u: READABLE_STRING_GENERAL; p: READABLE_STRING_GENERAL; a_type: READABLE_STRING_8)
-- Create a custom `a_type' authentication.
require
a_type_accepted: a_type.is_case_insensitive_equal (Basic_auth_type)
@@ -90,15 +90,20 @@ feature -- Initialization
local
t: STRING_8
utf: UTF_CONVERTER
s: STRING_32
do
login := u
password := p
create login.make_from_string_general (u)
create password.make_from_string_general (p)
create t.make_from_string (a_type)
t.left_adjust; t.right_adjust
type := t
if t.is_case_insensitive_equal (Basic_auth_type) then
type := Basic_auth_type
create http_authorization.make_from_string ("Basic " + (create {BASE64}).encoded_string (utf.string_32_to_utf_8_string_8 (u + {STRING_32} ":" + p)))
create s.make_from_string_general (u)
s.extend (':')
s.append_string_general (p)
create http_authorization.make_from_string ("Basic " + (create {BASE64}).encoded_string (utf.string_32_to_utf_8_string_8 (s)))
elseif t.is_case_insensitive_equal (Digest_auth_type) then
type := Digest_auth_type
to_implement ("HTTP Authorization %""+ t +"%", not yet implemented")
@@ -115,9 +120,9 @@ feature -- Access
type: READABLE_STRING_8
login: detachable READABLE_STRING_32
login: detachable IMMUTABLE_STRING_32
password: detachable READABLE_STRING_32
password: detachable IMMUTABLE_STRING_32
feature -- Status report

View File

@@ -167,7 +167,7 @@ feature -- Element change
end
set_socket_timeout (a_nb_seconds: like socket_timeout)
-- Set `socket_timeout' with `a_nb_seconds'
-- Set `socket_timeout' with `a_nb_seconds'.
do
socket_timeout := a_nb_seconds
ensure
@@ -175,7 +175,7 @@ feature -- Element change
end
set_socket_recv_timeout (a_nb_seconds: like socket_recv_timeout)
-- Set `socket_recv_timeout' with `a_nb_seconds'
-- Set `socket_recv_timeout' with `a_nb_seconds'.
do
socket_recv_timeout := a_nb_seconds
ensure
@@ -183,7 +183,7 @@ feature -- Element change
end
set_keep_alive_timeout (a_seconds: like keep_alive_timeout)
-- Set `keep_alive_timeout' with `a_seconds'
-- Set `keep_alive_timeout' with `a_seconds'.
do
keep_alive_timeout := a_seconds
ensure
@@ -191,7 +191,7 @@ feature -- Element change
end
set_max_keep_alive_requests (nb: like max_keep_alive_requests)
-- Set `max_keep_alive_requests' with `nb'
-- Set `max_keep_alive_requests' with `nb'.
do
max_keep_alive_requests := nb
ensure
@@ -254,7 +254,7 @@ feature -- Element change
end
mark_secure
-- Set is_secure in True
-- Set is_secure in True.
do
set_is_secure (True)
ensure
@@ -287,7 +287,7 @@ feature -- Element change
end
set_secure_protocol (a_version: NATURAL)
-- Set `secure_protocol' with `a_version'
-- Set `secure_protocol' with `a_version'.
do
secure_protocol := a_version
ensure
@@ -295,7 +295,7 @@ feature -- Element change
end
set_secure_protocol_from_string (a_ssl_version: READABLE_STRING_GENERAL)
-- Set `secure_protocol' with `a_ssl_version'
-- Set `secure_protocol' with `a_ssl_version'.
do
if a_ssl_version.is_case_insensitive_equal ("ssl_2_3") then
set_secure_protocol_to_ssl_2_or_3
@@ -316,6 +316,8 @@ feature -- SSL Helpers
set_secure_protocol_to_ssl_2_or_3
-- Set `secure_protocol' with `Ssl_23'.
obsolete
"Use set_secure_protocol_to_tls_1_2 [2017-06-23]."
deferred
end

View File

@@ -36,9 +36,16 @@ feature -- Access
feature -- SSL Helpers
set_secure_protocol_to_ssl_2_or_3
-- Set `secure_protocol' with `Ssl_23'.
-- Set `ssl_protocol' with `Ssl_23'.
-- Protocol not supported anymore.
obsolete
"Use set_secure_protocol_to_tls_1_2 [2017-06-23]."
local
err: DEVELOPER_EXCEPTION
do
set_secure_protocol ({SSL_PROTOCOL}.Ssl_23)
create err
err.set_description ("SSL_2 or SSL_3 are not supported anymore, upgrate to TLS set_secure_protocol_to_tls_1_2")
err.raise
end
set_secure_protocol_to_tls_1_0
@@ -67,7 +74,7 @@ feature -- SSL Helpers
note
copyright: "2011-2014, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
copyright: "2011-2017, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
source: "[
Eiffel Software