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)