Improve CI/CD: RSA sign+decrypt and EC decrypt
This commit is contained in:
parent
0b43929a83
commit
e84853414f
6
.github/workflows/main.yml
vendored
6
.github/workflows/main.yml
vendored
@ -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/<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
|
||||
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
|
||||
@ -54,7 +54,7 @@ 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;
|
||||
|
376
.github/workflows/test_SmartPGP.py
vendored
376
.github/workflows/test_SmartPGP.py
vendored
@ -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
|
||||
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:
|
||||
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
|
||||
# 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) : ")
|
||||
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:
|
||||
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
|
||||
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
|
||||
|
||||
|
||||
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:
|
||||
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
|
||||
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
|
||||
# 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 : ")
|
||||
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
|
||||
|
||||
# Make 200 ECDSA
|
||||
print(f"\nPublicKey for signature : 0x{pubkey_card.hex()}")
|
||||
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)
|
||||
|
Loading…
Reference in New Issue
Block a user