Module: BSV::Auth::ValidateCertificates

Defined in:
lib/bsv/auth/validate_certificates.rb

Overview

Utility module for validating certificates received in an authenticated message.

Exposes a single module method, call, which is also available as BSV::Auth.validate_certificates via extend.

Algorithm:

  1. Raise AuthError if message[:certificates] is nil or empty.

  2. For each certificate in the array:

    1. Verify cert subject == message identity key.

    2. Construct a VerifiableCertificate if the input is a plain Hash.

    3. Call cert.verify — raise if signature is invalid.

    4. If requested_certificates is provided, check certifier and type.

    5. Call cert.decrypt_fields(wallet) — wrap any error in AuthError.

Wallet is duck-typed — any object responding to verify_signature and decrypt is accepted.

Class Method Summary collapse

Class Method Details

.validate_certificates(wallet, message, requested_certificates = nil) ⇒ Object

Validates certificates attached to an incoming authenticated message.

Parameters:

  • wallet (#verify_signature, #decrypt)

    the verifier’s wallet

  • message (Hash)

    incoming authenticated message; must contain :certificates and :identity_key (symbol or string keys accepted)

  • requested_certificates (Hash, nil) (defaults to: nil)

    optional filter with keys :certifiers (Array of pubkey hex strings) and :types (Hash of type string => fields)

Raises:



33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
# File 'lib/bsv/auth/validate_certificates.rb', line 33

def validate_certificates(wallet, message, requested_certificates = nil)
  certs = message[:certificates] || message['certificates']
  raise AuthError, 'No certificates were provided in the message.' if certs.nil? || certs.empty?

  identity_key = message[:identity_key] || message['identity_key']

  certs.each do |incoming_cert|
    subject = if incoming_cert.is_a?(Hash)
                incoming_cert[:subject] || incoming_cert['subject']
              else
                incoming_cert.subject
              end

    if subject != identity_key
      raise AuthError,
            "The subject of one of your certificates (\"#{subject}\") is not the same as " \
            "the message sender (\"#{identity_key}\")."
    end

    cert = if incoming_cert.is_a?(VerifiableCertificate)
             incoming_cert
           else
             VerifiableCertificate.from_hash(incoming_cert)
           end

    begin
      valid = cert.verify
    rescue ArgumentError => e
      raise AuthError, "Certificate signature verification failed: #{e.message}"
    end

    unless valid
      raise AuthError,
            "The signature for the certificate with serial number #{cert.serial_number} is invalid!"
    end

    if requested_certificates
      certifiers = requested_certificates[:certifiers] || requested_certificates['certifiers'] || []
      types = requested_certificates[:types] || requested_certificates['types'] || {}

      unless certifiers.include?(cert.certifier)
        raise AuthError,
              "Certificate with serial number #{cert.serial_number} has an unrequested certifier: #{cert.certifier}"
      end

      unless types.key?(cert.type)
        raise AuthError,
              "Certificate with type #{cert.type} was not requested"
      end
    end

    begin
      cert.decrypt_fields(wallet)
    rescue ArgumentError, RuntimeError => e
      raise AuthError, "Failed to decrypt certificate fields: #{e.message}"
    end
  end
end