Skip to content
Open
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
49 changes: 29 additions & 20 deletions lib/ex_saml/core/xml/dsig.ex
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,7 @@ defmodule ExSaml.Core.Xml.Dsig do
:bad_digest
| :bad_signature
| :cert_not_accepted
| :missing_certificate
| :no_signature
| :multiple_signatures
| :insecure_algorithm
Expand Down Expand Up @@ -403,31 +404,39 @@ defmodule ExSaml.Core.Xml.Dsig do

sig = :base64.decode(xmlText(sig_text, :value))

{key, cert_bin} = extract_public_key(element, ds_ns)

case :public_key.verify(data, hash_function, sig, key) do
true -> check_fingerprints(cert_bin, fingerprints)
false -> {:error, :bad_signature}
with {:ok, {key, cert_bin}} <- extract_public_key(element, ds_ns) do
case :public_key.verify(data, hash_function, sig, key) do
true -> check_fingerprints(cert_bin, fingerprints)
false -> {:error, :bad_signature}
end
end
end

defp extract_public_key(element, ds_ns) do
[cert_text] =
:xmerl_xpath.string(~c"ds:Signature//ds:X509Certificate/text()", element, namespace: ds_ns)

cert_bin = :base64.decode(xmlText(cert_text, :value))
cert = :public_key.pkix_decode_cert(cert_bin, :plain)
tbs = certificate(cert, :tbsCertificate)
spki = tbs_certificate(tbs, :subjectPublicKeyInfo)

key_bin =
case subject_public_key_info(spki, :subjectPublicKey) do
{_, kb} -> kb
kb -> kb
end
case :xmerl_xpath.string(
~c"ds:Signature//ds:X509Certificate/text()",
element,
namespace: ds_ns
) do
[] ->
{:error, :missing_certificate}

# The first certificate is the signing one; the rest is its chain.
[cert_text | _] ->
cert_bin = :base64.decode(xmlText(cert_text, :value))
cert = :public_key.pkix_decode_cert(cert_bin, :plain)
tbs = certificate(cert, :tbsCertificate)
spki = tbs_certificate(tbs, :subjectPublicKeyInfo)

key_bin =
case subject_public_key_info(spki, :subjectPublicKey) do
{_, kb} -> kb
kb -> kb
end

key = :public_key.pem_entry_decode({:RSAPublicKey, key_bin, :not_encrypted})
{key, cert_bin}
key = :public_key.pem_entry_decode({:RSAPublicKey, key_bin, :not_encrypted})
{:ok, {key, cert_bin}}
end
end

defp check_fingerprints(_cert_bin, :any), do: :ok
Expand Down
10 changes: 9 additions & 1 deletion lib/ex_saml/error_messages.ex
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,11 @@ defmodule ExSaml.ErrorMessages do
@status_responder {:saml_error, ~c"urn:oasis:names:tc:SAML:2.0:status:Responder", :undefined}
@assertion_signature {:assertion, {:error, :no_signature}}
@assertion_cert_not_accepted {:assertion, {:error, :cert_not_accepted}}
@assertion_missing_certificate {:assertion, {:error, :missing_certificate}}
@envelop_signature {:envelope, {:error, :no_signature}}
@envelope_missing_certificate {:envelope, {:error, :missing_certificate}}
@name_id_errors {:saml_error, ~c"urn:oasis:names:tc:SAML:2.0:status:Requester", :undefined}
@errors ~w(bad_digest assertion_cert_not_accepted missing_assertion_key bad_audience cert_no_accepted invalid_nameid_policy invalid_nonce missing_assertion_signature missing_envelope_signature status_responder)a
@errors ~w(bad_digest assertion_cert_not_accepted missing_assertion_key bad_audience cert_no_accepted invalid_nameid_policy invalid_nonce missing_assertion_signature missing_certificate missing_envelope_signature status_responder)a

