Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ecdsa support #3

Draft
wants to merge 8 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 0 additions & 8 deletions .travis.yml

This file was deleted.

3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Binary file removed rebar
Binary file not shown.
2 changes: 1 addition & 1 deletion src/esaml.app.src
Original file line number Diff line number Diff line change
@@ -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,
Expand Down
15 changes: 10 additions & 5 deletions src/esaml_binding.erl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
35 changes: 29 additions & 6 deletions src/xmerl_dsig.erl
Original file line number Diff line number Diff line change
Expand Up @@ -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}]),
Expand Down Expand Up @@ -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 ->
Expand All @@ -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
Expand All @@ -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).
Expand Down