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)