Compare commits
10 Commits
c2d7070f2d
...
c289445f9f
Author | SHA1 | Date | |
---|---|---|---|
c289445f9f | |||
8b977953a3 | |||
64eed711d0 | |||
|
75f884de5d | ||
|
efdbcb6274 | ||
|
8b1463c399 | ||
|
faaa48c042 | ||
|
e84853414f | ||
|
0b43929a83 | ||
|
4fbafe513b |
56
.github/workflows/card-status.py
vendored
Normal file
56
.github/workflows/card-status.py
vendored
Normal 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()
|
54
.github/workflows/main.yml
vendored
Normal file
54
.github/workflows/main.yml
vendored
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
name: smartgpg-test-applet
|
||||||
|
|
||||||
|
# Run this workflow every time a new commit pushed to your repository
|
||||||
|
on: push
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
applet_tests:
|
||||||
|
runs-on: ubuntu-20.04
|
||||||
|
steps:
|
||||||
|
# Checkout repository
|
||||||
|
- name: checkout repository
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
# Run actions
|
||||||
|
# Compilation tests
|
||||||
|
- name: applet tests
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
# get dependencies
|
||||||
|
echo "==== 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;
|
||||||
|
sudo apt-get install -y python3-setuptools python3-pyscard python3-pyasn1;
|
||||||
|
pip3 install OpenPGPpy;
|
||||||
|
# get JavaCard SDKs
|
||||||
|
echo "==== get JavaCard SDKs";
|
||||||
|
git clone https://github.com/martinpaljak/oracle_javacard_sdks && mv oracle_javacard_sdks/jc303_kit/ /tmp/ && mv oracle_javacard_sdks/jc305u4_kit/ /tmp/ && rm -rf oracle_javacard_sdks;
|
||||||
|
# compile SmartPGP
|
||||||
|
echo "==== compile SmartPGP";
|
||||||
|
cat build.xml | sed 's/<cap /<cap export="SmartPGPApplet" /' > /tmp/build.xml && mv /tmp/build.xml ./;
|
||||||
|
JC_HOME=/tmp/jc303_kit/ ant;
|
||||||
|
# clone jcardsim repository, compile and install
|
||||||
|
echo "==== clone jcardsim repository, compile and install";
|
||||||
|
git clone https://github.com/licel/jcardsim;
|
||||||
|
cd jcardsim && export JC_CLASSIC_HOME=/tmp/jc305u4_kit/ && mvn initialize && mvn clean package && cd -;
|
||||||
|
# clone vsmartcard, compile and install
|
||||||
|
echo "==== 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 -;
|
||||||
|
# relaunch PCSC
|
||||||
|
echo "==== relaunch PCSC";
|
||||||
|
sudo killall -KILL pcscd 2> /dev/null || true;
|
||||||
|
sudo pcscd -fad 2>&1 > /tmp/log_pcsc &
|
||||||
|
sleep 2;
|
||||||
|
# launch jcardsim
|
||||||
|
echo "==== launch jcardsim";
|
||||||
|
java -cp jcardsim/target/jcardsim-3.0.5-SNAPSHOT.jar:SmartPGPApplet/smartpgp.jar com.licel.jcardsim.remote.VSmartCard .github/workflows/smartpgp.cfg 2>&1 > /tmp/log_jcardsim &
|
||||||
|
sleep 5;
|
||||||
|
# install SmartPGP
|
||||||
|
echo "==== test SmartPGP";
|
||||||
|
opensc-tool -l;
|
||||||
|
opensc-tool -s 80b800001810d276000124010304AFAF000000000000050000020F0F00;
|
||||||
|
# get card status
|
||||||
|
python3 .github/workflows/card-status.py;
|
||||||
|
# main tests
|
||||||
|
python3 .github/workflows/test_SmartPGP.py;
|
47
.github/workflows/reset.py
vendored
Normal file
47
.github/workflows/reset.py
vendored
Normal 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
6
.github/workflows/smartpgp.cfg
vendored
Normal 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
|
372
.github/workflows/test_SmartPGP.py
vendored
Normal file
372
.github/workflows/test_SmartPGP.py
vendored
Normal file
@ -0,0 +1,372 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
import os
|
||||||
|
from sys import path
|
||||||
|
import subprocess
|
||||||
|
import getpass
|
||||||
|
import hashlib
|
||||||
|
import tempfile
|
||||||
|
|
||||||
|
|
||||||
|
path.append(".")
|
||||||
|
import OpenPGPpy
|
||||||
|
|
||||||
|
|
||||||
|
class BadTestResult(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def sha256(data):
|
||||||
|
return hashlib.sha256(data).digest()
|
||||||
|
|
||||||
|
|
||||||
|
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_rsa_2048_signature(message, signature, pubkeyd):
|
||||||
|
pubkey = rsa_2048_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=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_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:
|
||||||
|
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
|
||||||
|
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:
|
||||||
|
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:
|
||||||
|
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
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
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 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("[-] 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(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 != 0x9000:
|
||||||
|
print(f"[-] FAILED with: 0x{exc.sw_code:02x}")
|
||||||
|
exit(2)
|
||||||
|
except BadTestResult:
|
||||||
|
print(f"[-] FAILED test")
|
||||||
|
exit(3)
|
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
*.jar
|
||||||
|
*.cap
|
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
[submodule "sdks"]
|
||||||
|
path = sdks
|
||||||
|
url = https://github.com/martinpaljak/oracle_javacard_sdks
|
@ -462,17 +462,17 @@ class CardConnectionContext:
|
|||||||
raise AdminPINFailed
|
raise AdminPINFailed
|
||||||
####### step 3bis
|
####### step 3bis
|
||||||
if nresetting_code != None:
|
if nresetting_code != None:
|
||||||
set_resetting_code(self.connection, nresetting_code)
|
(_,sw1,sw2) = set_resetting_code(self.connection, nresetting_code)
|
||||||
if sw1!=0x90 or sw2!=0x00:
|
if sw1!=0x90 or sw2!=0x00:
|
||||||
print("set_resetting_code failed")
|
print("set_resetting_code failed")
|
||||||
return
|
return
|
||||||
####### step 4
|
####### step 4
|
||||||
change_reference_data_pw1(self.connection, ascii_encode_pin(pw1), list(npw1))
|
(sw1,sw2) = change_reference_data_pw1(self.connection, ascii_encode_pin(pw1), list(npw1))
|
||||||
if sw1!=0x90 or sw2!=0x00:
|
if sw1!=0x90 or sw2!=0x00:
|
||||||
print("change_reference_data_pw1 failed")
|
print("change_reference_data_pw1 failed")
|
||||||
return
|
return
|
||||||
####### step 4bis
|
####### step 4bis
|
||||||
change_reference_data_pw3(self.connection, ascii_encode_pin(pw3), list(npw3))
|
(sw1,sw2) = change_reference_data_pw3(self.connection, ascii_encode_pin(pw3), list(npw3))
|
||||||
if sw1!=0x90 or sw2!=0x00:
|
if sw1!=0x90 or sw2!=0x00:
|
||||||
print("change_reference_data_pw3 failed")
|
print("change_reference_data_pw3 failed")
|
||||||
return
|
return
|
||||||
|
25
build.xml
25
build.xml
@ -1,13 +1,18 @@
|
|||||||
<?xml version="1.0"?>
|
<?xml version="1.0"?>
|
||||||
<project name="smartpgp" default="convert" basedir=".">
|
<project name="smartpgp" default="convert" basedir=".">
|
||||||
<description>Ant build for SmartPGP applet</description>
|
<description>Ant build for SmartPGP applet</description>
|
||||||
<get src="https://github.com/martinpaljak/ant-javacard/releases/download/v20.03.25/ant-javacard.jar" dest="." skipexisting="true"/>
|
|
||||||
<taskdef name="javacard" classname="pro.javacard.ant.JavaCard" classpath="ant-javacard.jar"/>
|
<!-- Add JC303 SDK for JCOP 3.0.1 -->
|
||||||
<target name="convert">
|
<property name="JC303" value="sdks/jc303_kit"/>
|
||||||
<javacard>
|
|
||||||
<cap output="SmartPGPApplet.cap" sources="src" aid="d27600012401" version="1.0">
|
<get src="https://github.com/martinpaljak/ant-javacard/releases/download/v20.03.25/ant-javacard.jar" dest="." skipexisting="true"/>
|
||||||
<applet class="fr.anssi.smartpgp.SmartPGPApplet" aid="d276000124010304AFAF000000000000"/>
|
<taskdef name="javacard" classname="pro.javacard.ant.JavaCard" classpath="ant-javacard.jar"/>
|
||||||
</cap>
|
|
||||||
</javacard>
|
<target name="convert">
|
||||||
</target>
|
<javacard>
|
||||||
|
<cap jckit="${JC303}" output="SmartPGPApplet.cap" sources="src" aid="d27600012401" version="1.0">
|
||||||
|
<applet class="fr.anssi.smartpgp.SmartPGPApplet" aid="d276000124010304c0deedb522560000"/>
|
||||||
|
</cap>
|
||||||
|
</javacard>
|
||||||
|
</target>
|
||||||
</project>
|
</project>
|
||||||
|
@ -2,15 +2,6 @@ diff --git a/src/fr/anssi/smartpgp/Constants.java b/src/fr/anssi/smartpgp/Consta
|
|||||||
index b3c0f80..2ee1107 100644
|
index b3c0f80..2ee1107 100644
|
||||||
--- a/src/fr/anssi/smartpgp/Constants.java
|
--- a/src/fr/anssi/smartpgp/Constants.java
|
||||||
+++ b/src/fr/anssi/smartpgp/Constants.java
|
+++ b/src/fr/anssi/smartpgp/Constants.java
|
||||||
@@ -30,7 +30,7 @@ public final class Constants {
|
|
||||||
protected static final short INTERNAL_BUFFER_MAX_LENGTH =
|
|
||||||
(short)0x500;
|
|
||||||
|
|
||||||
- protected static final short APDU_MAX_LENGTH = (short)256;
|
|
||||||
+ protected static final short APDU_MAX_LENGTH = (short)0x400;
|
|
||||||
|
|
||||||
protected static final byte[] KEY_DERIVATION_FUNCTION_DEFAULT = {
|
|
||||||
(byte)0x81, (byte)0x01, (byte)0x00
|
|
||||||
@@ -149,7 +149,7 @@ public final class Constants {
|
@@ -149,7 +149,7 @@ public final class Constants {
|
||||||
(byte)0x73, /* card capabilities */
|
(byte)0x73, /* card capabilities */
|
||||||
(byte)0xC0, /* 1st byte: "methods supported" see ISO 7816-4 */
|
(byte)0xC0, /* 1st byte: "methods supported" see ISO 7816-4 */
|
||||||
@ -20,25 +11,3 @@ index b3c0f80..2ee1107 100644
|
|||||||
|
|
||||||
(byte)0x05, /* status indicator byte : operational state */
|
(byte)0x05, /* status indicator byte : operational state */
|
||||||
(byte)0x90, /* SW1 */
|
(byte)0x90, /* SW1 */
|
||||||
diff --git a/src/fr/anssi/smartpgp/SmartPGPApplet.java b/src/fr/anssi/smartpgp/SmartPGPApplet.java
|
|
||||||
index c47870f..78d446d 100644
|
|
||||||
--- a/src/fr/anssi/smartpgp/SmartPGPApplet.java
|
|
||||||
+++ b/src/fr/anssi/smartpgp/SmartPGPApplet.java
|
|
||||||
@@ -25,7 +25,7 @@ import javacard.security.*;
|
|
||||||
import javacardx.apdu.*;
|
|
||||||
import javacardx.crypto.*;
|
|
||||||
|
|
||||||
-public final class SmartPGPApplet extends Applet {
|
|
||||||
+public final class SmartPGPApplet extends Applet implements ExtendedLength {
|
|
||||||
|
|
||||||
private final Persistent data;
|
|
||||||
|
|
||||||
@@ -104,7 +104,7 @@ public final class SmartPGPApplet extends Applet {
|
|
||||||
short blen = apdu.setIncomingAndReceive();
|
|
||||||
|
|
||||||
final short lc = apdu.getIncomingLength();
|
|
||||||
- final short offcdata = ISO7816.OFFSET_CDATA;
|
|
||||||
+ final short offcdata = apdu.getOffsetCdata();
|
|
||||||
|
|
||||||
short off = transients.chainingInputLength();
|
|
||||||
|
|
||||||
|
1
sdks
Submodule
1
sdks
Submodule
@ -0,0 +1 @@
|
|||||||
|
Subproject commit e305a1a0b9bf6b9a8b0c91a9aad8d73537e7ff1b
|
@ -27,7 +27,7 @@ public final class Constants {
|
|||||||
protected static final short INTERNAL_BUFFER_MAX_LENGTH =
|
protected static final short INTERNAL_BUFFER_MAX_LENGTH =
|
||||||
(short)0x500;
|
(short)0x500;
|
||||||
|
|
||||||
protected static final short APDU_MAX_LENGTH = (short)256;
|
protected static final short APDU_MAX_LENGTH = (short)0x400;
|
||||||
|
|
||||||
protected static final byte[] KEY_DERIVATION_FUNCTION_DEFAULT = {
|
protected static final byte[] KEY_DERIVATION_FUNCTION_DEFAULT = {
|
||||||
(byte)0x81, (byte)0x01, (byte)0x00
|
(byte)0x81, (byte)0x01, (byte)0x00
|
||||||
|
@ -236,8 +236,8 @@ public final class Persistent {
|
|||||||
Common.beginTransaction(isRegistering);
|
Common.beginTransaction(isRegistering);
|
||||||
user_pin_length = (byte)Constants.USER_PIN_DEFAULT.length;
|
user_pin_length = (byte)Constants.USER_PIN_DEFAULT.length;
|
||||||
user_pin.update(Constants.USER_PIN_DEFAULT, (short)0, user_pin_length);
|
user_pin.update(Constants.USER_PIN_DEFAULT, (short)0, user_pin_length);
|
||||||
user_pin.resetAndUnblock();
|
|
||||||
Common.commitTransaction(isRegistering);
|
Common.commitTransaction(isRegistering);
|
||||||
|
user_pin.resetAndUnblock();
|
||||||
|
|
||||||
Common.beginTransaction(isRegistering);
|
Common.beginTransaction(isRegistering);
|
||||||
user_puk_length = (short)0;
|
user_puk_length = (short)0;
|
||||||
@ -253,8 +253,8 @@ public final class Persistent {
|
|||||||
Common.beginTransaction(isRegistering);
|
Common.beginTransaction(isRegistering);
|
||||||
admin_pin_length = (byte)Constants.ADMIN_PIN_DEFAULT.length;
|
admin_pin_length = (byte)Constants.ADMIN_PIN_DEFAULT.length;
|
||||||
admin_pin.update(Constants.ADMIN_PIN_DEFAULT, (short)0, admin_pin_length);
|
admin_pin.update(Constants.ADMIN_PIN_DEFAULT, (short)0, admin_pin_length);
|
||||||
admin_pin.resetAndUnblock();
|
|
||||||
Common.commitTransaction(isRegistering);
|
Common.commitTransaction(isRegistering);
|
||||||
|
admin_pin.resetAndUnblock();
|
||||||
|
|
||||||
isTerminated = false;
|
isTerminated = false;
|
||||||
}
|
}
|
||||||
|
@ -23,8 +23,9 @@ package fr.anssi.smartpgp;
|
|||||||
import javacard.framework.*;
|
import javacard.framework.*;
|
||||||
import javacard.security.*;
|
import javacard.security.*;
|
||||||
import javacardx.crypto.*;
|
import javacardx.crypto.*;
|
||||||
|
import javacardx.apdu.ExtendedLength;
|
||||||
|
|
||||||
public final class SmartPGPApplet extends Applet {
|
public final class SmartPGPApplet extends Applet implements ExtendedLength {
|
||||||
|
|
||||||
private final Common common;
|
private final Common common;
|
||||||
private final Persistent data;
|
private final Persistent data;
|
||||||
@ -100,7 +101,7 @@ public final class SmartPGPApplet extends Applet {
|
|||||||
short blen = apdu.setIncomingAndReceive();
|
short blen = apdu.setIncomingAndReceive();
|
||||||
|
|
||||||
final short lc = apdu.getIncomingLength();
|
final short lc = apdu.getIncomingLength();
|
||||||
final short offcdata = ISO7816.OFFSET_CDATA;
|
final short offcdata = apdu.getOffsetCdata();
|
||||||
|
|
||||||
short off = transients.chainingInputLength();
|
short off = transients.chainingInputLength();
|
||||||
|
|
||||||
@ -766,10 +767,17 @@ public final class SmartPGPApplet extends Applet {
|
|||||||
|
|
||||||
case (byte)0x02:
|
case (byte)0x02:
|
||||||
assertAdmin();
|
assertAdmin();
|
||||||
if((lc < Constants.USER_PIN_MIN_SIZE) ||
|
if(data.keyDerivationIsActive()) {
|
||||||
(lc > Constants.USER_PIN_MAX_SIZE)) {
|
if(lc != data.keyDerivationSize()) {
|
||||||
ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
|
ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if((lc < Constants.USER_PIN_MIN_SIZE) ||
|
||||||
|
(lc > Constants.USER_PIN_MAX_SIZE)) {
|
||||||
|
ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
transients.setUserPinMode81(false);
|
transients.setUserPinMode81(false);
|
||||||
transients.setUserPinMode82(false);
|
transients.setUserPinMode82(false);
|
||||||
@ -1141,10 +1149,17 @@ public final class SmartPGPApplet extends Applet {
|
|||||||
|
|
||||||
case Constants.TAG_RESETTING_CODE:
|
case Constants.TAG_RESETTING_CODE:
|
||||||
assertAdmin();
|
assertAdmin();
|
||||||
if((lc < Constants.USER_PUK_MIN_SIZE) ||
|
if(data.keyDerivationIsActive()) {
|
||||||
(lc > Constants.USER_PUK_MAX_SIZE)) {
|
if(lc != data.keyDerivationSize()) {
|
||||||
ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
|
ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if((lc < Constants.USER_PUK_MIN_SIZE) ||
|
||||||
|
(lc > Constants.USER_PUK_MAX_SIZE)) {
|
||||||
|
ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
JCSystem.beginTransaction();
|
JCSystem.beginTransaction();
|
||||||
data.user_puk_length = (byte)lc;
|
data.user_puk_length = (byte)lc;
|
||||||
|
Loading…
Reference in New Issue
Block a user