Class: Safire::Protocols::Udap Private

Inherits:
Object
  • Object
show all
Includes:
Behaviours, OAuthResponseHandling
Defined in:
lib/safire/protocols/udap.rb

Overview

This class is part of a private API. You should avoid using this class if possible, as it may be removed or be changed in the future.

Note:

For internal use by Client only.

UDAP Security STU2 protocol implementation.

Handles server metadata discovery from the UDAP well-known endpoint (per STU2 §2) and Dynamic Client Registration (per STU2 §3). Discovery results are cached per community within each instance.

Other UDAP flows (B2B client credentials token acquisition, B2C authorization code, and Tiered OAuth) raise NotImplementedError and are planned for future PRs.

This is an internal class used exclusively by Client. Do not instantiate it directly — use Client instead.

Constant Summary collapse

WELL_KNOWN_PATH =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

'/.well-known/udap'.freeze

Instance Method Summary collapse

Methods included from Behaviours

#authorization_url, #refresh_token, #request_access_token, #request_backend_token, #token_response_valid?

Constructor Details

#initialize(config) ⇒ Udap

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns a new instance of Udap.



34
35
36
37
38
39
40
41
42
# File 'lib/safire/protocols/udap.rb', line 34

def initialize(config)
  @base_url = config.base_url
  @allow_insecure_localhost = config.allow_insecure_localhost
  @private_key = config.private_key
  @certificate_chain = config.certificate_chain
  @jwt_algorithm = config.jwt_algorithm
  @http_client = Safire::HTTPClient.new(allow_insecure_localhost: @allow_insecure_localhost)
  @metadata_cache = {}
end

Instance Method Details

#cancel_registration(metadata, client_uri:, community: nil, certifications: nil, trusted_anchors: [], crls: [], revocation_checker: nil, verify_chain: true, private_key: @private_key, certificate_chain: @certificate_chain, jwt_algorithm: @jwt_algorithm) ⇒ Hash

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Cancels an existing UDAP Dynamic Client Registration.

Cancellation uses the same discovery-bound endpoint, trust policy, certifications, and X.509-backed software-statement signing as #register_client. Safire builds the software statement in cancellation mode, which injects an empty grant_types array and rejects any caller-supplied grant_types value.

STU2 defines cancellation confirmation by the successful response body: client_id must be present and grant_types must be an empty array.