@doc false
def get(error, locale \\ "en")
Expand All @@ -38,9 +40,15 @@ defmodule ExSaml.ErrorMessages do
def get(@assertion_cert_not_accepted, locale),
do: get(:assertion_cert_not_accepted, locale)

def get(@assertion_missing_certificate, locale),
do: get(:missing_certificate, locale)

def get(@envelop_signature, locale),
do: get(:missing_envelope_signature, locale)

def get(@envelope_missing_certificate, locale),
do: get(:missing_certificate, locale)

def get(@name_id_errors, locale),
do: get(:invalid_nameid_policy, locale)

Expand Down
3 changes: 3 additions & 0 deletions priv/gettext/en/LC_MESSAGES/errors.po
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ msgstr "Missing signature. Please ensure that the assertions are properly signed
msgid "assertion_cert_not_accepted"
msgstr "The certificate used to sign the assertions has not been accepted. It is possible that the certificate has expired or is not supported."

msgid "missing_certificate"
msgstr "No X.509 certificate was found in the SAML signature. Please configure your Identity Provider to include its signing certificate in signed responses and assertions."

msgid "missing_envelope_signature"
msgstr "Missing envelope signature. Please make sure you uploaded correctly the Cryptr Certificate to your IdP"

Expand Down
3 changes: 3 additions & 0 deletions priv/gettext/errors.pot
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ msgstr "Missing signature. Please ensure that the assertions are properly signed
msgid "assertion_cert_not_accepted"
msgstr "The certificate used to sign the assertions has not been accepted. It is possible that the certificate has expired or is not supported."

msgid "missing_certificate"
msgstr "No X.509 certificate was found in the SAML signature. Please configure your Identity Provider to include its signing certificate in signed responses and assertions."

msgid "missing_envelope_signature"
msgstr "Missing envelope signature. Please make sure you uploaded correctly the Cryptr Certificate to your IdP."

Expand Down
3 changes: 3 additions & 0 deletions priv/gettext/fr/LC_MESSAGES/errors.po
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ msgstr "Signature manquante. Veuillez vous assurer que les assertions sont corre
msgid "assertion_cert_not_accepted"
msgstr "Le certificat utilisé pour signer les assertions n'a pas été accepté. Il est possible que le certificat ait expiré ou qu'il ne soit pas pris en charge."

msgid "missing_certificate"
msgstr "Aucun certificat X.509 n'a été trouvé dans la signature SAML. Veuillez configurer votre fournisseur d'identité pour inclure son certificat de signature dans les réponses et assertions signées."

msgid "missing_envelope_signature"
msgstr "La Signature pour les enveloppes est manquantes. Assurez-vous que vous avec correctement uploadé votre Certificat Cryptr sur votre IdP."

Expand Down
21 changes: 21 additions & 0 deletions test/ex_saml/core/xml/dsig_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,27 @@ defmodule ExSaml.Core.Xml.DsigTest do

assert :ok = Dsig.verify(signed_xml, [:crypto.hash(:sha, cert_bin)])
end

test "signature without X509Certificate returns {:error, :missing_certificate}" do
doc =
parse_xml(~S|<x:foo id="test" xmlns:x="urn:foo:x:"><x:name>blah</x:name></x:foo>|)

{key, cert_bin} = test_sign_256_key()
signed_xml = Dsig.sign(doc, key, cert_bin, :rsa_sha256)

# Drop KeyInfo from the serialized document: the enveloped-signature
# transform excludes ds:Signature from the digest, so it stays valid.
certless_doc =
[signed_xml]
|> :xmerl.export(:xmerl_xml)
|> List.flatten()
|> to_string()
|> String.replace(~r|<ds:KeyInfo>.*</ds:KeyInfo>|s, "")
|> parse_xml()

assert {:error, :missing_certificate} =
Dsig.verify(certless_doc, [:crypto.hash(:sha, cert_bin)])
end
end

describe "strip/1" do
Expand Down
Loading