Phase 29 (D-11) — a minimal, genuine XMLDSig signer for tests.
This module produces a SAML <Response> carrying a real ds:DigestValue and a
real ds:SignatureValue over the contained <Assertion>. It exists so the
suite can assert the ONE input that must remain {:ok} after Phase 29 wires
cryptographic verification into Relyra.Security.Signature: a legitimately
signed Response (the positive control, ROADMAP success #3). Every
structure-only "signature" the verifier now rejects has a genuine counterpart
here.
Anti-divergent-signer guarantee (D-12)
The signer canonicalizes with the SAME C14N engine the verifier uses — it
parses the final Response with Relyra.Security.XML.SaxyTree, locates the
exact <Assertion> / <SignedInfo> tree nodes the verifier will bind, and
canonicalizes THEM via Relyra.Security.XML.PureBeam.canonicalize/1
(referenced element → DigestValue) and Relyra.Security.XML.C14N.serialize/1
(SignedInfo → signed bytes). Because the digest and the signature are
computed over the bytes produced by parsing the emitted XML, the signer can
never canonicalize differently from the verifier — a divergent second signer
would make the positive control pass for the wrong reason (T-29-15).
Keypair reuse (D-11)
The RSA-2048 key is Relyra.TestSupport.FakeIdP.keypair() — there is NO second
:public_key.generate_key call in this module. The self-signed cert PEM the
verifier trusts is derived from that same key. Phase 30 PROMOTES this module
into FakeIdP (it fills the empty SignedInfo that FakeIdP.response_xml
emits today); keeping the byte-alignment guarantee above is the whole point of
the promotion.
Prod guard (T-29-18)
Mirrors FakeIdP's @prod_build + ensure_not_prod!/0 discipline so the
signing code never compiles/runs in :prod.
Summary
Types
Result of signed_response/1: the genuinely-signed Response XML plus the
cert chain (one self-signed PEM) the verifier must be configured with to
accept it.
Functions
The self-signed certificate PEM (a single-element cert chain) the verifier must
trust to accept this signer's Responses. Derived from FakeIdP.keypair().
Genuinely sign an EXISTING SAML <Response> XML in place, preserving its
exact element shape.
Build a genuinely-signed SAML <Response> and the matching cert chain.
Types
Result of signed_response/1: the genuinely-signed Response XML plus the
cert chain (one self-signed PEM) the verifier must be configured with to
accept it.
Functions
@spec self_signed_cert_pem() :: String.t()
The self-signed certificate PEM (a single-element cert chain) the verifier must
trust to accept this signer's Responses. Derived from FakeIdP.keypair().
Exposed so a test can assert against the cert directly, or pass a DIFFERENT
cert (a throwaway keypair) to drive the wrong-key :invalid_signature
negative.
Genuinely sign an EXISTING SAML <Response> XML in place, preserving its
exact element shape.
The input response_xml must contain an <Assertion ID="..."> (the referenced
element) and a <Signature> whose <SignedInfo> carries a
<Reference URI="#assertion_id"> with a <DigestMethod> but no
<DigestValue>/<SignatureValue> yet (the structure-only shape the suite
already builds). This function:
- parses the input with the verifier's parser,
- computes the genuine
DigestValueover the canonicalized referenced<Assertion>(the SAME engine the verifier uses), - injects
<DigestValue>…</DigestValue>into the Reference, - re-parses, canonicalizes the
<SignedInfo>(SAME engine), signs it withFakeIdP.keypair(), and - injects
<SignatureValue>…</SignatureValue>after</SignedInfo>.
Returns %{response_xml: signed_xml, cert_chain: [pem]}.
Use this to re-point structure-only fixtures (whose declared outcome fires AT or AFTER the crypto step — time conditions, replay, success) at a genuine signature WITHOUT changing the fixture's assertion shape (so the downstream stage still sees exactly the fields it asserts on).
Requires the referenced <Assertion> to carry the ID matching the
Reference's URI. Raises if the structure cannot be located (a malformed test
fixture should fail loudly, not sign silently).
Build a genuinely-signed SAML <Response> and the matching cert chain.
Returns %{response_xml: binary(), cert_chain: [pem]}. Feed response_xml
through Relyra.consume_response/3 (or ValidationPipeline.run/4) with
cert_chain threaded onto the connection (:idp_certificates / :cert_chain)
or the :cert_chain opt, and the login verifies {:ok} for the RIGHT reason
(a real RSA signature + a real digest over the canonicalized assertion).
Options
:issuer,:destination,:recipient,:audience,:in_response_to,:name_id,:assertion_id,:status,:not_before,:not_on_or_after,:subject_confirmation_not_on_or_after— protocol field overrides so the same genuine signer can stand in for the fixed-shape structure-only Responses the triaged tests previously fed.:tamper_name_id— when set to a string, the emitted XML's<NameID>text is rewritten to that value AFTER signing (the signature/digest are NOT recomputed). The result is a Response whoseSignatureValueis well-formed but whose Reference digest no longer matches the (tampered) content — the:digest_mismatchnegative control (T-29-17). Defaults tonil(no tamper).