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.
This commit is contained in:
Ryad Benadjila 2021-03-25 10:38:27 +01:00 committed by Arnaud Fontaine
parent 4fbafe513b
commit 0b43929a83
6 changed files with 316 additions and 0 deletions

56
.github/workflows/card-status.py vendored Normal file
View File

@ -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 <http://www.gnu.org/licenses/>
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()

View File

@ -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) {

69
.github/workflows/main.yml vendored Normal file
View File

@ -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/<cap /<cap export="SmartPGPApplet" /' > /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;

47
.github/workflows/reset.py vendored Normal file
View File

@ -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 <http://www.gnu.org/licenses/>
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()

6
.github/workflows/smartpgp.cfg vendored Normal file
View File

@ -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

118
.github/workflows/test_SmartPGP.py vendored Normal file
View File

@ -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)