Software Statements
Current status: Safire uses this signing layer internally when Safire::Client#register_client and Safire::Client#cancel_registration submit UDAP Security STU2 registration lifecycle requests. Most applications should use those facade methods rather than construct software statements directly.
What Safire Builds
UDAP registration metadata is signed into a compact JWS software statement inside register_client and cancel_registration. The software statement carries the client registration parameters plus the required security claims:
| Claim | Safire behavior |
|---|---|
iss | Exact client_uri supplied by the caller |
sub | Same exact value as iss |
aud | Exact discovered registration endpoint |
iat | Integer NumericDate from the signing clock |
exp | iat + 300 seconds |
jti | Fresh UUID by default |
The JOSE header contains only alg and x5c. The x5c value is the supplied leaf-first, issuer-ordered certificate chain encoded as Base64 DER strings, without PEM wrappers.
Safire does not emit typ, kid, jku, or x5u for registration software statements.
Signing Identity
Configure a private key and a leaf-first, issuer-ordered certificate chain as the client signing identity:
config = Safire::ClientConfig.new(
base_url: 'https://fhir.example.com',
private_key: File.read('client-key.pem'),
certificate_chain: [
File.read('client-cert.pem'),
File.read('issuing-ca.pem')
],
jwt_algorithm: 'RS256'
)
The private key must match the leaf certificate. The leaf certificate must contain a URI Subject Alternative Name that exactly matches the client_uri used for registration. Safire does not canonicalize the URI before comparison: case, port, and trailing slash differences are significant.
The client_uri is not required to use HTTPS. STU2 permits trust communities to use other URI schemes for client identifiers, such as decentralized identifiers. If the client URI uses HTTP or HTTPS, it must include a host.
Algorithms
The registration server advertises supported software-statement algorithms in registration_endpoint_jwt_signing_alg_values_supported. Safire intersects that list with the configured private key:
| Key | Safire-supported algorithms |
|---|---|
| RSA | RS256, RS384 |
| EC P-256 | ES256 |
| EC P-384 | ES384 |
When jwt_algorithm is omitted, Safire selects the first compatible advertised algorithm. RSA keys prefer RS256 because it is the STU2 baseline. An explicit algorithm must be advertised by the server and compatible with the key. Safire raises DiscoveryError before signing if the server metadata omits mandatory RS256 registration signing support.
Validation Boundaries
Safire performs local consistency checks before signing:
- registration metadata must already be a
UdapRegistrationMetadataobject - certificate chains must be non-empty, leaf-first, and issuer-ordered
- certificate entries must be parseable PEM strings or
OpenSSL::X509::Certificateobjects - every certificate must be valid according to the signing clock when the builder is created and when the JWT is emitted
- the private key must contain private material and match the leaf certificate
- the client URI must exactly match a URI SAN in the leaf certificate
- the registration endpoint must be HTTPS unless
allow_insecure_localhost: trueis explicitly used for an HTTP loopback endpoint in development
Safire does not decide whether the authorization server trusts the client certificate. Chain validation, revocation status, and community trust for the client certificate are authorization-server decisions during registration.
See ADR-014 for the signing design rationale.