diff --git a/.github/aosp_ec.pem b/.github/aosp_ec.pem new file mode 100644 index 0000000..02fdfd1 --- /dev/null +++ b/.github/aosp_ec.pem @@ -0,0 +1,4 @@ +-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE7l1ex+HA220Dpn7mthvsTWpdamgu +D/9/SQ59dx9EIm29sa/6FsvHrcV30lacqrewLVQBXT5DKyqO107sSHVBpA== +-----END PUBLIC KEY----- diff --git a/.github/aosp_rsa.pem b/.github/aosp_rsa.pem new file mode 100644 index 0000000..89cd099 --- /dev/null +++ b/.github/aosp_rsa.pem @@ -0,0 +1,6 @@ +-----BEGIN PUBLIC KEY----- +MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCia63rbi5EYe/VDoLmt5TRdSMf +d5tjkWP/96r/C3JHTsAsQ+wzfNes7UA+jCigZtX3hwszl94OuE4TQKuvpSe/lWmg +MdsGUmX4RFlXYfC78hdLt0GAZMAoDo9Sd47b0ke2RekZyOmLw9vCkT/X11DEHTVm ++Vfkl5YLCazOkjWFmwIDAQAB +-----END PUBLIC KEY----- diff --git a/.github/check.py b/.github/check.py new file mode 100644 index 0000000..c6247ae --- /dev/null +++ b/.github/check.py @@ -0,0 +1,140 @@ +import csv +from datetime import UTC, datetime +from pathlib import Path + +import requests +from cryptography import x509 +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives import hashes, serialization +from cryptography.hazmat.primitives.asymmetric import ec, padding +from defusedxml.ElementTree import parse + + +def load_public_key_from_file(file_path): + with open(file_path, "rb") as key_file: + public_key = serialization.load_pem_public_key( + key_file.read(), backend=default_backend() + ) + return public_key + + +def compare_keys(public_key1, public_key2): + return public_key1.public_bytes( + encoding=serialization.Encoding.PEM, + format=serialization.PublicFormat.SubjectPublicKeyInfo, + ) == public_key2.public_bytes( + encoding=serialization.Encoding.PEM, + format=serialization.PublicFormat.SubjectPublicKeyInfo, + ) + + +revoked_keybox_list = requests.get( + "https://android.googleapis.com/attestation/status", + headers={ + "Cache-Control": "max-age=0, no-cache, no-store, must-revalidate", + "Pragma": "no-cache", + "Expires": "0", + }, +).json()["entries"] + +google_public_key = load_public_key_from_file(".github/google.pem") +aosp_ec_public_key = load_public_key_from_file(".github/aosp_ec.pem") +aosp_rsa_public_key = load_public_key_from_file(".github/aosp_rsa.pem") +knox_public_key = load_public_key_from_file(".github/knox.pem") + +with open("status.csv", "w") as csvfile: + fieldnames = [ + "File", + "Serial number", + "Subject", + "Certificate within validity period", + "Valid keychain", + "note", + "Serial number not found in Google's revoked keybox list", + ] + writer = csv.DictWriter(csvfile, fieldnames=fieldnames) + writer.writeheader() + for kb in Path(".").glob("*.xml"): + output = [kb.name] + + root = parse(kb).getroot() + pem_number = int(root.find(".//NumberOfCertificates").text.strip()) + pem_certificates = [ + cert.text.strip() + for cert in root.findall('.//Certificate[@format="pem"]')[:pem_number] + ] + certificate = x509.load_pem_x509_certificate( + pem_certificates[0].encode(), default_backend() + ) + serial_number = hex(certificate.serial_number)[2:] + output.append(serial_number) + + subject = "" + for rdn in certificate.subject: + subject += f"{rdn.oid._name}={rdn.value} | " + subject = subject[:-3] + output.append(subject) + + not_valid_before = certificate.not_valid_before_utc + not_valid_after = certificate.not_valid_after_utc + current_time = datetime.now(UTC) + is_valid = not_valid_before <= current_time <= not_valid_after + output.append("✅" if is_valid else "❌") + + flag = True + for i in range(pem_number - 1): + son_certificate = x509.load_pem_x509_certificate( + pem_certificates[i].encode(), default_backend() + ) + father_certificate = x509.load_pem_x509_certificate( + pem_certificates[i + 1].encode(), default_backend() + ) + + if son_certificate.issuer != father_certificate.subject: + flag = False + break + signature = son_certificate.signature + signature_algorithm = son_certificate.signature_algorithm_oid._name + tbs_certificate = son_certificate.tbs_certificate_bytes + public_key = father_certificate.public_key() + try: + match signature_algorithm: + case "sha256WithRSAEncryption" | "ecdsa-with-SHA256": + hash_algorithm = hashes.SHA256() + case "sha1WithRSAEncryption" | "ecdsa-with-SHA1": + hash_algorithm = hashes.SHA1() + case "sha384WithRSAEncryption" | "ecdsa-with-SHA384": + hash_algorithm = hashes.SHA384() + case "sha512WithRSAEncryption" | "ecdsa-with-SHA512": + hash_algorithm = hashes.SHA512() + + if signature_algorithm.endswith("WithRSAEncryption"): + padding_algorithm = padding.PKCS1v15() + public_key.verify( + signature, tbs_certificate, padding_algorithm, hash_algorithm + ) + else: + padding_algorithm = ec.ECDSA(hash_algorithm) + public_key.verify(signature, tbs_certificate, padding_algorithm) + except Exception as e: + flag = False + break + output.append("✅" if flag else "❌") + + root_certificate = x509.load_pem_x509_certificate( + pem_certificates[-1].encode(), default_backend() + ) + root_public_key = root_certificate.public_key() + if compare_keys(root_public_key, google_public_key): + output.append("✅ Google hardware attestation root certificate") + elif compare_keys(root_public_key, aosp_ec_public_key): + output.append("🟡 AOSP software attestation root certificate (EC)") + elif compare_keys(root_public_key, aosp_rsa_public_key): + output.append("🟡 AOSP software attestation root certificate (RSA)") + elif compare_keys(root_public_key, knox_public_key): + output.append("✅ Samsung Knox attestation root certificate") + else: + output.append("❌ Unknown root certificate") + + output.append("✅" if not revoked_keybox_list.get(serial_number, None) else "❌") + writer.writerow(dict(zip(fieldnames, output))) diff --git a/.github/google.pem b/.github/google.pem new file mode 100644 index 0000000..fb44493 --- /dev/null +++ b/.github/google.pem @@ -0,0 +1,14 @@ +-----BEGIN PUBLIC KEY----- +MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAr7bHgiuxpwHsK7Qui8xU +FmOr75gvMsd/dTEDDJdSSxtf6An7xyqpRR90PL2abxM1dEqlXnf2tqw1Ne4Xwl5j +lRfdnJLmN0pTy/4lj4/7tv0Sk3iiKkypnEUtR6WfMgH0QZfKHM1+di+y9TFRtv6y +//0rb+T+W8a9nsNL/ggjnar86461qO0rOs2cXjp3kOG1FEJ5MVmFmBGtnrKpa73X +pXyTqRxB/M0n1n/W9nGqC4FSYa04T6N5RIZGBN2z2MT5IKGbFlbC8UrW0DxW7AYI +mQQcHtGl/m00QLVWutHQoVJYnFPlXTcHYvASLu+RhhsbDmxMgJJ0mcDpvsC4PjvB ++TxywElgS70vE0XmLD+OJtvsBslHZvPBKCOdT0MS+tgSOIfga+z1Z1g7+DVagf7q +uvmag8jfPioyKvxnK/EgsTUVi2ghzq8wm27ud/mIM7AY2qEORR8Go3TVB4HzWQgp +Zrt3i5MIlCaY504LzSRiigHCzAPlHws+W0rB5N+er5/2pJKnfBSDiCiFAVtCLOZ7 +gLiMm0jhO2B6tUXHI/+MRPjy02i59lINMRRev56GKtcd9qO/0kUJWdZTdA2XoS82 +ixPvZtXQpUpuL12ab+9EaDK8Z4RHJYYfCT3Q5vNAXaiWQ+8PTWm2QgBR/bkwSWc+ +NpUFgNPN9PvQi8WEg5UmAGMCAwEAAQ== +-----END PUBLIC KEY----- diff --git a/.github/knox.pem b/.github/knox.pem new file mode 100644 index 0000000..dbeadd9 --- /dev/null +++ b/.github/knox.pem @@ -0,0 +1,6 @@ +-----BEGIN PUBLIC KEY----- +MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQBhbGuLrpql5I2WJmrE5kEVZOo+dgA +46mKrVJf/sgzfzs2u7M9c1Y9ZkCEiiYkhTFE9vPbasmUfXybwgZ2EM30A1ABPd12 +4n3JbEDfsB/wnMH1AcgsJyJFPbETZiy42Fhwi+2BCA5bcHe7SrdkRIYSsdBRaKBo +ZsapxB0gAOs0jSPRX5M= +-----END PUBLIC KEY----- diff --git a/.github/requirements.txt b/.github/requirements.txt new file mode 100644 index 0000000..4005d87 --- /dev/null +++ b/.github/requirements.txt @@ -0,0 +1,3 @@ +cryptography +defusedxml +requests diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml new file mode 100644 index 0000000..28b237d --- /dev/null +++ b/.github/workflows/check.yml @@ -0,0 +1,32 @@ +name: CI + +on: + push: + branches: + - main + - Check-CI + paths-ignore: + - "status.csv" + pull_request: + branches: + - main + - Check-CI + schedule: + - cron: "0 * * * *" + workflow_dispatch: + +jobs: + check: + runs-on: ubuntu-latest + + permissions: + contents: write + + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v3 + with: + python-version: "3.12" + - run: pip install -r .github/requirements.txt + - run: python3 .github/check.py + - uses: stefanzweifel/git-auto-commit-action@v5 diff --git a/status.csv b/status.csv new file mode 100644 index 0000000..578a233 --- /dev/null +++ b/status.csv @@ -0,0 +1,27 @@ +File,Serial number,Subject,Certificate within validity period,Valid keychain,note,Serial number not found in Google's revoked keybox list +vVc41BKd.xml,3644717209372610241,serialNumber=38f26f1125a20e5b,✅,✅,✅ Google hardware attestation root certificate,✅ +TaSxbdih.xml,2441400622645777626,serialNumber=4d0008edd4523f4a,✅,✅,✅ Google hardware attestation root certificate,✅ +NDtfvGvC.xml,4393895627937872466,serialNumber=b71b0a2454c69577 | title=TEE,✅,✅,✅ Google hardware attestation root certificate,✅ +8qnsDAYP.xml,511301ba8fd1c44c317f78dc1d7d2cd1,title=TEE | serialNumber=bb1bea2a057e922d7725f26eb1c3b1a5,✅,✅,✅ Google hardware attestation root certificate,✅ +2alL1ky.xml,2b0a09a69c59b482ddb8a21786fdd439,title=TEE | serialNumber=0c8684c66d5c3f63c2d2494b72b82d50,✅,✅,✅ Google hardware attestation root certificate,✅ +e13lwsP4.xml,b48dbbd75efb21cd0a48497f8176d0,title=TEE | serialNumber=d85fc4b748795139f5f3be099c39c139,✅,✅,✅ Google hardware attestation root certificate,✅ +n7zXDUuf.xml,17342033108574180851,serialNumber=0bb56886b018b336,✅,✅,✅ Google hardware attestation root certificate,✅ +k1Hf0ySh.xml,11004354893796124535,serialNumber=49ed31cf1d5b73fa | title=TEE,✅,✅,✅ Google hardware attestation root certificate,✅ +vR7kjdRn.xml,6843403734403185074,serialNumber=a1cdc6b69229c69f,✅,✅,✅ Google hardware attestation root certificate,✅ +ewxySgBV.xml,b46f082b679a78cd1ddb33727d62fb22,title=TEE | serialNumber=446093695543b5ccc6f96849ab486ff7,✅,✅,✅ Google hardware attestation root certificate,✅ +M6DpviD5.xml,14138857243207454896,serialNumber=094ca2793d91c305 | title=TEE,✅,✅,✅ Google hardware attestation root certificate,✅ +KPZ1iysJ.xml,208f5008c39038a7105aa3d2ccfa8f76,title=TEE | serialNumber=026b244e023abdc307c080b2a070fcea,✅,✅,✅ Google hardware attestation root certificate,✅ +rQJrW8Su.xml,6843403734403185074,serialNumber=a1cdc6b69229c69f,✅,✅,✅ Google hardware attestation root certificate,✅ +NR65kJaP.xml,24f7bff38ae38534a66dd409a15018d3,title=TEE | serialNumber=59a927ca4c978eedd100e8a0b2189420,✅,✅,✅ Google hardware attestation root certificate,✅ +zj9DixHk.xml,263c56c9661db1056077c41533e9273a,title=TEE | serialNumber=4464fc9748f7fa6f1ca1b244ffe8531c,✅,✅,✅ Google hardware attestation root certificate,✅ +q0f4iYhS.xml,4648453603379027456,serialNumber=18d9fa8a1e02a531,✅,✅,✅ Google hardware attestation root certificate,✅ +z9H65wAq.xml,6933392829777369721,serialNumber=2cb3e9572d82a93b,✅,✅,✅ Google hardware attestation root certificate,✅ +hbzFmkYe.xml,cf408d495722b0ac413071aff33e5827,title=TEE | serialNumber=9ccc7cb13f0bddc940736bcda24698c6,✅,✅,✅ Google hardware attestation root certificate,✅ +yId912ap.xml,13063048137843225758,serialNumber=d0d0c8d8f4a0f97d | title=TEE,✅,✅,✅ Google hardware attestation root certificate,✅ +vFLyHUSf.xml,9714381576908752953,serialNumber=75f2bfacd5d24ade,✅,✅,✅ Google hardware attestation root certificate,✅ +mXZE5tB8.xml,9703144777339823906,serialNumber=b410301e51e47cfb,✅,✅,✅ Google hardware attestation root certificate,✅ +kre13p41l.xml,fc609574570c3e0a1d347e02a2ad6d4d,title=TEE | serialNumber=3b6c77873cdf80616fc7a86bb4c2944f,✅,✅,✅ Google hardware attestation root certificate,❌ +3YxC37x4.xml,5871646753572800414,serialNumber=e23a5cdb6e2ff2e9 | title=TEE,✅,✅,✅ Google hardware attestation root certificate,✅ +cXKFcxDb.xml,12668387747158417143,serialNumber=5d0fa1fc96023121 | title=TEE,✅,✅,✅ Google hardware attestation root certificate,✅ +MPJWMwUE.xml,9460229422403884383,serialNumber=2fc78bcdd321b854,✅,✅,✅ Google hardware attestation root certificate,✅ +DL1neMtH.xml,10182630751496984110,serialNumber=35e0d466d6467970 | title=TEE,✅,✅,✅ Google hardware attestation root certificate,✅