Parameters:

  • metadata (Hash)

    identifying UDAP client metadata; omit grant_types

  • client_uri (String)

    exact URI used as iss and sub and required to appear as a URI SAN in the leaf certificate

  • community (String, nil) (defaults to: nil)

    optional UDAP community URI for discovery

  • certifications (Array<String>, nil) (defaults to: nil)

    optional third-party certification JWTs; nil omits the field and [] sends an explicit empty collection

  • trusted_anchors (Array<OpenSSL::X509::Certificate>) (defaults to: [])

    server trust anchors for signed metadata validation

  • crls (Array<OpenSSL::X509::CRL>) (defaults to: [])

    revocation lists for signed metadata validation

  • revocation_checker (#call, nil) (defaults to: nil)

    custom server certificate revocation policy

  • verify_chain (Boolean) (defaults to: true)

    whether discovery signed_metadata chain validation is required

  • private_key (OpenSSL::PKey::RSA, OpenSSL::PKey::EC, String, nil) (defaults to: @private_key)

    client signing private key; defaults to configuration

  • certificate_chain (Array<String, OpenSSL::X509::Certificate>, nil) (defaults to: @certificate_chain)

    leaf-first client signing certificate chain; defaults to configuration

  • jwt_algorithm (String, nil) (defaults to: @jwt_algorithm)

    optional explicit registration signing algorithm

Returns:

  • (Hash)

    cancellation response from the authorization server

Raises:



190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
# File 'lib/safire/protocols/udap.rb', line 190

def cancel_registration(, client_uri:, community: nil, certifications: nil, trusted_anchors: [],
                        crls: [], revocation_checker: nil, verify_chain: true,
                        private_key: @private_key, certificate_chain: @certificate_chain,
                        jwt_algorithm: @jwt_algorithm)
  response = submit_registration_request(
    ,
    operation: :cancel,
    action: CANCEL_ACTION,
    client_uri:,
    community:,
    trusted_anchors:,
    crls:,
    revocation_checker:,
    verify_chain:,
    certifications:,
    private_key:,
    certificate_chain:,
    jwt_algorithm:
  )
  parse_cancellation_response(response)
rescue Faraday::Error => e
  raise registration_error_from(e)
end

#register_client(metadata, client_uri:, community: nil, certifications: nil, trusted_anchors: [], crls: [], revocation_checker: nil, verify_chain: true, private_key: @private_key, certificate_chain: @certificate_chain, jwt_algorithm: @jwt_algorithm) ⇒ Hash

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Dynamically registers or modifies a UDAP client using STU2 Dynamic Client Registration.

UDAP registration is discovery-bound: Safire first discovers and validates UDAP metadata, then posts a fixed request envelope to the discovered registration_endpoint. The caller-provided metadata is validated and signed into the software_statement JWT; it is not duplicated at the top level.

Calling this method again with the same client_uri and community requests modification of the existing registration. Safire accepts both 201 Created and update-style 200 responses as long as the response is a JSON object with a non-blank string client_id.

Parameters:

  • metadata (Hash)

    caller-controlled UDAP registration metadata

  • client_uri (String)

    exact URI used as iss and sub and required to appear as a URI SAN in the leaf certificate

  • community (String, nil) (defaults to: nil)

    optional UDAP community URI for discovery

  • certifications (Array<String>, nil) (defaults to: nil)

    optional third-party certification JWTs; nil omits the field and [] sends an explicit empty collection

  • trusted_anchors (Array<OpenSSL::X509::Certificate>) (defaults to: [])

    server trust anchors for signed metadata validation

  • crls (Array<OpenSSL::X509::CRL>) (defaults to: [])

    revocation lists for signed metadata validation

  • revocation_checker (#call, nil) (defaults to: nil)

    custom server certificate revocation policy

  • verify_chain (Boolean) (defaults to: true)

    whether discovery signed_metadata chain validation is required

  • private_key (OpenSSL::PKey::RSA, OpenSSL::PKey::EC, String, nil) (defaults to: @private_key)

    client signing private key; defaults to configuration

  • certificate_chain (Array<String, OpenSSL::X509::Certificate>, nil) (defaults to: @certificate_chain)

    leaf-first client signing certificate chain; defaults to configuration

  • jwt_algorithm (String, nil) (defaults to: @jwt_algorithm)

    optional explicit registration signing algorithm

Returns:

  • (Hash)

    registration response from the authorization server

Raises:



129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
# File 'lib/safire/protocols/udap.rb', line 129

def register_client(, client_uri:, community: nil, certifications: nil, trusted_anchors: [],
                    crls: [], revocation_checker: nil, verify_chain: true,
                    private_key: @private_key, certificate_chain: @certificate_chain,
                    jwt_algorithm: @jwt_algorithm)
  response = submit_registration_request(
    ,
    operation: :register,
    action: REGISTER_ACTION,
    client_uri:,
    community:,
    trusted_anchors:,
    crls:,
    revocation_checker:,
    verify_chain:,
    certifications:,
    private_key:,
    certificate_chain:,
    jwt_algorithm:
  )
  validate_registration_response_status!(response)
  parse_registration_response(response.body)
rescue Faraday::Error => e
  raise registration_error_from(e)
end

#server_metadata(community: nil, trusted_anchors: [], crls: [], revocation_checker: nil, verify_chain: true) ⇒ Safire::Protocols::UdapMetadata

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Retrieves and parses UDAP server metadata from the well-known endpoint.

When a community URI is provided, the request is scoped to that community by appending ?community=<encoded-uri> to the endpoint URL. Results are cached per community — subsequent calls with the same community return the cached result without a second HTTP request, as long as its signed_metadata still validates against the current trust policy.

The signed_metadata JWT in the discovery response is validated per UDAP Security STU2. Signed endpoint claims (+token_endpoint+, registration_endpoint, and optionally authorization_endpoint) are merged over the unsigned values before the metadata object is constructed.

Parameters:

  • community (String, nil) (defaults to: nil)

    optional UDAP community URI; scopes discovery

  • trusted_anchors (Array<OpenSSL::X509::Certificate>) (defaults to: [])

    X.509 trust anchors for signed_metadata chain verification; required for production use

  • crls (Array<OpenSSL::X509::CRL>) (defaults to: [])

    certificate revocation lists for production chain validation

  • revocation_checker (#call, nil) (defaults to: nil)

    custom revocation policy; must return true to pass

  • verify_chain (Boolean) (defaults to: true)

    when false, skips X.509 chain validation (dev/test only)

Returns:

Raises:



69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
# File 'lib/safire/protocols/udap.rb', line 69

def (community: nil, trusted_anchors: [], crls: [], revocation_checker: nil, verify_chain: true)
  community = normalize_community(community)
  trust_policy = {
    trusted_anchors:,
    crls:,
    revocation_checker:,
    verify_chain:,
    allow_insecure_localhost: @allow_insecure_localhost
  }
  cache_key = build_cache_key(community, trusted_anchors, crls, revocation_checker, verify_chain)
  cached_entry = @metadata_cache[cache_key]
  return cached_entry.fetch(:metadata) if cached_entry && cached_entry_valid?(cached_entry, trust_policy)

  @metadata_cache.delete(cache_key)

  entry = (
    community:,
    trust_policy:
  )
  @metadata_cache[cache_key] = entry
  entry.fetch(:metadata)
end