From e84853414f7d12145641c3b1b20e1de6c4170c9d Mon Sep 17 00:00:00 2001 From: Arnaud Fontaine Date: Mon, 10 Jan 2022 20:36:59 +0100 Subject: [PATCH] Improve CI/CD: RSA sign+decrypt and EC decrypt --- .github/workflows/main.yml | 10 +- .github/workflows/test_SmartPGP.py | 382 ++++++++++++++++++++++++----- 2 files changed, 323 insertions(+), 69 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 0ebc5cc..78b60d5 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -23,10 +23,10 @@ jobs: sudo apt-get update && sudo apt-get install --reinstall adoptopenjdk-8-hotspot; sudo update-java-alternatives -s adoptopenjdk-8-hotspot-amd64; # 3) Get Javacard SDKs - git clone https://github.com/martinpaljak/oracle_javacard_sdks && mv oracle_javacard_sdks/jc305u3_kit/ /tmp/ && rm -rf oracle_javacard_sdks; + git clone https://github.com/martinpaljak/oracle_javacard_sdks && mv oracle_javacard_sdks/jc303_kit/ /tmp/ && mv oracle_javacard_sdks/jc305u3_kit/ /tmp/ &&rm -rf oracle_javacard_sdks; # 4) Compile our applet cat build.xml | sed 's/ /tmp/build.xml && mv /tmp/build.xml ./; # export jar for simulator - JC_HOME=/tmp/jc305u3_kit/ ant; + JC_HOME=/tmp/jc303_kit/ ant; # 5) Clone jcardsim repository, compile and install git clone https://github.com/licel/jcardsim && cd jcardsim && git fetch origin pull/155/head:pr155 && git config --global user.name "John Doe" && git merge pr155 -m "AA" && cd -; # Patch random @@ -44,7 +44,7 @@ jobs: # Wait a bit for insertion sleep 2; echo "========= PCSCD log" && cat /tmp/log_pcsc; - echo "========= jcard sim log" && cat /tmp/log_jcardsim; + echo "========= jcard sim log" && cat /tmp/log_jcardsim; # 10) Test our applet # Execute tests sudo apt-get install -y python3-setuptools python3-pyscard python-setuptools python-pyscard python-pyasn1; @@ -54,12 +54,12 @@ jobs: opensc-tool -s 80b800001810d276000124010304AFAF000000000000050000020F0F00; # Get card status python3 .github/workflows/card-status.py; - # Test ECDSA signatures + # Main tests python3 .github/workflows/test_SmartPGP.py; # Reset python3 .github/workflows/reset.py; # Test crypto switch - python2 bin/example-set-mixed-crypto.py; + python2 bin/example-set-mixed-crypto.py; # Test AES echo -n "AAAAAAAAAAAAAAAA" > /tmp/aes_key.bin; python2 bin/smartpgp-cli put-aes-key -i /tmp/aes_key.bin; diff --git a/.github/workflows/test_SmartPGP.py b/.github/workflows/test_SmartPGP.py index ddbdb85..125a54b 100644 --- a/.github/workflows/test_SmartPGP.py +++ b/.github/workflows/test_SmartPGP.py @@ -1,8 +1,6 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -# Testing SmartPGP applet up to a crash because of a memory leak - import os from sys import path import subprocess @@ -15,19 +13,28 @@ path.append(".") import OpenPGPpy +class BadTestResult(Exception): + pass + + def sha256(data): return hashlib.sha256(data).digest() -def pubkey_to_der(pubkey): - # Add ASN1 DER header (EC parameters) - # ECP 256 r1 header - header_hex = "3059301306072A8648CE3D020106082A8648CE3D030107034200" - return bytes.fromhex(header_hex + pubkey.hex()) +def rsa_2048_pubkey_to_der(pubkey): + # Add ASN1 DER header + # RSA 2048 + header_modulus = "30820122300d06092a864886f70d01010105000382010f003082010a0282010100" + header_exponent = "0203" + + modulus = pubkey[9:265].hex() + exponent = pubkey[267:].hex() + + return bytes.fromhex(header_modulus + modulus + header_exponent + exponent) -def check_signature(msg, signature, pubkeyd): - pubkey = pubkey_to_der(pubkeyd) +def check_rsa_2048_signature(message, signature, pubkeyd): + pubkey = rsa_2048_pubkey_to_der(pubkeyd) fpk = tempfile.NamedTemporaryFile(delete=False) fpk.write(pubkey) fpk.close() @@ -40,79 +47,326 @@ def check_signature(msg, signature, pubkeyd): sigOK = False try: subprocess.run( - verify_cmd, input=msg, shell=True, check=True, stdout=subprocess.PIPE + verify_cmd, input=message, shell=True, check=True, stdout=subprocess.PIPE ) sigOK = True except Exception: - print("Error in signature verification") - print(">>> Requires openssl in path to check signatures") + print("[-] ERROR in signature verification:") + print(">>> openssl is required in path to check signatures") sigOK = False os.remove(fpk.name) os.remove(fsig.name) return sigOK -def main(): +def encrypt_rsa_2048(message, pubkeyd): + pubkey = rsa_2048_pubkey_to_der(pubkeyd) + fpk = tempfile.NamedTemporaryFile(delete=False) + fpk.write(pubkey) + fpk.close() + fenc = tempfile.NamedTemporaryFile(delete=False) + fenc.close() + encrypt_cmd = ( + f"openssl rsautl -keyform DER -pubin -inkey {fpk.name} -pkcs -encrypt -out {fenc.name}" + ) try: - # instanciated with (True) to enable debug mode - mydevice = OpenPGPpy.OpenPGPcard(True) - except OpenPGPpy.ConnectionException as exc: - print(exc) - return - print("OpenPGP device detected") - pubkey_card_all = None + subprocess.run( + encrypt_cmd, input=message, shell=True, check=True, stdout=subprocess.PIPE + ) + except Exception: + print("[-] ERROR in encrpytion") + print(">>> openssl is required in path to encrypt message") + raise + os.remove(fpk.name) + fres = open(fenc.name, 'rb') + res = fres.read() + fres.close() + os.remove(fenc.name) + return bytes.fromhex("00") + res + + +def test_rsa_2048_signature(token, PIN1, PIN3, repeat): + print(f"[+] == Test RSA 2048 signature ({repeat} signatures) ==") + + # Verify PIN3, required for the next command + print("[+] Verify PIN3") + token.verify_pin(3, PIN3) + + # Setup RSA 2048 for SIG key + print("[+] Set SIG key to RSA 2048") + token.put_data("00C1", "010800001103") + + # Generate key for SIG key + print("[+] Generate SIG key") + pubkey_card = token.gen_key("B600") + + # Digest message and sign + message = "Hello SmartPGP! Take that message.".encode("ascii") + header_sha256 = "3031300D060960864801650304020105000420"; + hash = bytes.fromhex(header_sha256) + sha256(message) + + for i in range(repeat): + print(f"[+] Test #{i + 1}") + + # Verify PIN1, required for signature + token.verify_pin(1, PIN1) + + print("[+] Sign SHA256 hash") + sig_card = token.sign(hash) + + print("[+] Verify signature") + if not check_rsa_2048_signature(message, sig_card, pubkey_card): + print("[-] BAD signature") + raise BadTestResult + + +def test_rsa_2048_decrypt(token, PIN1, PIN3, repeat): + print(f"[+] == Test RSA 2048 decrypt ({repeat} deciphers) ==") + + # Verify PIN3, required for the next command + print("[+] Verify PIN3") + token.verify_pin(3, PIN3) + + # Setup RSA 2048 for DEC key + print("[+] Set DEC key to RSA 2048") + token.put_data("00C2", "010800001103") + + # Generate key for DEC key + print("[+] Generate DEC key") + pubkey_card = token.gen_key("B800") + + # Verify PIN2, required for decrypt + token.verify_pin(2, PIN1) + + # Cipher message + message = "Hello SmartPGP! Take that message.".encode("ascii") + + print("[+] Prepare encrypted message") + encrypted = encrypt_rsa_2048(message, pubkey_card) + + for i in range(repeat): + print(f"[+] Test #{i + 1}") + + print("[+] Decipher message") + decrypted = token.decipher(encrypted) + + print("[+] Verify message") + if message == decrypted: + print("[+] Good deciphering") + else: + print("[-] BAD deciphering") + raise BadTestResult + + +def test_rsa_2048(token, PIN1, PIN3, repeat): + print("[+] === Test RSA 2048 ===") + + test_rsa_2048_signature(token, PIN1, PIN3, repeat) + + test_rsa_2048_decrypt(token, PIN1, PIN3, repeat) + + +def ec_prime256v1_pubkey_to_der(pubkey): + # Add ASN1 DER header (EC parameters) + # ECP 256 r1 header + header_hex = "3059301306072A8648CE3D020106082A8648CE3D030107034200" + return bytes.fromhex(header_hex + pubkey.hex()) + + +def check_ec_prime256v1_signature(message, signature, pubkeyd): + pubkey = ec_prime256v1_pubkey_to_der(pubkeyd) + fpk = tempfile.NamedTemporaryFile(delete=False) + fpk.write(pubkey) + fpk.close() + fsig = tempfile.NamedTemporaryFile(delete=False) + fsig.write(signature) + fsig.close() + verify_cmd = ( + f"openssl dgst -sha256 -keyform DER -verify {fpk.name} -signature {fsig.name}" + ) + sigOK = False try: - pubkey_card_all = mydevice.get_public_key("B600") - except OpenPGPpy.PGPCardException as exc: - # SW = 0x6581 or 0x6A88 ? - if exc.sw_code != 0x6581 and exc.sw_code != 0x6A88: - raise - # SIGn key was not created, continue to setup this key - if pubkey_card_all is None: - print("Setup the new device") - PIN3 = "12345678" #getpass.getpass("Enter PIN3 (PUK) : ") - try: - mydevice.verify_pin(3, PIN3) - except OpenPGPpy.PGPCardException as exc: - if exc.sw_code == 0x6982 or exc.sw_code == 0x6A80: - print("Error: Wrong PUK") - return - # Setup EC256r1 for SIG key - try: - mydevice.put_data("00C1", "132A8648CE3D030107") - except OpenPGPpy.PGPCardException as exc: - if exc.sw_code == 0x6A80: - raise Exception( - "This device is not compatible with ECDSA 256r1." - ) from exc - raise - # Generate key for sign - pubkey_card_all = mydevice.gen_key("B600") - pubkey_card = pubkey_card_all[-65:] - print('Device "SIG" public key read') + subprocess.run( + verify_cmd, input=message, shell=True, check=True, stdout=subprocess.PIPE + ) + sigOK = True + except Exception: + print("[-] ERROR in signature verification") + print(">>> openssl is required in path to check signatures") + sigOK = False + os.remove(fpk.name) + os.remove(fsig.name) + return sigOK - PIN1 = "123456" #getpass.getpass("Enter PIN1 : ") - # Make 200 ECDSA - print(f"\nPublicKey for signature : 0x{pubkey_card.hex()}") +def encrypt_ec_prime256v1(pubkeyd): + pubkey = ec_prime256v1_pubkey_to_der(pubkeyd) + fpub = tempfile.NamedTemporaryFile(delete=False) + fpub.write(pubkey) + fpub.close() + + fpriv2 = tempfile.NamedTemporaryFile(delete=False) + fpriv2.close() + generate_private_cmd = ( + f"openssl genpkey -outform DER -out {fpriv2.name} -algorithm EC -pkeyopt ec_paramgen_curve:prime256v1 -pkeyopt ec_param_enc:named_curve" + ) + try: + subprocess.run( + generate_private_cmd, shell=True, check=True, stdout=subprocess.PIPE + ) + except Exception: + print("[-] ERROR in encrpytion") + print(">>> openssl is required in path to encrypt message") + raise + + fpub2 = tempfile.NamedTemporaryFile(delete=False) + fpub2.close() + generate_pub_cmd = ( + f"openssl pkey -pubout -inform DER -in {fpriv2.name} -outform DER -out {fpub2.name}" + ) + try: + subprocess.run( + generate_pub_cmd, shell=True, check=True, stdout=subprocess.PIPE + ) + except Exception: + print("[-] ERROR in encrpytion") + print(">>> openssl is required in path to encrypt message") + raise + + fenc = tempfile.NamedTemporaryFile(delete=False) + fenc.close() + generate_ecdh_cmd = ( + f"openssl pkeyutl -derive -out {fenc.name} -keyform DER -inkey {fpriv2.name} -peerform DER -peerkey {fpub.name}" + ) + try: + subprocess.run( + generate_ecdh_cmd, shell=True, check=True, stdout=subprocess.PIPE + ) + except Exception: + print("[-] ERROR in encrpytion") + print(">>> openssl is required in path to encrypt message") + raise + + f = open(fpub2.name, 'rb') + pub2 = f.read() + pub2 = pub2[26:] + f.close() + + f = open(fenc.name, 'rb') + enc = f.read() + f.close() + + os.remove(fpub.name) + os.remove(fpriv2.name) + os.remove(fpub2.name) + os.remove(fenc.name) + + return (bytes.fromhex("A646") + bytes.fromhex("7F4943") + bytes.fromhex("8641") + pub2, enc) + + +def test_ec_prime256v1_signature(token, PIN1, PIN3, repeat): + print(f"[+] == Test EC prime256v1 signature ({repeat} signatures) ==") + + # Verify PIN3, required for the next command + print("[+] Verify PIN3") + token.verify_pin(3, PIN3) + + # Setup EC prime256v1 for SIG key + print("[+] Set SIG key to EC prime256v1") + token.put_data("00C1", "132A8648CE3D030107") + + # Generate key for SIG key + print("[+] Generate SIG key") + pubkey_card = token.gen_key("B600") + pubkey_card = pubkey_card[-65:] + + # Digest message and sign message = "Hello SmartPGP! Take that message.".encode("ascii") hash = sha256(message) - for _ in range(200): - mydevice.verify_pin(1, PIN1) - sig_card = mydevice.sign_ec_der(hash) - print(f"Signature : 0x{sig_card.hex()}") - if check_signature(message, sig_card, pubkey_card): - print("OK") + + for i in range(repeat): + print(f"[+] Test #{i + 1}") + + # Verify PIN1, required for signature + token.verify_pin(1, PIN1) + + print("[+] Sign SHA256 hash") + sig_card = token.sign_ec_der(hash) + + print("[+] Verify signature") + if not check_ec_prime256v1_signature(message, sig_card, pubkey_card): + print("[-] BAD signature") + raise BadTestResult + + +def test_ec_prime256v1_decrypt(token, PIN1, PIN3, repeat): + print(f"[+] == Test EC prime256v1 decrypt ({repeat} deciphers) ==") + + # Verify PIN3, required for the next command + print("[+] Verify PIN3") + token.verify_pin(3, PIN3) + + # Setup EC prime256v1 for DEC key + print("[+] Set DEC key to EC prime256v1") + token.put_data("00C2", "122A8648CE3D030107") + + # Generate key for DEC key + print("[+] Generate DEC key") + pubkey_card = token.gen_key("B800") + pubkey_card = pubkey_card[-65:] + + # Verify PIN2, required for decrypt + token.verify_pin(2, PIN1) + + print("[+] Prepare ECDH") + (encrypted, secret) = encrypt_ec_prime256v1(pubkey_card) + + for i in range(repeat): + print(f"[+] Test #{i + 1}") + + print("[+] Decipher (compute ECDH secret)") + decrypted = token.decipher(encrypted) + + print("[+] Verify message") + if secret == decrypted: + print("[+] Good deciphering") else: - print("Can't check signature") - return + print("[-] BAD deciphering") + raise BadTestResult + + +def test_ec_prime256v1(token, PIN1, PIN3, repeat): + print("[+] === Test EC prime256v1 ===") + + test_ec_prime256v1_signature(token, PIN1, PIN3, repeat) + + test_ec_prime256v1_decrypt(token, PIN1, PIN3, repeat) + + +def main(rsa, ec, repeat): + # True/False == with/without debug + token = OpenPGPpy.OpenPGPcard(False) + + PIN1 = "123456" + PIN3 = "12345678" + + if rsa: + test_rsa_2048(token, PIN1, PIN3, repeat) + + if ec: + test_ec_prime256v1(token, PIN1, PIN3, repeat) if __name__ == "__main__": try: - main() + main(rsa=True, ec=False, repeat=200) + except OpenPGPpy.openpgp_card.ConnectionException: + print(f"[-] FAILED to find OpenPGP token") + exit(1) except OpenPGPpy.PGPCardException as exc: - if exc.sw_code == 0x6F00: - print("Crash, game over.") - print("SFYL !") - print(exc) + if exc.sw_code != 0x9000: + print(f"[-] FAILED with: 0x{exc.sw_code:02x}") + exit(2) + except BadTestResult: + print(f"[-] FAILED test") + exit(3)