From 0b43929a830133e0d3c568f35f387feade8a557a Mon Sep 17 00:00:00 2001 From: Ryad Benadjila Date: Thu, 25 Mar 2021 10:38:27 +0100 Subject: [PATCH] Add CI/CD tests using Github Actions. The tests compile and run the applet in the jcardsim Javacard simulator. ECDSA signature tests are performed using OpenPGPpy, other tests such as AES are performed using the local bin/smartpgp-cli script. TODO: add tests for RSA, asymmetric encryption, pin modifications, secure messaging (if well supported by jcardsim), etc. --- .github/workflows/card-status.py | 56 +++++++++ .../jcardsim_patches/jcardsim_random_patch | 20 +++ .github/workflows/main.yml | 69 ++++++++++ .github/workflows/reset.py | 47 +++++++ .github/workflows/smartpgp.cfg | 6 + .github/workflows/test_SmartPGP.py | 118 ++++++++++++++++++ 6 files changed, 316 insertions(+) create mode 100644 .github/workflows/card-status.py create mode 100644 .github/workflows/jcardsim_patches/jcardsim_random_patch create mode 100644 .github/workflows/main.yml create mode 100644 .github/workflows/reset.py create mode 100644 .github/workflows/smartpgp.cfg create mode 100644 .github/workflows/test_SmartPGP.py diff --git a/.github/workflows/card-status.py b/.github/workflows/card-status.py new file mode 100644 index 0000000..704f273 --- /dev/null +++ b/.github/workflows/card-status.py @@ -0,0 +1,56 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# OpenPGPpy : Scan devices demo +# Copyright (C) 2020 BitLogiK +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, version 3 of the License. +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# You should have received a copy of the GNU General Public License +# along with this program. If not, see + + +try: + import OpenPGPpy +except ModuleNotFoundError: + # Import the OpenPGPpy from parent or current folder + # Can run demo w/o OpenPGPpy installed (from root or demo folder) + from sys import path + + path.append(".") + path.append("..") + import OpenPGPpy + + +def main(): + i = 0 + displayed = 0 + while True: + try: + current_card = OpenPGPpy.OpenPGPcard(reader_index=i) + print("--------------------------------------------------------") + print(f"OpenPGP card/reader : {current_card.name}") + print(f"OpenPGP version : {current_card.pgpverstr}") + print( + f"Manufacturer : {current_card.manufacturer} ({current_card.manufacturer_id})" + ) + print( + f"Device serial : {current_card.serial} ({hex(current_card.serial)})" + ) + displayed += 1 + i += 1 + except OpenPGPpy.ConnectionException as exc: + if str(exc) != "No OpenPGP applet on this reader.": + break + i += 1 + if displayed == 0: + print("No OpenPGP device available") + + +if __name__ == "__main__": + main() diff --git a/.github/workflows/jcardsim_patches/jcardsim_random_patch b/.github/workflows/jcardsim_patches/jcardsim_random_patch new file mode 100644 index 0000000..477643d --- /dev/null +++ b/.github/workflows/jcardsim_patches/jcardsim_random_patch @@ -0,0 +1,20 @@ +diff --git a/src/main/java/com/licel/jcardsim/crypto/SecureRandomNullProvider.java b/src/main/java/com/licel/jcardsim/crypto/SecureRandomNullProvider.java +index f4cca44..70d41a3 100644 +--- a/src/main/java/com/licel/jcardsim/crypto/SecureRandomNullProvider.java ++++ b/src/main/java/com/licel/jcardsim/crypto/SecureRandomNullProvider.java +@@ -32,7 +32,14 @@ class SecureRandomNullProvider extends SecureRandom { + } + @Override + protected void engineNextBytes(byte[] arg) { +- engine.nextBytes(arg); ++ final String doSecureRandom = System.getProperty("com.licel.jcardsim.randomdata.secure", "0"); ++ if ("1".equals(doSecureRandom)){ ++ SecureRandom randomGenerator = new SecureRandom(); ++ randomGenerator.nextBytes(arg); ++ } ++ else{ ++ engine.nextBytes(arg); ++ } + } + @Override + protected byte[] engineGenerateSeed(int len) { diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..0ebc5cc --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,69 @@ +name: smartgpg-test-applet + +# Run this workflow every time a new commit pushed to your repository +on: push + +jobs: + applet_tests: + runs-on: ubuntu-18.04 + steps: + # Checkout repository + - name: checkout repository + uses: actions/checkout@v2 + # Run actions + # Compilation tests + - name: applet tests + shell: bash + run: | + # 1) Get dependencies + sudo apt-get install -y --no-install-recommends procps autoconf automake libtool m4 pkg-config help2man make gcc ant automake autotools-dev sudo wget gnupg software-properties-common maven git pcscd libpcsclite-dev opensc; + # 2) Get JDK + wget -qO - https://adoptopenjdk.jfrog.io/adoptopenjdk/api/gpg/key/public | sudo apt-key add -; + sudo add-apt-repository --yes https://adoptopenjdk.jfrog.io/adoptopenjdk/deb/; + 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; + # 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; + # 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 + cd jcardsim && patch -p1 < ../.github/workflows/jcardsim_patches/jcardsim_random_patch && cd -; + # Compile + cd jcardsim && export JC_CLASSIC_HOME=/tmp/jc305u3_kit/ && mvn initialize && mvn clean install && cd -; + # 7) Clone vsmartcard, compile and install + git clone https://github.com/frankmorgner/vsmartcard.git; + cd vsmartcard/virtualsmartcard && autoreconf --verbose --install && ./configure --sysconfdir=/etc && make && sudo make install && cd -; + # 8) (re)Launch PCSCD + sudo killall pcscd && sudo pcscd -fad &>/tmp/log_pcsc & echo "PCSCD launched"; + sleep 2; + # 9) launch jcardsim + java -cp jcardsim/target/jcardsim-3.0.5-SNAPSHOT.jar:SmartPGPApplet/smartpgp.jar com.licel.jcardsim.remote.VSmartCard .github/workflows/smartpgp.cfg &>/tmp/log_jcardsim & echo "jcardsim launched!"; + # Wait a bit for insertion + sleep 2; + echo "========= PCSCD log" && cat /tmp/log_pcsc; + 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; + pip3 install OpenPGPpy; + # Install applet + opensc-tool -l; + opensc-tool -s 80b800001810d276000124010304AFAF000000000000050000020F0F00; + # Get card status + python3 .github/workflows/card-status.py; + # Test ECDSA signatures + python3 .github/workflows/test_SmartPGP.py; + # Reset + python3 .github/workflows/reset.py; + # Test crypto switch + 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; + echo -n "BBBBBBBBBBBBBBBB" > /tmp/aes_to_encrypt.bin; + python2 bin/smartpgp-cli encrypt-aes -p "123456" -i /tmp/aes_to_encrypt.bin -o /tmp/aes_encrypted.bin; + python2 bin/smartpgp-cli decrypt-aes -p "123456" -i /tmp/aes_encrypted.bin -o /tmp/aes_decrypted.bin; + diff /tmp/aes_decrypted.bin /tmp/aes_to_encrypt.bin; diff --git a/.github/workflows/reset.py b/.github/workflows/reset.py new file mode 100644 index 0000000..962e6e1 --- /dev/null +++ b/.github/workflows/reset.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# OpenPGPpy : Reset device demo +# Copyright (C) 2020 BitLogiK +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, version 3 of the License. +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# You should have received a copy of the GNU General Public License +# along with this program. If not, see + + +import getpass + + +try: + import OpenPGPpy +except ModuleNotFoundError: + # Import the OpenPGPpy from parent or current folder + # Can run demo w/o OpenPGPpy installed (from root or demo folder) + from sys import path + + path.append(".") + path.append("..") + import OpenPGPpy + + +def main(): + mydevice = OpenPGPpy.OpenPGPcard() + print("Enter the PUK to reset the", mydevice.name) + PIN3 = "12345678" #getpass.getpass("Enter PIN3 (PUK) : ") + try: + mydevice.reset(PIN3) + except OpenPGPpy.PGPCardException as exc: + if exc.sw_code == 0x6982 or exc.sw_code == 0x6A80: + print("Error: Wrong PUK") + return + print("Reset done.") + + +if __name__ == "__main__": + main() diff --git a/.github/workflows/smartpgp.cfg b/.github/workflows/smartpgp.cfg new file mode 100644 index 0000000..ddce280 --- /dev/null +++ b/.github/workflows/smartpgp.cfg @@ -0,0 +1,6 @@ +com.licel.jcardsim.card.applet.0.AID=d276000124010304AFAF000000000000 +com.licel.jcardsim.card.applet.0.Class=fr.anssi.smartpgp.SmartPGPApplet +com.licel.jcardsim.card.ATR=3B80800101 +com.licel.jcardsim.vsmartcard.host=localhost +com.licel.jcardsim.vsmartcard.port=35963 +com.licel.jcardsim.randomdata.secure=1 diff --git a/.github/workflows/test_SmartPGP.py b/.github/workflows/test_SmartPGP.py new file mode 100644 index 0000000..ddbdb85 --- /dev/null +++ b/.github/workflows/test_SmartPGP.py @@ -0,0 +1,118 @@ +#!/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 +import getpass +import hashlib +import tempfile + + +path.append(".") +import OpenPGPpy + + +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 check_signature(msg, signature, pubkeyd): + pubkey = 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: + subprocess.run( + verify_cmd, input=msg, shell=True, check=True, stdout=subprocess.PIPE + ) + sigOK = True + except Exception: + print("Error in signature verification") + print(">>> Requires openssl in path to check signatures") + sigOK = False + os.remove(fpk.name) + os.remove(fsig.name) + return sigOK + + +def main(): + 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: + 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') + + PIN1 = "123456" #getpass.getpass("Enter PIN1 : ") + + # Make 200 ECDSA + print(f"\nPublicKey for signature : 0x{pubkey_card.hex()}") + 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") + else: + print("Can't check signature") + return + + +if __name__ == "__main__": + try: + main() + except OpenPGPpy.PGPCardException as exc: + if exc.sw_code == 0x6F00: + print("Crash, game over.") + print("SFYL !") + print(exc)