Relyra.TestSupport.FakeIdP (relyra v1.5.3)

Copy Markdown View Source

A small in-process SAML response builder for tests.

The fake IdP does not attempt to model a real admin UI or cryptographic signing pipeline. It builds protocol-correct XML that exercises the SP pipeline, including the signature and assertion parsing paths used by the test suite.

Summary

Functions

The XML-Enc algorithm URIs the encrypt path knows about, as a map.

Wrap a plaintext (typically a genuinely-signed <Assertion> fragment) into an <EncryptedAssertion> envelope using RSA-OAEP key transport + AES-256-GCM content encryption against the SP public key, mirroring how sign/2 is the single canonical signer (D-08).

Adversarial-aware variant of encrypt/2.

Build a complete SAML <Response> binary carrying an <EncryptedAssertion> in place of a cleartext <Assertion> — the shape ValidationPipeline.run/4 will consume (Plan 04).

The self-signed certificate PEM (a single-element cert chain) callers must trust to accept a FakeIdP.sign/2-produced Response. Derived from keypair/0 via the promoted genuine signer (D-03), so configuring a connection's cert_chain / idp_certificates with this PEM lets the verifier accept FakeIdP's real signatures.

Functions

build_response(opts \\ [])

@spec build_response(keyword()) :: Relyra.TestSupport.FakeIdP.Builder.t()

enc_algorithm_uris()

@spec enc_algorithm_uris() :: %{
  rsa_oaep: String.t(),
  aes256_gcm: String.t(),
  rsa_pkcs1: String.t(),
  aes256_cbc: String.t()
}

The XML-Enc algorithm URIs the encrypt path knows about, as a map.

Plan 04 adversarial fixtures read the blocked variants (:rsa_pkcs1 and :aes256_cbc) from here so the URI strings live in exactly one place — the single canonical generator — rather than being re-typed per fixture.

encrypt(plaintext, sp_pub_key)

@spec encrypt(binary(), :public_key.rsa_public_key()) :: String.t()

Wrap a plaintext (typically a genuinely-signed <Assertion> fragment) into an <EncryptedAssertion> envelope using RSA-OAEP key transport + AES-256-GCM content encryption against the SP public key, mirroring how sign/2 is the single canonical signer (D-08).

This is the single canonical encrypted-assertion generator — every ENC-01 adversarial fixture (Plan 04) wraps its plaintext through here so a divergent per-test recipe can never mask a real canonicalization or auth-tag bug (Pitfall 1 / T-34-04).

The CipherValue layout is exactly Base.encode64(iv <> ciphertext <> auth_tag) with a 12-byte IV and a 16-byte GCM tag — the IV(12) || CT || Tag(16) layout Relyra.Security.XMLEnc.split_cipher_value/1 round-trips (Pitfall 4 / T-34-05). Feeding the output to XMLEnc.decrypt/3 with the matching SP private key resolver returns {:ok, plaintext} (byte identity).

Only the SP public key {:RSAPublicKey, n, e} is used here; no private key material touches the encrypt path (T-34-06). Derive sp_pub_key from keypair/0 the way xml_enc_test.exs:13-15 does.

encrypt(plaintext, sp_pub_key, opts)

@spec encrypt(binary(), :public_key.rsa_public_key(), keyword()) :: String.t()

Adversarial-aware variant of encrypt/2.

Options let Plan 04 fixtures deliberately diverge from the accepted recipe so the SP pipeline can be proven to fail closed:

  • :key_transport_uri — algorithm URI advertised for the wrapped CEK (default rsa-oaep-mgf1p; rsa-1_5 for the blocked-PKCS1v1.5 fixture).
  • :key_padding — the :rsa_padding atom actually used to wrap the CEK (default :rsa_pkcs1_oaep_padding).
  • :content_uri — algorithm URI advertised for content encryption (default aes256-gcm; aes256-cbc for the blocked-CBC fixture).
  • :tag_length — GCM auth-tag byte length (default 16; e.g. 15 for the truncated-tag fixture).
  • :iv — explicit 12-byte IV (default random).
  • :cipher_value_b64 — override the content CipherValue text verbatim (e.g. malformed base64 for the bad-ciphertext fixture); skips content encryption.

encrypted_response(response_opts \\ [], encrypt_opts \\ [])

@spec encrypted_response(keyword(), keyword()) :: String.t()

Build a complete SAML <Response> binary carrying an <EncryptedAssertion> in place of a cleartext <Assertion> — the shape ValidationPipeline.run/4 will consume (Plan 04).

The inner <Assertion> is genuinely signed FIRST via the canonical XmldsigSigner path, THEN encrypted, so the post-decrypt bytes carry the real DigestValue / SignatureValue the verifier checks (RESEARCH note line 381). The encrypted <Assertion> (and its sibling <Signature>) carry their own xmlns="urn:oasis:names:tc:SAML:2.0:assertion" so canonical bytes survive the decrypt → re-parse → splice (Pitfall 1 / T-34-04).

response_opts are forwarded to the signer (:issuer, :name_id, :assertion_id, :destination, …); encrypt_opts are forwarded to encrypt/3 (the adversarial overrides).

keypair()

@spec keypair() :: term()

metadata()

@spec metadata() :: String.t()

self_signed_cert_pem()

@spec self_signed_cert_pem() :: String.t()

The self-signed certificate PEM (a single-element cert chain) callers must trust to accept a FakeIdP.sign/2-produced Response. Derived from keypair/0 via the promoted genuine signer (D-03), so configuring a connection's cert_chain / idp_certificates with this PEM lets the verifier accept FakeIdP's real signatures.

sign(opts, extra_opts \\ [])

@spec sign(
  Relyra.TestSupport.FakeIdP.Builder.t() | keyword(),
  keyword()
) :: String.t()