Skip to content

Commit

Permalink
fix: apple attestation key validation (bcgov#17)
Browse files Browse the repository at this point in the history
Signed-off-by: Jason C. Leach <[email protected]>
  • Loading branch information
jleach authored Jan 19, 2024
1 parent 575d2d0 commit 34be00a
Show file tree
Hide file tree
Showing 5 changed files with 61 additions and 49 deletions.
9 changes: 4 additions & 5 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@
"$schema": "https://aka.ms/vscode-remote/devcontainer-schema",
"name": "Python3 and Redis",
"dockerComposeFile": [
"docker-compose.yml",
"docker-compose.workspace.yml"
"docker-compose.yml"
],
"service": "workspace",
// "forwardPorts": [
Expand All @@ -24,10 +23,10 @@
"vscode": {
"extensions": [
"ms-python.python",
"github.copilot",
"github.copilot-chat",
"ms-python.flake8",
"ms-python.black-formatter"
"ms-python.black-formatter",
"github.copilot",
"github.copilot-chat"
],
"settings": {
"github.copilot.chat.enabled": true,
Expand Down
17 changes: 0 additions & 17 deletions .devcontainer/docker-compose.workspace.yml

This file was deleted.

15 changes: 13 additions & 2 deletions .devcontainer/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
version: '3.9'
version: "3.9"

networks:
local_network:
Expand All @@ -8,7 +8,18 @@ services:
redis:
image: redis:7.2.3-alpine
ports:
- '6379:6379'
- "6379:6379"
tty: true
networks:
- local_network

workspace:
image: python:3.8.18-bullseye
working_dir: /work
volumes:
- ../:/work
ports:
- "8443:8443"
tty: true
networks:
- local_network
64 changes: 39 additions & 25 deletions src/apple.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,16 @@
from cryptography import x509
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import rsa, ec, padding
from cryptography.hazmat.primitives.hashes import SHA256
from cryptography.hazmat.primitives.serialization import Encoding, PublicFormat
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.backends import default_backend
from cryptography.x509.oid import NameOID
from pyasn1.codec.der import decoder
from pyasn1.type import univ
from typing import List, Dict, Union
import cbor
import base64
import hashlib
import jsonify
import requests
import os
import json
from dotenv import load_dotenv
from constants import app_id, rp_id_hash_end, counter_start, counter_end, aaguid_start, aaguid_end, cred_id_start

Expand Down Expand Up @@ -118,21 +114,44 @@ def extract_attestation_object_extension(attestation_object, oid='1.2.840.113635
return decoded_data[0].asOctets().hex()


def is_valid_pem(pem):
try:
serialization.load_pem_public_key(
pem,
backend=default_backend()
)
return True
except ValueError:
return False

def create_hash_from_pub_key(cred_certificate):
certificate = x509.load_der_x509_certificate(cred_certificate)
certificate = x509.load_der_x509_certificate(cred_certificate, default_backend())

# Get the public key from the certificate
# Apple expect the public key to be in the X9.62 uncompressed
# point format.
public_key = certificate.public_key()

# Serialize the public key to bytes
public_key_bytes = public_key.public_bytes(Encoding.DER, PublicFormat.SubjectPublicKeyInfo)
# Check if the public key is an Elliptic Curve key
if not isinstance(public_key.curve, ec.EllipticCurve):
# raise ValueError('AppleInvalidPublicKey')
return None

# Compute the SHA-256 hash of the public key bytes
hash_object = hashlib.sha256(public_key_bytes)
hash_hex = hash_object.hexdigest()
# Retrieve the X and Y coordinates of the public key
x = public_key.public_numbers().x
y = public_key.public_numbers().y

# Convert the coordinates to byte strings and prepend with b'\x04'
public_key_bytes = b'\x04' + x.to_bytes(32, byteorder='big') + y.to_bytes(32, byteorder='big')

# Print the hash as a hexadecimal string
return hash_hex
# Create a SHA256 hash of the public key bytes
digest = hashes.Hash(hashes.SHA256())
digest.update(public_key_bytes)
public_key_sha256 = digest.finalize()

# Convert the SHA256 hash to a hexadecimal representation
public_key_sha256_hex = public_key_sha256.hex()

return public_key_sha256_hex

def create_app_id_hash():
app_id_bytes = app_id.encode('utf-8')
Expand Down Expand Up @@ -178,15 +197,10 @@ def verify_attestation_statement(attestation_object, nonce):
# 5. Create the SHA256 hash of the public key in credCert, and verify that it matches the
# key identifier from your app.
print('Apple Attestation step 5...')
public_hash = create_hash_from_pub_key(apple_attestation_object['attStmt']['x5c'][0])
bytes_value = base64.b64decode(attestation_object['key_id'])
hash_object = hashlib.sha256(bytes_value)
hash = hash_object.hexdigest()
if (hash != public_hash):
# this step is failing without caching nonce so commenting out for now
# return False
print('hash:', hash)
print('public_hash:', public_hash)
pub_key_hash = create_hash_from_pub_key(apple_attestation_object['attStmt']['x5c'][0])
key_id_b64 = base64.b64decode(attestation_object['key_id'])
if (key_id_b64.hex() != pub_key_hash):
return False

# 6. Compute the SHA256 hash of your app’s App ID, and verify that it’s the same as the
# authenticator data’s RP ID hash.
Expand Down Expand Up @@ -215,7 +229,7 @@ def verify_attestation_statement(attestation_object, nonce):
# 9. Verify that the authenticator data’s credentialId field is the same as the
# key identifier.
print('Apple Attestation step 9...')
key_identifier = bytes_value
key_identifier = base64.b64decode(attestation_object['key_id'])
cred_id_length = len(key_identifier)
cred_id_end = cred_id_start + cred_id_length
credential_id = apple_attestation_object['authData'][cred_id_start:cred_id_end]
Expand Down
5 changes: 5 additions & 0 deletions src/controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,11 @@ def decode_base64_to_json(s):

return json_obj

@server.route('/topic/ping/', methods=['POST'])
def ping():
print("Run POST /ping/")
return make_response('', 204)

@server.route('/topic/basicmessages/', methods=['POST'])
def basicmessages():
print("Run POST /topic/basicmessages/")
Expand Down

0 comments on commit 34be00a

Please sign in to comment.