diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 1d0a25a..b26397d 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -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": [ @@ -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, diff --git a/.devcontainer/docker-compose.workspace.yml b/.devcontainer/docker-compose.workspace.yml deleted file mode 100644 index 49accaa..0000000 --- a/.devcontainer/docker-compose.workspace.yml +++ /dev/null @@ -1,17 +0,0 @@ -version: '3.9' - -networks: - local_network: - name: local_network - -services: - workspace: - image: python:3-alpine3.19 - working_dir: /work - volumes: - - ../:/work - ports: - - '8443:8443' - tty: true - networks: - - local_network diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml index 593cb12..a55952d 100644 --- a/.devcontainer/docker-compose.yml +++ b/.devcontainer/docker-compose.yml @@ -1,4 +1,4 @@ -version: '3.9' +version: "3.9" networks: local_network: @@ -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 diff --git a/src/apple.py b/src/apple.py index 856eeac..a8a84ed 100644 --- a/src/apple.py +++ b/src/apple.py @@ -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 @@ -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') @@ -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. @@ -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] diff --git a/src/controller.py b/src/controller.py index 65ceeec..d52baf3 100644 --- a/src/controller.py +++ b/src/controller.py @@ -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/")