Module: Portage::Ucp::Ap2::MandateSignature
- Defined in:
- lib/portage/ucp/ap2/mandate_signature.rb
Overview
Cryptographic verification of a PaymentMandate's signature — the
piece MandateGuard's own comment (and PaymentMandate's, before this
file existed) flagged as deliberately missing: "no key infrastructure
or trust anchor exists in this repo to verify a signature against."
A caller that now has a trust anchor (a real PSP adapter, or a
platform-provided key resolver) passes it as trusted_keys: to get
actual proof instead of shape-only validation.
Same ECDSA/JWK wire conventions as Security::Signature (P-256 mandatory/P-384 optional, raw r||s signature bytes, JWK x/y coordinates) since that curve support is already proven against this gem's RFC 9421 verifier — but deliberately a separate class, not a shared one: a mandate isn't an HTTP request (no method/path/headers to canonicalize, just PaymentMandate#signing_payload), and Security::Signature's own class doc already frames every signing story in this gem as deliberately distinct rather than unified under one abstraction.
Trust anchor: trusted_keys here is the mandate issuer's key set
(the shopper's agent, or the agent platform vouching for it) — a
different trust root than Security::Signature's trusted_keys (the
calling platform's own request-signing key), even though both reuse
the same flat-JWK-array-or-#call(kid)-resolver shape (§9's
convention, reused again here rather than inventing a second
differently-shaped key config for the same job).
Constant Summary collapse
- CURVES =
{ "P-256" => { oid: "1.2.840.10045.3.1.7", coord: 32, digest: "SHA256" }, "P-384" => { oid: "1.3.132.0.34", coord: 48, digest: "SHA384" } }.freeze
- EC_PUBLIC_KEY_OID =
"1.2.840.10045.2.1".freeze
Class Method Summary collapse
-
.verify!(mandate, trusted_keys:) ⇒ true
Never a falsy result; raises Portage::Ucp::InvalidMandateError on any failure so a caller can't accidentally treat "didn't check" as "checked and passed" (same convention as Security::Signature.verify!).
Class Method Details
.verify!(mandate, trusted_keys:) ⇒ true
Returns never a falsy result; raises Portage::Ucp::InvalidMandateError on any failure so a caller can't accidentally treat "didn't check" as "checked and passed" (same convention as Security::Signature.verify!).
47 48 49 50 51 52 53 54 55 56 57 58 59 60 |
# File 'lib/portage/ucp/ap2/mandate_signature.rb', line 47 def self.verify!(mandate, trusted_keys:) jwk = trust_key!(mandate, trusted_keys) curve = curve_for!(jwk) raw_signature = sized_signature!(mandate, curve) key = ec_public_key(jwk, curve) der = raw_to_der(raw_signature, curve[:coord]) verified = key.verify(curve[:digest], der, mandate.signing_payload) raise Portage::Ucp::InvalidMandateError, "mandate signature does not verify" unless verified true rescue OpenSSL::PKey::PKeyError, OpenSSL::PKey::EC::Point::Error, OpenSSL::ASN1::ASN1Error => e raise Portage::Ucp::InvalidMandateError, "mandate signature verification failed: #{e.}" end |