0.0
The project is in a healthy, maintained state
A Ruby library for verifying iOS App Attestation tokens
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
 Dependencies

Development

~> 2.0
~> 13.0
~> 3.0

Runtime

~> 0.5.9
~> 3.0
 Project Readme

iOS App Attest

A Ruby gem for verifying iOS App Attest tokens - Apple's device attestation mechanism for iOS apps.

Installation

Add this line to your application's Gemfile:

gem 'ios_app_attest'

And then execute:

$ bundle install

Or install it yourself as:

$ gem install ios_app_attest

Usage

Configuration

Configure the gem with your app's specific settings:

IosAppAttest.configure do |config|
  config.app_id = "TEAM_ID.BUNDLE_ID" # Your Apple Team ID and Bundle ID
  config.encryption_key = ENV.fetch("IOS_APP_ATTEST_TOKEN").byteslice(0, 32) # Your encryption key (32 bytes)
end

Note: The Apple App Attestation root CA certificate and App Attest OID ("1.2.840.113635.100.8.2") are now hardcoded in the gem for security and convenience.

Complete Attestation Flow

1. Generating and Storing Challenge Nonces

When a client requests a nonce, your server generates it and stores it in Redis with a TTL:

# Create a Redis client for nonce storage
redis = Redis.new(url: ENV["REDIS_URL"] || "redis://localhost:6379/0")

# Create a nonce generator
nonce_generator = IosAppAttest::NonceGenerator.new(
  redis_client: redis,
  logger: Rails.logger, # Optional
  expiry_seconds: 300 # Optional: Nonce expiry time in seconds (default: 120)
)

# Generate a nonce (this also stores it in Redis with the configured TTL)
nonce_data = nonce_generator.generate

# The nonce_data contains:
# - challenge_nonce_id: A unique identifier for the challenge (used as Redis key)
# - challenge_nonce: The encrypted challenge nonce (base64 encoded)
# - initialization_vector: The IV used for encryption (base64 encoded)

# Send this data to the client for attestation

2. Client-Side Processing

# On the client side (iOS app):
# 1. Receive the nonce data from the server
# 2. Decrypt the challenge_nonce using the same encryption key:
#    a. Base64 decode the challenge_nonce and initialization_vector
#    b. Use AES-256-CBC with the shared encryption key to decrypt the challenge
# 3. Use the decrypted nonce in the App Attestation process
# 4. Send the attestation object back to the server along with the original nonce data

3. Server-Side Verification

When the client sends back the attestation object along with the original nonce data, the server:

# Attestation parameters from the client
attestation_params = {
  attestation_object: "base64_encoded_attestation_object",
  key_id: "base64_encoded_key_id",
  challenge_nonce: "base64_encoded_challenge", # The encrypted challenge nonce (base64 encoded)
  initialization_vector: "base64_encoded_initialization_vector", # The IV used for encryption (base64 encoded)
  challenge_nonce_id: "challenge_id" # The unique identifier for the challenge
}

# Create a verifier with Redis client for nonce verification
verifier = IosAppAttest::Verifier.new(
  attestation_params,
  redis_client: redis, # Redis client for nonce verification (required for TTL check)
  logger: Rails.logger # Optional: Logger for error logging
)

begin
  # Verify the attestation - this process includes:
  # 1. Decrypting the challenge nonce using the encryption key
  # 2. Checking if the nonce exists in Redis (validates TTL)
  # 3. Validating the attestation structure and certificates
  # 4. Verifying the nonce matches what was used in the attestation
  public_key, receipt = verifier.verify
  
  # Use the public_key and receipt for further processing
  # e.g., store the public_key for future authentications
rescue IosAppAttest::CertificateError => e
  # Handle certificate validation errors
  puts "Certificate validation failed: #{e.message}"
rescue IosAppAttest::ChallengeError => e
  # Handle challenge validation errors
  puts "Challenge validation failed: #{e.message}"
rescue IosAppAttest::AttestationError => e
  # Handle attestation format errors
  puts "Attestation format invalid: #{e.message}"
rescue IosAppAttest::AppIdentityError => e
  # Handle app identity validation errors
  puts "App identity validation failed: #{e.message}"
rescue IosAppAttest::NonceError => e
  # Handle nonce validation errors
  puts "Nonce validation failed: #{e.message}"
rescue IosAppAttest::VerificationError => e
  # Handle other verification errors
  puts "Verification failed: #{e.message}"
end

Complete Flow

Here's the complete flow for implementing iOS App Attestation in your application:

  1. Server-side: Generate a challenge nonce

    nonce_data = nonce_generator.generate
    # Returns: { challenge_nonce_id:, challenge_nonce:, initialization_vector: }
  2. Send to Client: Send the nonce data to your iOS client

  3. Client-side: The iOS client uses the nonce to generate an attestation using Apple's DeviceCheck framework

    // Swift code (client-side)
    let service = DCAppAttestService.shared
    if service.isSupported {
      // Generate a new key pair
      service.generateKey { keyId, error in
        // Use the keyId and challenge to generate an attestation
        service.attestKey(keyId, clientDataHash: challengeHash) { attestation, error in
          // Send attestation, keyId, and challenge data back to server
        }
      }
    }
  4. Server-side: Verify the attestation

    verifier = IosAppAttest::Verifier.new(
      attestation_params,
      redis_client: redis
    )
    
    public_key, receipt = verifier.verify
    # Store the public_key for future authentications
  5. Future Assertions: For subsequent requests, the client generates assertions that can be verified using the stored public key

Parameter Reference

The library uses the following parameter names:

Parameter Name Description
challenge_nonce_id Unique identifier for the challenge nonce
challenge_nonce Base64-encoded encrypted challenge nonce
initialization_vector Base64-encoded initialization vector used for encryption
attestation_object Base64-encoded attestation object from Apple's DeviceCheck framework
key_id Base64-encoded key ID generated by the client

Development

After checking out the repo, run bin/setup to install dependencies. Then, run rake spec to run the tests. You can also run bin/console for an interactive prompt that will allow you to experiment.

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/chaitra-mudili/ios-app-attest-ruby.

License

The gem is available as open source under the terms of the MIT License.