Class: IosAppAttest::Validators::ChallengeValidator

Inherits:
BaseValidator
  • Object
show all
Defined in:
lib/ios_app_attest/validators/challenge_validator.rb

Overview

Validates challenge nonce and related aspects of the attestation

This validator is responsible for verifying the challenge nonce used in the attestation process. It validates that the nonce is valid, not expired, and matches the expected value. It also verifies the key ID against the certificate's public key and provides methods for decrypting challenges.

Examples:

validator = IosAppAttest::Validators::ChallengeValidator.new(config, redis_client: redis)
validator.validate_nonce(challenge_id, challenge_decrypted)
validator.validate_challenge(cred_cert, challenge_decrypted, auth_data)
validator.validate_key_id(cred_cert, key_id)

Instance Attribute Summary collapse

Attributes inherited from BaseValidator

#config, #logger

Instance Method Summary collapse

Constructor Details

#initialize(config, redis_client: nil, logger: nil) ⇒ ChallengeValidator

Initialize the challenge validator

This initializes the validator with configuration and optional Redis client. The Redis client is used to store and retrieve nonces for verification. If no Redis client is provided, nonce verification will be skipped.



29
30
31
32
# File 'lib/ios_app_attest/validators/challenge_validator.rb', line 29

def initialize(config, redis_client: nil, logger: nil)
  super(config, logger: logger)
  @redis_client = redis_client
end

Instance Attribute Details

#redis_client ⇒ Object (readonly)

Returns the value of attribute redis_client.



18
19
20
# File 'lib/ios_app_attest/validators/challenge_validator.rb', line 18

def redis_client
  @redis_client
end

Instance Method Details

#decrypt_challenge(challenge, iv) ⇒ String

Decrypt challenge using AES-256-CBC

This method decrypts the challenge nonce using AES-256-CBC encryption with the provided initialization vector and the encryption key from configuration.



108
109
110
111
112
113
114
# File 'lib/ios_app_attest/validators/challenge_validator.rb', line 108

def decrypt_challenge(challenge, iv)
  cipher = OpenSSL::Cipher::AES256.new(:CBC)
  cipher.decrypt
  cipher.key = encryption_key
  cipher.iv = iv
  cipher.update(challenge) + cipher.final
end

#validate_challenge(cred_cert, challenge_decrypted, auth_data) ⇒ Object

Verify challenge nonce from certificate

This method verifies that the challenge nonce was correctly incorporated into the attestation by checking that the certificate contains a hash derived from the authentication data and challenge nonce. This ensures the attestation was created specifically for this challenge.

Raises:



69
70
71
72
73
74
75
76
77
78
79
80
# File 'lib/ios_app_attest/validators/challenge_validator.rb', line 69

def validate_challenge(cred_cert, challenge_decrypted, auth_data)
  challenge_hash = sha256.digest(challenge_decrypted)

  extension = cred_cert.extensions.find { |e| e.oid == app_attest_oid }
  sequence = OpenSSL::ASN1.decode(OpenSSL::ASN1.decode(extension.to_der).value[1].value)
  to_verify = sequence.value[0].value[0].value

  expected_hash = sha256.digest(auth_data + challenge_hash)
  unless to_verify == expected_hash
    raise IosAppAttest::ChallengeError, 'Challenge verification failed'
  end
end

#validate_key_id(cred_cert, key_id) ⇒ Object

Validate the key ID matches the certificate's public key

This method verifies that the key ID provided in the attestation parameters matches the hash of the public key from the credential certificate. This ensures the attestation is using the correct key pair.

Raises:



91
92
93
94
95
96
97
98
# File 'lib/ios_app_attest/validators/challenge_validator.rb', line 91

def validate_key_id(cred_cert, key_id)
  uncompressed_point_key = cred_cert.public_key.public_key.to_octet_string(:uncompressed)
  expected_key_id = Base64.strict_encode64(sha256.digest(uncompressed_point_key))
  
  unless key_id == expected_key_id
    raise IosAppAttest::ChallengeError, 'Key ID verification failed'
  end
end

#validate_nonce(challenge_id, challenge_decrypted) ⇒ Object

Note:

This method requires a Redis client to be provided during initialization. If no Redis client is available, this validation is skipped.

Validate the challenge nonce against stored value

This method verifies that the provided challenge nonce matches the one previously stored in Redis. After successful validation, the nonce is deleted from Redis to prevent replay attacks.

Raises:



46
47
48
49
50
51
52
53
54
55
56
# File 'lib/ios_app_attest/validators/challenge_validator.rb', line 46

def validate_nonce(challenge_id, challenge_decrypted)
  return unless redis_client
  
  nonce = redis_client.get("nonce:#{challenge_id}")
  unless nonce && nonce == encode_base64(challenge_decrypted)
    raise IosAppAttest::ChallengeError, "Invalid or expired challenge nonce"
  end
  
  # Delete the nonce after successful validation to prevent replay attacks
  redis_client.del("nonce:#{challenge_id}")
end