class WebAuthn::AttestationStatement::Apple

Constants

CERTIFICATE_EXTENSION_OID
ROOT_CERTIFICATE

Attributes

certs[RW]
x5c[RW]

Public Class Methods

new(x5c:) click to toggle source
# File lib/web_authn/attestation_statement/apple.rb, line 24
def initialize(x5c:)
  self.x5c = Array(x5c)
  self.certs = self.x5c.collect do |x5c|
    OpenSSL::X509::Certificate.new x5c
  end
end

Private Class Methods

decode(att_stmt) click to toggle source
# File lib/web_authn/attestation_statement/apple.rb, line 78
def decode(att_stmt)
  new(
    x5c: att_stmt[:x5c]
  )
end

Public Instance Methods

verify!(authenticator_data, client_data_json) click to toggle source
# File lib/web_authn/attestation_statement/apple.rb, line 31
def verify!(authenticator_data, client_data_json)
  verify_nonce! authenticator_data, client_data_json
  verify_certificate! authenticator_data.attested_credential_data
end

Private Instance Methods

verify_certificate!(attested_credential_data) click to toggle source
# File lib/web_authn/attestation_statement/apple.rb, line 52
def verify_certificate!(attested_credential_data)
  attested_cert = certs.first
  remaining_chain = certs[1..-1]

  store = OpenSSL::X509::Store.new
  store.add_cert OpenSSL::X509::Certificate.new ROOT_CERTIFICATE
  valid_chain = store.verify(attested_cert, remaining_chain)

  valid_timestamp = (
    attested_cert.not_after > Time.now &&
    attested_cert.not_before < Time.now
  )

  valid_attested_public_key = (
    attested_credential_data.public_key.to_pem ==
    attested_cert.public_key.to_pem
  )

  # TODO: do we need CRL check?

  unless valid_chain && valid_attested_public_key && valid_timestamp
     raise InvalidAttestation, 'Invalid Apple Response: certificate'
  end
end
verify_nonce!(authenticator_data, client_data_json) click to toggle source
# File lib/web_authn/attestation_statement/apple.rb, line 38
def verify_nonce!(authenticator_data, client_data_json)
  nonce = OpenSSL::Digest::SHA256.digest [
    authenticator_data.raw,
    OpenSSL::Digest::SHA256.digest(client_data_json.raw)
  ].join

  extension = certs.first.find_extension(CERTIFICATE_EXTENSION_OID)
  expected_nonce = OpenSSL::ASN1.decode(extension.value_der).first.value.first.value

  unless expected_nonce == nonce
    raise InvalidAttestation, 'Invalid Apple Response: nonce'
  end
end