class Gala::PaymentToken

Constants

APPLE_ROOT_CERT
INTERMEDIATE_CERTIFICATE_OID
LEAF_CERTIFICATE_OID
MERCHANT_ID_FIELD_OID

Attributes

application_data[RW]
data[RW]
ephemeral_public_key[RW]
public_key_hash[RW]
signature[RW]
transaction_id[RW]
version[RW]

Public Class Methods

chain_of_trust_verified?(leaf_cert, intermediate_cert, root_cert) click to toggle source
# File lib/gala/payment_token.rb, line 73
def chain_of_trust_verified?(leaf_cert, intermediate_cert, root_cert)
  trusted_certificate_store = OpenSSL::X509::Store.new.tap do |store|
    store.add_cert(root_cert)
    store.add_cert(intermediate_cert)
  end
  trusted_certificate_store.verify(leaf_cert)
end
decrypt(encrypted_data, symmetric_key) click to toggle source
# File lib/gala/payment_token.rb, line 112
def decrypt(encrypted_data, symmetric_key)
  # Initialization vector of 16 null bytes
  iv_length = 16
  # 0.chr => "\x00"
  iv = 0.chr * iv_length

  # Last 16 bytes (iv_length) of encrypted data
  tag = encrypted_data[-iv_length..-1]
  # Data without tag
  encrypted_data = encrypted_data[0..(-iv_length - 1)]

  cipher = OpenSSL::Cipher.new("aes-256-gcm").decrypt
  cipher.key = symmetric_key
  cipher.iv_len = iv_length
  cipher.iv = iv

  # Decipher without associated authentication data
  cipher.auth_tag = tag
  cipher.auth_data = ''

  cipher.update(encrypted_data) + cipher.final
end
extract_merchant_id(certificate) click to toggle source
# File lib/gala/payment_token.rb, line 81
def extract_merchant_id(certificate)
  merchant_id_field = certificate.extensions.find do |ext|
    ext.oid == MERCHANT_ID_FIELD_OID
  end
  raise MissingMerchantIdError unless merchant_id_field
  val = merchant_id_field.value
  val[2..(val.length - 1)]
end
generate_shared_secret(private_key, ephemeral_public_key) click to toggle source
# File lib/gala/payment_token.rb, line 90
def generate_shared_secret(private_key, ephemeral_public_key)
  public_ec = OpenSSL::PKey::EC.new(Base64.decode64(ephemeral_public_key))
  point = OpenSSL::PKey::EC::Point.new(private_key.group, public_ec.public_key.to_bn)
  private_key.dh_compute_key(point)
end
generate_symmetric_key(merchant_id, shared_secret) click to toggle source

Derive the symmetric key using the key derivation function described in NIST SP 800-56A, section 5.8.1

http://csrc.nist.gov/publications/nistpubs/800-56A/SP800-56A_Revision1_Mar08-2007.pdf
# File lib/gala/payment_token.rb, line 98
def generate_symmetric_key(merchant_id, shared_secret)

  kdf_algorithm = "\x0D" + 'id-aes256-GCM'
  kdf_party_v = merchant_id.scan(/../).inject("") { |binary,hn| binary << hn.to_i(16).chr } # Converts each pair of hex characters into bytes in a string.
  kdf_info = kdf_algorithm + "Apple" + kdf_party_v

  digest = Digest::SHA256.new
  digest << 0.chr * 3
  digest << 1.chr
  digest << shared_secret
  digest << kdf_info
  digest.digest
end
new(token_attrs) click to toggle source
# File lib/gala/payment_token.rb, line 18
def initialize(token_attrs)
  self.version = token_attrs["version"]
  self.data = token_attrs["data"]
  self.signature = token_attrs["signature"]
  headers = token_attrs["header"]
  self.transaction_id = headers["transactionId"]
  self.ephemeral_public_key = headers["ephemeralPublicKey"]
  self.public_key_hash = headers["publicKeyHash"]
  self.application_data = headers["applicationData"]
end
validate_signature(signature, ephemeral_public_key, data, transaction_id, application_data) click to toggle source
# File lib/gala/payment_token.rb, line 44
def validate_signature(signature, ephemeral_public_key, data, transaction_id, application_data)
  # Ensure that the certificates contain the correct custom OIDs
  intermediate_cert = nil
  leaf_cert = nil
  p7 = OpenSSL::PKCS7.new(Base64.decode64(signature))
  p7.certificates.each {|c|
    c.extensions.each { |e|
      leaf_cert = c if e.oid == LEAF_CERTIFICATE_OID
      intermediate_cert = c if e.oid == INTERMEDIATE_CERTIFICATE_OID
    }
  }
  raise InvalidSignatureError, "Signature does not contain the correct custom OIDs." unless leaf_cert && intermediate_cert

  # Ensure that the root CA is the Apple Root CA - G3
  root_cert = OpenSSL::X509::Certificate.new(APPLE_ROOT_CERT)

  # Ensure that there is a valid X.509 chain of trust from the signature to the root CA
  raise InvalidSignatureError, "Unable to verify a valid chain of trust from signature to root certificate." unless chain_of_trust_verified?(leaf_cert, intermediate_cert, root_cert)

  #Ensure that the signature is a valid ECDSA signature
  unless application_data
    verification_string = Base64.decode64(ephemeral_public_key) + Base64.decode64(data) + [transaction_id].pack("H*")
    # verification_string = verification_string + application_data.pack("H*") if application_data
    store = OpenSSL::X509::Store.new
    verified = p7.verify([], store, verification_string, OpenSSL::PKCS7::NOVERIFY )
    raise InvalidSignatureError, "The given signature is not a valid ECDSA signature." unless verified
  end
end

Public Instance Methods

decrypt(certificate_pem, private_key_pem) click to toggle source
# File lib/gala/payment_token.rb, line 29
def decrypt(certificate_pem, private_key_pem)
  self.class.validate_signature(signature, ephemeral_public_key, data, transaction_id, application_data)

  certificate = OpenSSL::X509::Certificate.new(certificate_pem)
  merchant_id = self.class.extract_merchant_id(certificate)
  private_key = OpenSSL::PKey::EC.new(private_key_pem)
  shared_secret = self.class.generate_shared_secret(private_key, ephemeral_public_key)
  symmetric_key = self.class.generate_symmetric_key(merchant_id, shared_secret)

  # Return JSON string, up to caller to parse
  self.class.decrypt(Base64.decode64(data), symmetric_key)
end