diff --git a/src/esaml_binding.erl b/src/esaml_binding.erl index 2071636..fc13884 100644 --- a/src/esaml_binding.erl +++ b/src/esaml_binding.erl @@ -9,10 +9,11 @@ %% @doc SAML HTTP binding handlers -module(esaml_binding). --export([decode_response/2, encode_http_redirect/4, encode_http_post/3, encode_http_post/4]). +-export([decode_response/2, encode_http_redirect/5, encode_http_post/3, encode_http_post/4]). -include_lib("xmerl/include/xmerl.hrl"). -define(deflate, <<"urn:oasis:names:tc:SAML:2.0:bindings:URL-Encoding:DEFLATE">>). +-define(DEFAULT_SIG_ALG, <<"http://www.w3.org/2001/04/xmldsig-more#rsa-sha256">>). -type uri() :: binary() | string(). -type html_doc() :: binary(). @@ -53,15 +54,54 @@ decode_response(_, SAMLResponse) -> %% @doc Encode a SAMLRequest (or SAMLResponse) as an HTTP-Redirect binding %% %% Returns the URI that should be the target of redirection. --spec encode_http_redirect(IDPTarget :: uri(), SignedXml :: xml(), Username :: undefined | string(), RelayState :: binary()) -> uri(). -encode_http_redirect(IdpTarget, SignedXml, Username, RelayState) -> - Type = xml_payload_type(SignedXml), - Req = lists:flatten(xmerl:export([SignedXml], xmerl_xml)), - Param = http_uri:encode(base64:encode_to_string(zlib:zip(Req))), - RelayStateEsc = http_uri:encode(binary_to_list(RelayState)), +%% +%% It is compliant with https://docs.oasis-open.org/security/saml/v2.0/saml-bindings-2.0-os.pdf (section 3.4.4.1). +%% It strips the ds:Signature tag and based on it determines sign algorithm. +-spec encode_http_redirect(IDPTarget :: uri(), SignedXml :: xml(), Username :: undefined | string(), RelayState :: binary(), Opts :: proplists:proplist()) -> uri(). +encode_http_redirect(IdpTarget, UnsignedXml0, Username, RelayState0, Opts) -> + PrivKey = proplists:get_value(key, Opts), + RelayState = binary_to_list(RelayState0), + SigAlg = ?DEFAULT_SIG_ALG, + ParamType = xml_payload_type(UnsignedXml0), + XmlCanon = lists:flatten(xmerl:export([UnsignedXml0], xmerl_xml)), + SamlParam = deflate_payload(XmlCanon), + + ContentToBeSigned = iolist_to_binary([ParamType, "=", http_uri:encode(SamlParam), $&, + "RelayState=", http_uri:encode(RelayState), $&, + "SigAlg=", http_uri:encode(SigAlg)]), + + Signature = sign(ContentToBeSigned, SigAlg, PrivKey), + 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]). + UsernamePart = redirect_username_part(Username), + iolist_to_binary([IdpTarget, + FirstParamDelimiter, + "SAMLEncoding=", ?deflate, $&, + ContentToBeSigned, $&, + "Signature=", http_uri:encode(Signature), + UsernamePart]). + +sign(Payload, SigMethod, Key) -> + DigestType = to_digest_type(SigMethod), + Signature = public_key:sign(Payload, DigestType, Key), + base64:encode(Signature). + +deflate_payload(Payload) -> base64:encode_to_string(zlib:zip(Payload)). + +to_digest_type(SigMethod) -> sha256. % TODO + +strip_signature(SignedXml) -> + [Signature] = xmerl_xpath:string("//ds:Signature", SignedXml, [{namespace, [{"ds", 'http://www.w3.org/2000/09/xmldsig#'}]}]), + SigAlg = get_sig_alg(Signature), + io:format("SigAlg is ~p", [SigAlg]), + {SigAlg, xmerl_dsig:strip(SignedXml)}. + +get_sig_alg(Signature) -> + #xmlObj{value = SigAlg} = + xmerl_xpath:string("string(/ds:Signature/ds:SignedInfo/ds:SignatureMethod/@Algorithm)", + Signature, + [{namespace, [{"ds", 'http://www.w3.org/2000/09/xmldsig#'}]}]), + SigAlg. redirect_username_part(Username) when is_binary(Username), size(Username) > 0 -> ["&username=", http_uri:encode(binary_to_list(Username))]; diff --git a/src/esaml_sp.erl b/src/esaml_sp.erl index 3a1abf4..b9a482f 100644 --- a/src/esaml_sp.erl +++ b/src/esaml_sp.erl @@ -14,7 +14,7 @@ -export([setup/1, generate_authn_request/2, generate_authn_request/3, generate_metadata/1]). -export([validate_assertion/2, validate_assertion/3]). --export([generate_logout_request/3, generate_logout_request/4, generate_logout_response/3]). +-export([generate_logout_request/4, generate_logout_request/5, generate_logout_response/3]). -export([validate_logout_request/2, validate_logout_response/2]). -type xml() :: #xmlElement{} | #xmlDocument{}. @@ -76,16 +76,17 @@ generate_authn_request(IdpURL, %% @doc Return a LogoutRequest as an XML element %% @deprecated Use generate_logout_request/4 --spec generate_logout_request(IdpURL :: string(), NameID :: string(), esaml:sp()) -> #xmlElement{}. -generate_logout_request(IdpURL, NameID, SP = #esaml_sp{}) -> +-spec generate_logout_request(IdpURL :: string(), NameID :: string(), esaml:sp(), proplists:proplist()) -> #xmlElement{}. +generate_logout_request(IdpURL, NameID, SP = #esaml_sp{}, Opts) -> SessionIndex = "", Subject = #esaml_subject{name = NameID}, - generate_logout_request(IdpURL, SessionIndex, Subject, SP). + generate_logout_request(IdpURL, SessionIndex, Subject, SP, Opts). %% @doc Return a LogoutRequest as an XML element --spec generate_logout_request(IdpURL :: string(), SessionIndex :: string(), esaml:subject(), esaml:sp()) -> #xmlElement{}. -generate_logout_request(IdpURL, SessionIndex, Subject = #esaml_subject{}, SP = #esaml_sp{metadata_uri = _MetaURI}) +-spec generate_logout_request(IdpURL :: string(), SessionIndex :: string(), esaml:subject(), esaml:sp(), proplists:proplist()) -> #xmlElement{}. +generate_logout_request(IdpURL, SessionIndex, Subject = #esaml_subject{}, SP = #esaml_sp{metadata_uri = _MetaURI}, Opts) when is_record(Subject, esaml_subject) -> + LogoutBinding = proplists:get_value(binding_type, Opts), Now = erlang:localtime_to_universaltime(erlang:localtime()), Stamp = esaml_util:datetime_to_saml(Now), Issuer = get_entity_id(SP), @@ -99,7 +100,7 @@ generate_logout_request(IdpURL, SessionIndex, Subject = #esaml_subject{}, SP = # name_format = Subject#esaml_subject.name_format, session_index = SessionIndex, reason = user}), - if SP#esaml_sp.sp_sign_requests -> + if SP#esaml_sp.sp_sign_requests andalso LogoutBinding =:= post -> reorder_issuer(xmerl_dsig:sign(Xml, SP#esaml_sp.key, SP#esaml_sp.certificate)); true -> add_xml_id(Xml) diff --git a/src/xmerl_dsig.erl b/src/xmerl_dsig.erl index e333a36..b546823 100644 --- a/src/xmerl_dsig.erl +++ b/src/xmerl_dsig.erl @@ -20,7 +20,7 @@ %% enveloped mode. -module(xmerl_dsig). --export([verify/1, verify/2, sign/3, sign/4, strip/1, digest/1]). +-export([verify/1, verify/2, sign/3, sign/4, strip/1, digest/1, signature_props/1]). -include_lib("xmerl/include/xmerl.hrl"). -include_lib("public_key/include/public_key.hrl").