diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 07eb23e..0000000 --- a/.travis.yml +++ /dev/null @@ -1,8 +0,0 @@ -language: erlang -otp_release: - - 24.1 - - 23.0 - - 22.0 - - 21.0 - - 20.3 -script: rebar3 do compile, eunit diff --git a/CHANGELOG.md b/CHANGELOG.md index 74c2069..24c3368 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ All changes are in the `main` branch (`master` remains unchanged). +### v4.5.0 ++ Update minor version due to using non-deprecated functions which may break previously supported OTP versions, specifically `http_uri` to `uri_string` [thanks jamesvl](https://github.com/dropbox/esaml/pull/6) + ### v4.4.0 + New feature providing additional key and certificate management functions to handle inline cert/key configuration rather than relying on a file + Fixes a race condition in start_ets/0 diff --git a/rebar b/rebar deleted file mode 100755 index f350b02..0000000 Binary files a/rebar and /dev/null differ diff --git a/src/esaml.app.src b/src/esaml.app.src index 7fd040a..e550070 100644 --- a/src/esaml.app.src +++ b/src/esaml.app.src @@ -1,6 +1,6 @@ {application,esaml, [{description,"SAML Server Provider library for erlang"}, - {vsn,"4.4.0"}, + {vsn,"4.5.0"}, {modules,[]}, {registered,[]}, {applications,[kernel,inets,ssl,stdlib,xmerl,cowlib,cowboy, diff --git a/src/esaml_binding.erl b/src/esaml_binding.erl index 1157311..f786035 100644 --- a/src/esaml_binding.erl +++ b/src/esaml_binding.erl @@ -57,15 +57,20 @@ decode_response(_, SAMLResponse) -> encode_http_redirect(IdpTarget, SignedXml, Username, RelayState) -> Type = xml_payload_type(SignedXml), Req = lists:flatten(xmerl:export([SignedXml], xmerl_xml)), - % TODO: unsure how to manage Param since no uri_string function can perform the required percent-encoding - Param = http_uri:encode(base64:encode_to_string(zlib:zip(Req))), - RelayStateEsc = uri_string:normalize(binary_to_list(RelayState)), + + QueryList = [ + {"SAMLEncoding", ?deflate}, + {Type, uri_string:quote(base64:encode_to_string(zlib:zip(Req)))}, + {"RelayState", uri_string:normalize(binary_to_list(RelayState))} + ], + QueryParamStr = uri_string:compose_query(QueryList), FirstParamDelimiter = case lists:member($?, IdpTarget) of true -> "&"; false -> "?" end, Username_Part = redirect_username_part(Username), - iolist_to_binary([IdpTarget, FirstParamDelimiter, "SAMLEncoding=", ?deflate, "&", Type, "=", Param, "&RelayState=", RelayStateEsc | Username_Part]). + + iolist_to_binary([IdpTarget, FirstParamDelimiter, QueryParamStr | Username_Part]). redirect_username_part(Username) when is_binary(Username), size(Username) > 0 -> - ["&username=", uri_string:normalize(binary_to_list(Username))]; + ["&", uri_string:compose_query([{"username", uri_string:normalize(binary_to_list(Username))}])]; redirect_username_part(_Other) -> []. %% @doc Encode a SAMLRequest (or SAMLResponse) as an HTTP-POST binding diff --git a/src/xmerl_dsig.erl b/src/xmerl_dsig.erl index 98088f0..41e6bb2 100644 --- a/src/xmerl_dsig.erl +++ b/src/xmerl_dsig.erl @@ -168,8 +168,9 @@ verify(Element, Fingerprints) -> case xmerl_xpath:string("ds:Signature/ds:SignedInfo/ds:SignatureMethod/@Algorithm", Element, [{namespace, DsNs}]) of [] -> {error, no_signature}; + [#xmlAttribute{value = SignatureMethodAlgorithm}] -> - {HashFunction, _, _} = signature_props(SignatureMethodAlgorithm), + {HashFunction, _, SignatureUrl} = signature_props(SignatureMethodAlgorithm), [#xmlAttribute{value = "http://www.w3.org/2001/10/xml-exc-c14n#"}] = xmerl_xpath:string("ds:Signature/ds:SignedInfo/ds:CanonicalizationMethod/@Algorithm", Element, [{namespace, DsNs}]), [#xmlAttribute{value = SignatureMethodAlgorithm}] = xmerl_xpath:string("ds:Signature/ds:SignedInfo/ds:SignatureMethod/@Algorithm", Element, [{namespace, DsNs}]), @@ -202,12 +203,15 @@ verify(Element, Fingerprints) -> CertHash = crypto:hash(sha, CertBin), CertHash2 = crypto:hash(sha256, CertBin), - Cert = public_key:pkix_decode_cert(CertBin, plain), - KeyBin = case Cert#'Certificate'.tbsCertificate#'TBSCertificate'.subjectPublicKeyInfo#'SubjectPublicKeyInfo'.subjectPublicKey of - {_, KeyBin2} -> KeyBin2; - KeyBin3 -> KeyBin3 + Key = case SignatureUrl of + "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha256" -> + extract_ec_key(CertBin); + + _ -> + Cert = public_key:pkix_decode_cert(CertBin, plain), + {_, KeyBin} = Cert#'Certificate'.tbsCertificate#'TBSCertificate'.subjectPublicKeyInfo#'SubjectPublicKeyInfo'.subjectPublicKey, + public_key:pem_entry_decode({'RSAPublicKey', KeyBin, not_encrypted}) end, - Key = public_key:pem_entry_decode({'RSAPublicKey', KeyBin, not_encrypted}), case public_key:verify(Data, HashFunction, Sig, Key) of true -> @@ -230,6 +234,18 @@ verify(Element, Fingerprints) -> {error, multiple_signatures} end. +-spec extract_ec_key(DerCert :: binary()) -> {#'ECPoint'{}, any()}. +extract_ec_key(DerCert) -> + OTPCertificate = public_key:pkix_decode_cert(DerCert, otp), + SubjectPublicKeyInfo = OTPCertificate#'OTPCertificate'.tbsCertificate#'OTPTBSCertificate'.subjectPublicKeyInfo, + + #'OTPSubjectPublicKeyInfo'{ + subjectPublicKey = ECPoint, + algorithm = #'PublicKeyAlgorithm'{ parameters = Params } + } = SubjectPublicKeyInfo, + + {ECPoint, Params}. + %% @doc Verifies an XML digital signature, trusting any valid certificate. %% %% This is really not recommended for production use, but it's handy in @@ -252,6 +268,13 @@ signature_props(rsa_sha256) -> HashFunction = sha256, DigestMethod = "http://www.w3.org/2001/04/xmlenc#sha256", Url = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256", + {HashFunction, DigestMethod, Url}; +signature_props("http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha256") -> + signature_props(ecdsa_sha256); +signature_props(ecdsa_sha256) -> + HashFunction = sha256, + DigestMethod = "http://www.w3.org/2001/04/xmlenc#sha256", + Url = "http://www.w3.org/2001/04/xmldsig-more#ecdsa-sha256", {HashFunction, DigestMethod, Url}. -ifdef(TEST).