Improve CI/CD: RSA sign+decrypt and EC decrypt

This commit is contained in:
Arnaud Fontaine 2022-01-10 20:36:59 +01:00
parent 0b43929a83
commit e84853414f
2 changed files with 323 additions and 69 deletions

View File

@ -23,10 +23,10 @@ jobs:
sudo apt-get update && sudo apt-get install --reinstall adoptopenjdk-8-hotspot; sudo apt-get update && sudo apt-get install --reinstall adoptopenjdk-8-hotspot;
sudo update-java-alternatives -s adoptopenjdk-8-hotspot-amd64; sudo update-java-alternatives -s adoptopenjdk-8-hotspot-amd64;
# 3) Get Javacard SDKs # 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 # 4) Compile our applet
cat build.xml | sed 's/<cap /<cap export="SmartPGPApplet" /' > /tmp/build.xml && mv /tmp/build.xml ./; # export jar for simulator cat build.xml | sed 's/<cap /<cap export="SmartPGPApplet" /' > /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 # 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 -; 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 # Patch random
@ -54,7 +54,7 @@ jobs:
opensc-tool -s 80b800001810d276000124010304AFAF000000000000050000020F0F00; opensc-tool -s 80b800001810d276000124010304AFAF000000000000050000020F0F00;
# Get card status # Get card status
python3 .github/workflows/card-status.py; python3 .github/workflows/card-status.py;
# Test ECDSA signatures # Main tests
python3 .github/workflows/test_SmartPGP.py; python3 .github/workflows/test_SmartPGP.py;
# Reset # Reset
python3 .github/workflows/reset.py; python3 .github/workflows/reset.py;

View File

@ -1,8 +1,6 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Testing SmartPGP applet up to a crash because of a memory leak
import os import os
from sys import path from sys import path
import subprocess import subprocess
@ -15,19 +13,28 @@ path.append(".")
import OpenPGPpy import OpenPGPpy
class BadTestResult(Exception):
pass
def sha256(data): def sha256(data):
return hashlib.sha256(data).digest() return hashlib.sha256(data).digest()
def pubkey_to_der(pubkey): def rsa_2048_pubkey_to_der(pubkey):
# Add ASN1 DER header (EC parameters) # Add ASN1 DER header
# ECP 256 r1 header # RSA 2048
header_hex = "3059301306072A8648CE3D020106082A8648CE3D030107034200" header_modulus = "30820122300d06092a864886f70d01010105000382010f003082010a0282010100"
return bytes.fromhex(header_hex + pubkey.hex()) 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): def check_rsa_2048_signature(message, signature, pubkeyd):
pubkey = pubkey_to_der(pubkeyd) pubkey = rsa_2048_pubkey_to_der(pubkeyd)
fpk = tempfile.NamedTemporaryFile(delete=False) fpk = tempfile.NamedTemporaryFile(delete=False)
fpk.write(pubkey) fpk.write(pubkey)
fpk.close() fpk.close()
@ -40,79 +47,326 @@ def check_signature(msg, signature, pubkeyd):
sigOK = False sigOK = False
try: try:
subprocess.run( 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 sigOK = True
except Exception: except Exception:
print("Error in signature verification") print("[-] ERROR in signature verification:")
print(">>> Requires openssl in path to check signatures") print(">>> openssl is required in path to check signatures")
sigOK = False sigOK = False
os.remove(fpk.name) os.remove(fpk.name)
os.remove(fsig.name) os.remove(fsig.name)
return sigOK 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: try:
# instanciated with (True) to enable debug mode subprocess.run(
mydevice = OpenPGPpy.OpenPGPcard(True) encrypt_cmd, input=message, shell=True, check=True, stdout=subprocess.PIPE
except OpenPGPpy.ConnectionException as exc: )
print(exc) except Exception:
return print("[-] ERROR in encrpytion")
print("OpenPGP device detected") print(">>> openssl is required in path to encrypt message")
pubkey_card_all = None 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: try:
pubkey_card_all = mydevice.get_public_key("B600") subprocess.run(
except OpenPGPpy.PGPCardException as exc: verify_cmd, input=message, shell=True, check=True, stdout=subprocess.PIPE
# SW = 0x6581 or 0x6A88 ? )
if exc.sw_code != 0x6581 and exc.sw_code != 0x6A88: sigOK = True
raise except Exception:
# SIGn key was not created, continue to setup this key print("[-] ERROR in signature verification")
if pubkey_card_all is None: print(">>> openssl is required in path to check signatures")
print("Setup the new device") sigOK = False
PIN3 = "12345678" #getpass.getpass("Enter PIN3 (PUK) : ") os.remove(fpk.name)
try: os.remove(fsig.name)
mydevice.verify_pin(3, PIN3) return sigOK
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')
PIN1 = "123456" #getpass.getpass("Enter PIN1 : ")
# Make 200 ECDSA def encrypt_ec_prime256v1(pubkeyd):
print(f"\nPublicKey for signature : 0x{pubkey_card.hex()}") 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") message = "Hello SmartPGP! Take that message.".encode("ascii")
hash = sha256(message) hash = sha256(message)
for _ in range(200):
mydevice.verify_pin(1, PIN1) for i in range(repeat):
sig_card = mydevice.sign_ec_der(hash) print(f"[+] Test #{i + 1}")
print(f"Signature : 0x{sig_card.hex()}")
if check_signature(message, sig_card, pubkey_card): # Verify PIN1, required for signature
print("OK") 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: else:
print("Can't check signature") print("[-] BAD deciphering")
return 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__": if __name__ == "__main__":
try: 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: except OpenPGPpy.PGPCardException as exc:
if exc.sw_code == 0x6F00: if exc.sw_code != 0x9000:
print("Crash, game over.") print(f"[-] FAILED with: 0x{exc.sw_code:02x}")
print("SFYL !") exit(2)
print(exc) except BadTestResult:
print(f"[-] FAILED test")
exit(3)