Compare commits
34 Commits
c2d7070f2d
...
javacard-3
Author | SHA1 | Date | |
---|---|---|---|
7a87360a4a | |||
459e16b8f0 | |||
37c5ed9540 | |||
653e968ee2 | |||
69b5c1a9bc | |||
55c1a5edb3 | |||
27f845187f | |||
dd1b18e5cb | |||
cc7f455e83 | |||
943f922668 | |||
52c4efd5f5 | |||
0b77c33d97 | |||
819f4b736b | |||
141cf85eb7 | |||
e9584d79b8 | |||
604de144c6 | |||
1724975b08 | |||
41c367371f | |||
6925c1a366 | |||
5168bb86d8 | |||
d70ca4b185 | |||
f14a76b2ae | |||
f9593b4b41 | |||
c59f9618ba | |||
c289445f9f | |||
8b977953a3 | |||
64eed711d0 | |||
|
75f884de5d | ||
|
efdbcb6274 | ||
|
8b1463c399 | ||
|
faaa48c042 | ||
|
e84853414f | ||
|
0b43929a83 | ||
|
4fbafe513b |
57
.github/workflows/card-status.py
vendored
Normal file
57
.github/workflows/card-status.py
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# OpenPGPpy : Scan devices demo
|
||||
# Copyright (C) 2020 BitLogiK
|
||||
# Copyright (C) 2023 Code Fox <SmartPGP@c0de.dev>
|
||||
#
|
||||
# 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;
|
48
.github/workflows/reset.py
vendored
Normal file
48
.github/workflows/reset.py
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# OpenPGPpy : Reset device demo
|
||||
# Copyright (C) 2020 BitLogiK
|
||||
# Copyright (C) 2023 Code Fox <SmartPGP@c0de.dev>
|
||||
#
|
||||
# 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=dev.c0de.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)
|
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
*.jar
|
||||
*.cap
|
||||
*/.venv/*
|
||||
*.pyc
|
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
|
18
.vscode/settings.json
vendored
Normal file
18
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"license.author": "Code Fox <SmartPGP@c0de.dev>",
|
||||
"license.extension": ".txt",
|
||||
"license.default": "GPL-2.0",
|
||||
"license.filename": "LICENSE",
|
||||
"license.year": "auto",
|
||||
"cSpell.words": [
|
||||
"APDU",
|
||||
"apdubuf",
|
||||
"CodeFactor",
|
||||
"Infineon",
|
||||
"minlen",
|
||||
"nopad",
|
||||
"offcdata",
|
||||
"pkcs",
|
||||
"smartpgp"
|
||||
]
|
||||
}
|
6
AUTHORS
6
AUTHORS
@@ -1,6 +0,0 @@
|
||||
Maintainer: Arnaud Fontaine
|
||||
|
||||
Contributors:
|
||||
- Arnaud Fontaine
|
||||
- Pierre Chifflier (bin/*)
|
||||
|
@@ -291,7 +291,7 @@ convey the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
Copyright (C) <YEAR> <YOUR NAME> (<YOUR EMAIL ADDRESS>)
|
||||
|
||||
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
|
168
README.md
168
README.md
@@ -1,159 +1,131 @@
|
||||
# SmartPGP applet
|
||||
|
||||
[](https://www.codefactor.io/repository/github/c0de-fox/smartpgp)
|
||||
|
||||
SmartPGP is a free and open source implementation of the [OpenPGP card
|
||||
3.4 specification](https://gnupg.org/ftp/specs/OpenPGP-smart-card-application-3.4.pdf) in JavaCard.
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
The following features are implemented at the applet level, but some
|
||||
of them depend on underlying hardware support and available
|
||||
(non-)volatile memory resources:
|
||||
|
||||
- RSA (>= 2048 bits modulus, 17 bits exponent) for signature,
|
||||
encryption and authentication;
|
||||
- RSA (>= 2048 bits modulus, 17 bits exponent) for signature, encryption and authentication
|
||||
- On-board key generation and external private key import
|
||||
- PIN codes (user, admin and resetting code) up to 127 characters each
|
||||
- Certificate up to 1 kB (DER encoded) for each key
|
||||
- Login, URL, and private DOs up to 256 bytes
|
||||
- Command and response chaining
|
||||
- AES 128/256 bits deciphering primitive
|
||||
|
||||
- On-board key generation and external private key import;
|
||||
## Warnings
|
||||
|
||||
- PIN codes (user, admin and resetting code) up to 127 characters;
|
||||
### ROCA
|
||||
|
||||
- Certificate up to 1 kB (DER encoded) for each key;
|
||||
[Infineon SLE78](https://www.infineon.com/cms/en/product/security-smart-card-solutions/security-controllers/contactless-and-dual-interface-security-controllers/) chips are vulnerable to [ROCA](https://crocs.fi.muni.cz/public/papers/rsa_ccs17).
|
||||
This attack is only relevant if you used on-device key generation. It allows an adversary to obtain your private key, using only your public key.
|
||||
|
||||
- Login, URL, and private DOs up to 256 bytes;
|
||||
|
||||
- Command and response chaining;
|
||||
|
||||
- AES 128/256 bits deciphering primitive;
|
||||
There isn't much that can be done to rectify this, other than generating the private RSA keys off of your device and importing them. [Other work arounds](https://crocs.fi.muni.cz/public/papers/rsa_ccs17#detection_tools_mitigation_and_workarounds). (Using the [OpenCrypto JCMathLib](https://github.com/OpenCryptoProject/JCMathLib) to handle the cryptographic functions may work too)
|
||||
|
||||
Use [this tool](https://github.com/crocs-muni/roca#install-with-pip) to determine if your public keys are vulnerable.
|
||||
|
||||
## Default values
|
||||
|
||||
The SmartPGP applet is configured with the following default values:
|
||||
|
||||
- Admin PIN is 12345678;
|
||||
- Admin PIN is 12345678
|
||||
- User PIN is 123456
|
||||
- No PUK (a.k.a. resetting code) is defined
|
||||
- RSA 2048 bits for PGP keys
|
||||
|
||||
- User PIN is 123456;
|
||||
These values can be changed by modifying the [Constants](src/dev/c0de/smartpgp/Constants.java) class.
|
||||
|
||||
- No PUK (a.k.a. resetting code) is defined;
|
||||
After the applet has been installed, the [smartpgp-cli](bin/smartpgp-cli) utility
|
||||
will be able to change the above values.
|
||||
|
||||
- RSA 2048 bits for PGP keys;
|
||||
_Note: These settings are restored if you factory reset the applet._
|
||||
|
||||
These values can be changed by modifying default values in the code
|
||||
(see the [Constants](src/fr/anssi/smartpgp/Constants.java)
|
||||
class).
|
||||
__Note: If you change algorithm attributes, the key and corresponding certificate will be erased.__
|
||||
|
||||
When the applet is installed, one can use the `smartpgp-cli` utility
|
||||
given in the `bin` directory to change these values. Keep in mind that
|
||||
when you change the algorithm attributes of a PGP key, the key and the
|
||||
corresponding certificate are
|
||||
erased. Also note that hard coded default values will be restored upon
|
||||
a factory reset.
|
||||
## Build and Install instructions
|
||||
|
||||
### Prerequisites
|
||||
|
||||
# Application support
|
||||
- A Java compiler (No higher than OpenJDK 11 or equivalent)
|
||||
- A device compliant with JavaCard 3.0.1 (or above) with enough available resources
|
||||
- Applet: ~23 KiB of non-volatile (eeprom/flash) memory
|
||||
- Persistant Data: ~10 KiB of non-volatile (eeprom/flash) memory
|
||||
- Transient Data: ~2 KiB of volatile (RAM) memory
|
||||
|
||||
Tokens following the OpenPGP card 3.4 specification are not yet fully
|
||||
supported by most PGP applications.
|
||||
<!-- - The [pyscard](https://pypi.org/project/pyscard/) and [pyasn1](https://pypi.org/project/pyasn1/)
|
||||
Python libraries for `smartcard-cli`. -->
|
||||
|
||||
## GnuPG
|
||||
### Importing RSA keys above 2048 bits
|
||||
|
||||
OpenPGP card 3.x is supported by [GnuPG](https://www.gnupg.org/)
|
||||
starting from version 2.1.16.
|
||||
The internal buffer that stores keys is configured with a default value that is only large enough for RSA 2048 bit keys.
|
||||
|
||||
## OpenKeychain
|
||||
If your card is able to handle larger RSA key bit-lengths (3072 or 4096), and you want to import those keys, you will need to increase the buffer size.
|
||||
|
||||
OpenPGP card 3.x is supported by [OpenKeychain](https://www.openkeychain.org/)
|
||||
starting from version 4.2.
|
||||
This can be accomplished by modifying `Constants.INTERNAL_BUFFER_MAX_LENGTH` in [Constants.java](src/dev/c0de/smartpgp/Constants.java)
|
||||
|
||||
#### RSA 2048 bit keys
|
||||
|
||||
# Content of the repository
|
||||
When produced by OpenPGP, these keys are 949 Bytes in length.
|
||||
`Constants.INTERNAL_BUFFER_MAX_LENGTH` may not be smaller than `(short)0x3b6` (decimal: 950)
|
||||
|
||||
The repository contains several directories:
|
||||
#### RSA 3072 bit keys
|
||||
|
||||
- `bin` contains a Python library and command line tool called
|
||||
`smartpgp-cli` to interact with an OpenPGP card 3.x;
|
||||
When produced by OpenPGP, these keys are 1294 Bytes in length.
|
||||
`Constants.INTERNAL_BUFFER_MAX_LENGTH` may not be smaller than `(short)0x50f` (decimal: 1295)
|
||||
|
||||
- `src` contains the JavaCard source code of the SmartPGP applet;
|
||||
#### RSA 4096 bit keys
|
||||
|
||||
- `videos` contains sample videos demonstrating smartcard interactions
|
||||
with OpenKeychain and K9 mail on Android Nexus 5.
|
||||
When produced by OpenPGP, these keys are 1644 Bytes in length.
|
||||
`Constants.INTERNAL_BUFFER_MAX_LENGTH` may not be smaller than `(short)0x66d` (decimal: 1645)
|
||||
|
||||
### Reducing flash and/or RAM consumption
|
||||
|
||||
When the applet is installed, all data structures will be allocated
|
||||
to their maximum size. This is a standard practice for JavaCard applets
|
||||
to ensure that there will not be a memory allocation failure at runtime.
|
||||
|
||||
# Build and installation instructions
|
||||
A consequence of this is that, if you configure a large rsa key and
|
||||
use a small key, that extra space can not be used for anything else.
|
||||
|
||||
|
||||
## Prerequisites
|
||||
- JavaCard Development Kit 3.0.1 (or above) from
|
||||
[Oracle website](http://www.oracle.com/technetwork/java/embedded/javacard/downloads/index.html);
|
||||
|
||||
- A device compliant with JavaCard 3.0.1 (or above) with enough
|
||||
available resources to hold the code (approximately 23 kB of
|
||||
non-volatile memory), persistent data (approximately 10 kB of
|
||||
non-volatile memory) and volatile data (approximately 2 kB of RAM).
|
||||
|
||||
- The [pyscard](https://pypi.org/project/pyscard/) and [pyasn1](https://pypi.org/project/pyasn1/)
|
||||
Python libraries for `smartcard-cli`.
|
||||
|
||||
|
||||
## Importing RSA keys above 2048 bits (3072 or 4096 bits)
|
||||
|
||||
The size of the internal buffer is set by default to a value that
|
||||
permits to import RSA 2048 bits. If your card is able to deal with RSA
|
||||
keys of 3072 or 4096 bits and you want to be able to import such keys,
|
||||
then you need to adjust the size of this buffer:
|
||||
|
||||
- for RSA 2048 bits, `Constants.INTERNAL_BUFFER_MAX_LENGTH` must be at
|
||||
least `(short)0x3b0`;
|
||||
|
||||
- for RSA 3072 bits, `Constants.INTERNAL_BUFFER_MAX_LENGTH` must be at
|
||||
least `(short)0x570`;
|
||||
|
||||
- for RSA 4096 bits, `Constants.INTERNAL_BUFFER_MAX_LENGTH` must be at
|
||||
least `(short)0x730`.
|
||||
|
||||
|
||||
## Reducing flash and/or RAM consumption
|
||||
|
||||
The applet allocates all its data structures to their maximal size
|
||||
at installation to avoid as much as possible runtime errors caused by
|
||||
memory allocation failure. If your device does not have enough flash
|
||||
and/or RAM available, or if you plan not to use some features
|
||||
(e.g. stored certificates), you can adjust the applet to reduce its
|
||||
resource consumption by tweaking the following variables:
|
||||
If your device does not have enough flash memory and/or RAM, or you plan
|
||||
to not use some features (eg. on-device certificates), you can modify
|
||||
the following variables in [Constants.java](src/dev/c0de/smartpgp/Constants.java)
|
||||
|
||||
- `Constants.INTERNAL_BUFFER_MAX_LENGTH`: the size in bytes of the
|
||||
internal RAM buffer used for input/output chaining. Chaining is
|
||||
especially used in case of long commands and responses such as those
|
||||
involved in private key import and certificate import/export;
|
||||
|
||||
- `Constants.EXTENDED_CAPABILITIES`, bytes 5 and 6: the maximal size
|
||||
in bytes of a certificate associated to a key. Following the OpenPGP
|
||||
card specification, a certificate can be stored for each of the
|
||||
three keys.
|
||||
- `Constants.EXTENDED_CAPABILITIES`, bytes 5 and 6: the maximum size
|
||||
in bytes of a certificate associated to a key. The default is 1152 Bytes.
|
||||
A certificate can be stored for each of the three keys.
|
||||
|
||||
### Building the CAP file
|
||||
|
||||
## Building the CAP file
|
||||
|
||||
- Set path to the JavaCard Development Kit:
|
||||
`export JC_HOME="your/path/to/javacardkit"`
|
||||
|
||||
- (Optional) Edit the `build.xml` file and replace the `0xAF:0xAF`
|
||||
bytes in the `APPLET_AID` with your own manufacturer identifier (see
|
||||
section 4.2.1 of OpenPGP card specification). Alternatively, set the
|
||||
- (Optional) Edit the `build.xml` file and replace the `APPLET_AID`
|
||||
with your a unique value. Alternatively, set the
|
||||
right AID instance bytes during applet installation.
|
||||
Generate the AID using [this tool](https://c0de.dev/c0de/SmartPGP-AID-Generator)
|
||||
|
||||
- Execute `ant` with no parameter will produce the CAP file in
|
||||
`SmartPGPApplet.cap`.
|
||||
- Execute `ant` to build `SmartPGPApplet.cap`
|
||||
|
||||
## Installing the CAP file
|
||||
### Installing the CAP file
|
||||
|
||||
The CAP file installation depends on your device, so you have to refer
|
||||
to the instructions given by your device manufacturer. Most open cards
|
||||
relying on Global Platform with default keys are supported by
|
||||
[GlobalPlatformPro](https://github.com/martinpaljak/GlobalPlatformPro).
|
||||
|
||||
Be careful to use a valid AID according to the OpenPGP card
|
||||
specification (see section 4.2.1) for each card (`-create <AID>` with
|
||||
GlobalPlatformPro)
|
||||
You must use a valid, unique AID that matches the OpenPGP card specification (see section 4.2.1)
|
||||
for each card (`-create <AID>` with GlobalPlatformPro)
|
||||
|
||||
Example Installation commands:
|
||||
|
||||
- `gp --install SmartPGPApplet.cap --default`
|
||||
- `gp --install SmartPGPApplet.cap --create <AID>`
|
||||
|
@@ -1,8 +1,9 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# SmartPGP : JavaCard implementation of OpenPGP card v3 specification
|
||||
# https://github.com/ANSSI-FR/SmartPGP
|
||||
# https://c0de.dev/c0de/SmartPGP
|
||||
# Copyright (C) 2016 ANSSI
|
||||
# Copyright (C) 2023 Code Fox <SmartPGP@c0de.dev>
|
||||
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
|
@@ -1,6 +1,7 @@
|
||||
# SmartPGP : JavaCard implementation of OpenPGP card v3 specification
|
||||
# https://github.com/ANSSI-FR/SmartPGP
|
||||
# https://c0de.dev/c0de/SmartPGP
|
||||
# Copyright (C) 2016 ANSSI
|
||||
# Copyright (C) 2023 Code Fox <SmartPGP@c0de.dev>
|
||||
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
|
@@ -1,7 +1,8 @@
|
||||
|
||||
# SmartPGP : JavaCard implementation of OpenPGP card v3 specification
|
||||
# https://github.com/ANSSI-FR/SmartPGP
|
||||
# https://c0de.dev/c0de/SmartPGP
|
||||
# Copyright (C) 2016 ANSSI
|
||||
# Copyright (C) 2023 Code Fox <SmartPGP@c0de.dev>
|
||||
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
@@ -462,17 +463,17 @@ class CardConnectionContext:
|
||||
raise AdminPINFailed
|
||||
####### step 3bis
|
||||
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:
|
||||
print("set_resetting_code failed")
|
||||
return
|
||||
####### 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:
|
||||
print("change_reference_data_pw1 failed")
|
||||
return
|
||||
####### 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:
|
||||
print("change_reference_data_pw3 failed")
|
||||
return
|
||||
|
@@ -1,12 +1,17 @@
|
||||
<?xml version="1.0"?>
|
||||
<project name="smartpgp" default="convert" basedir=".">
|
||||
<description>Ant build for SmartPGP applet</description>
|
||||
|
||||
<!-- Add JC303 SDK for JCOP 3.0.1 -->
|
||||
<property name="JC303" value="sdks/jc303_kit"/>
|
||||
|
||||
<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"/>
|
||||
|
||||
<target name="convert">
|
||||
<javacard>
|
||||
<cap output="SmartPGPApplet.cap" sources="src" aid="d27600012401" version="1.0">
|
||||
<applet class="fr.anssi.smartpgp.SmartPGPApplet" aid="d276000124010304AFAF000000000000"/>
|
||||
<cap jckit="${JC303}" output="SmartPGPApplet.cap" sources="src" aid="d27600012401" version="1.0">
|
||||
<applet class="dev.c0de.smartpgp.SmartPGPApplet" aid="d276000124010304c0de000000000000"/>
|
||||
</cap>
|
||||
</javacard>
|
||||
</target>
|
||||
|
@@ -1,17 +0,0 @@
|
||||
diff --git a/src/fr/anssi/smartpgp/SmartPGPApplet.java b/src/fr/anssi/smartpgp/SmartPGPApplet.java
|
||||
index c47870f..87162fb 100644
|
||||
--- a/src/fr/anssi/smartpgp/SmartPGPApplet.java
|
||||
+++ b/src/fr/anssi/smartpgp/SmartPGPApplet.java
|
||||
@@ -1329,10 +1329,12 @@ public final class SmartPGPApplet extends Applet {
|
||||
return;
|
||||
}
|
||||
|
||||
+/*
|
||||
if(data.admin_pin.getTriesRemaining() <= 0) {
|
||||
data.isTerminated = true;
|
||||
return;
|
||||
}
|
||||
+*/
|
||||
|
||||
assertAdmin();
|
||||
|
@@ -1,44 +0,0 @@
|
||||
diff --git a/src/fr/anssi/smartpgp/Constants.java b/src/fr/anssi/smartpgp/Constants.java
|
||||
index b3c0f80..2ee1107 100644
|
||||
--- a/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 {
|
||||
(byte)0x73, /* card capabilities */
|
||||
(byte)0xC0, /* 1st byte: "methods supported" see ISO 7816-4 */
|
||||
(byte)0x01, /* 2nd byte: "data coding byte" idem */
|
||||
- (byte)0x80, /* 3rd byte: command chaining (not extended length by default as all readers do not support them...) */
|
||||
+ (byte)0xC0, /* 3rd byte: command chaining + extended length */
|
||||
|
||||
(byte)0x05, /* status indicator byte : operational state */
|
||||
(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
Submodule sdks added at e305a1a0b9
@@ -1,7 +1,8 @@
|
||||
/*
|
||||
SmartPGP : JavaCard implementation of OpenPGP card v3 specification
|
||||
https://github.com/ANSSI-FR/SmartPGP
|
||||
https://c0de.dev/c0de/SmartPGP
|
||||
Copyright (C) 2016 ANSSI
|
||||
Copyright (C) 2023 Code Fox <SmartPGP@c0de.dev>
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
@@ -18,7 +19,7 @@
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
package fr.anssi.smartpgp;
|
||||
package dev.c0de.smartpgp;
|
||||
|
||||
import javacard.framework.*;
|
||||
import javacard.security.*;
|
||||
@@ -31,9 +32,11 @@ public final class Common {
|
||||
protected final RandomData random;
|
||||
|
||||
protected Common() {
|
||||
/* Get an instance of cryptography Ciphers */
|
||||
cipher_aes_cbc_nopad = Cipher.getInstance(Cipher.ALG_AES_BLOCK_128_CBC_NOPAD, false);
|
||||
cipher_rsa_pkcs1 = Cipher.getInstance(Cipher.ALG_RSA_PKCS1, false);
|
||||
|
||||
/* Get an instance of the Secure Random Number Generator */
|
||||
random = RandomData.getInstance(RandomData.ALG_SECURE_RANDOM);
|
||||
}
|
||||
|
||||
@@ -157,8 +160,8 @@ public final class Common {
|
||||
}
|
||||
}
|
||||
|
||||
protected static final short writeAlgorithmInformation(final byte key_tag,
|
||||
final byte[] buf, short off) {
|
||||
protected static final short writeAlgorithmInformation(final byte key_tag, final byte[] buf, short off) {
|
||||
|
||||
for(short m = 2; m <= 4; ++m) {
|
||||
for(byte form = Constants.RSA_IMPORT_SUPPORTS_FORMAT_1 ? 1 : 3; form <= 3; form += 2) {
|
||||
buf[off++] = key_tag;
|
@@ -1,7 +1,8 @@
|
||||
/*
|
||||
SmartPGP : JavaCard implementation of OpenPGP card v3 specification
|
||||
https://github.com/ANSSI-FR/SmartPGP
|
||||
https://c0de.dev/c0de/SmartPGP
|
||||
Copyright (C) 2016 ANSSI
|
||||
Copyright (C) 2023 Code Fox <SmartPGP@c0de.dev>
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
@@ -18,51 +19,57 @@
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
package fr.anssi.smartpgp;
|
||||
package dev.c0de.smartpgp;
|
||||
|
||||
import javacard.framework.*;
|
||||
|
||||
public final class Constants {
|
||||
|
||||
protected static final short INTERNAL_BUFFER_MAX_LENGTH =
|
||||
(short)0x500;
|
||||
/* 0x3b6 for rsa 2048; 0x50f for rsa 3072; 0x66d for rsa 4096 */
|
||||
protected static final short INTERNAL_BUFFER_MAX_LENGTH = (short)0x3b6; // Max size of key in bytes
|
||||
|
||||
protected static final short APDU_MAX_LENGTH = (short)256;
|
||||
protected static final short APDU_MAX_LENGTH = (short)0x400;
|
||||
|
||||
/* See section 4.3.2 of the specification; Default is NONE; Standard UTF-8 PWs*/
|
||||
protected static final byte[] KEY_DERIVATION_FUNCTION_DEFAULT = {
|
||||
(byte)0x81, (byte)0x01, (byte)0x00
|
||||
};
|
||||
|
||||
protected static final byte USER_PIN_RETRY_COUNT = 3;
|
||||
protected static final byte USER_PIN_MIN_SIZE = 0x06;
|
||||
protected static final byte USER_PIN_MAX_SIZE = 0x7f; /* max is 0x7f because PIN format 2 */
|
||||
protected static final byte USER_PIN_RETRY_COUNT = 3; /* Card gets locked after this many incorrect attempts */
|
||||
protected static final byte USER_PIN_MIN_SIZE = 0x06; /* 6 chars is minimum as defined by spec */
|
||||
protected static final byte USER_PIN_MAX_SIZE = 0x7f; /* 127 chars is maximum as defined by spec */
|
||||
|
||||
/* UTF-8 bytes for the default user pin: 123456 is the value defined by the specification */
|
||||
protected static final byte[] USER_PIN_DEFAULT = {
|
||||
(byte)0x31, (byte)0x32, (byte)0x33, (byte)0x34,
|
||||
(byte)0x35, (byte)0x36
|
||||
};
|
||||
|
||||
/* Is the USER_PIN required for signing actions; default: true */
|
||||
protected static final boolean USER_PIN_DEFAULT_FORCE_VERIFY_SIGNATURE = true;
|
||||
|
||||
protected static final byte USER_PUK_RETRY_COUNT = 3;
|
||||
protected static final byte USER_PUK_MIN_SIZE = 0x08;
|
||||
protected static final byte USER_PUK_MAX_SIZE = 0x7f; /* max is 0x7f because PIN format 2 */
|
||||
protected static final byte USER_PUK_RETRY_COUNT = 3; /* */
|
||||
protected static final byte USER_PUK_MIN_SIZE = 0x08; /* 8 chars is minimum as defined by spec */
|
||||
protected static final byte USER_PUK_MAX_SIZE = 0x7f; /* 127 chars is maximum as defined by spec */
|
||||
|
||||
protected static final byte ADMIN_PIN_RETRY_COUNT = 3;
|
||||
protected static final byte ADMIN_PIN_MIN_SIZE = 0x08;
|
||||
protected static final byte ADMIN_PIN_MAX_SIZE = 0x7f; /* max is 0x7f because PIN format 2 */
|
||||
protected static final byte ADMIN_PIN_RETRY_COUNT = 3; /* Card gets reset after this many failed attempts */
|
||||
protected static final byte ADMIN_PIN_MIN_SIZE = 0x08; /* 8 chars is minimum as defined by spec */
|
||||
protected static final byte ADMIN_PIN_MAX_SIZE = 0x7f; /* 127 chars is maximum as defined by spec */
|
||||
protected static final byte[] ADMIN_PIN_DEFAULT = {
|
||||
(byte)0x31, (byte)0x32, (byte)0x33, (byte)0x34,
|
||||
(byte)0x35, (byte)0x36, (byte)0x37, (byte)0x38
|
||||
};
|
||||
|
||||
|
||||
protected static final byte FINGERPRINT_SIZE = 20;
|
||||
protected static final byte GENERATION_DATE_SIZE = 4;
|
||||
protected static final byte FINGERPRINT_SIZE = 20; /* size of each fingerprint in bytes */
|
||||
protected static final byte GENERATION_DATE_SIZE = 4; /* number of bytes to store the date+time */
|
||||
|
||||
protected static final byte NAME_MAX_LENGTH = 39;
|
||||
protected static final byte LANG_MIN_LENGTH = 2;
|
||||
protected static final byte LANG_MAX_LENGTH = 8;
|
||||
protected static final byte[] LANG_DEFAULT = { (byte)0x65, (byte)0x6e };
|
||||
protected static final byte NAME_MAX_LENGTH = 39; /* max number of chars in cardholder name */
|
||||
protected static final byte LANG_MIN_LENGTH = 2; /* 2 char language codes */
|
||||
protected static final byte LANG_MAX_LENGTH = 8; /* spec allows up to 4 languages */
|
||||
protected static final byte[] LANG_DEFAULT = { (byte)0x65, (byte)0x6e }; /* utf-8: EN */
|
||||
|
||||
/* Unsure if the following bytes and shorts actually need to be in card memory */
|
||||
|
||||
protected static final byte SEX_NOT_KNOWN = (byte)0x30;
|
||||
protected static final byte SEX_MALE = (byte)0x31;
|
||||
@@ -107,6 +114,8 @@ public final class Constants {
|
||||
protected static final short TAG_KEY_DERIVATION_FUNCTION = (short)0x00f9;
|
||||
protected static final short TAG_ALGORITHM_INFORMATION = (short)0x00fa;
|
||||
|
||||
/* The bytes and shorts below appear to be needed */
|
||||
|
||||
protected static final byte CRT_TAG_AUTHENTICATION_KEY = (byte)0xa4;
|
||||
protected static final byte CRT_TAG_SIGNATURE_KEY = (byte)0xb6;
|
||||
protected static final byte CRT_TAG_DECRYPTION_KEY = (byte)0xb8;
|
||||
@@ -148,7 +157,10 @@ public final class Constants {
|
||||
(byte)0x73, /* card capabilities */
|
||||
(byte)0xC0, /* 1st byte: "methods supported" see ISO 7816-4 */
|
||||
(byte)0x01, /* 2nd byte: "data coding byte" idem */
|
||||
(byte)0x80, /* 3rd byte: command chaining (not extended length by default as all readers do not support them...) */
|
||||
|
||||
/* 3rd byte: command chaining + extended length; Set to 0xC0 if
|
||||
extended length is supported by card and reader */
|
||||
(byte)0x80,
|
||||
|
||||
(byte)0x05, /* status indicator byte : operational state */
|
||||
(byte)0x90, /* SW1 */
|
||||
@@ -165,9 +177,9 @@ public final class Constants {
|
||||
0x02 | /* support PSO:DEC/ENC AES */
|
||||
0x01), /* support KDF-DO */
|
||||
(byte)0x00, /* SM 0x01 = 128 bits, 0x02 = 256 bits, 0x03 = SCP11b */
|
||||
(byte)0x00, (byte)0x20, /* max length get challenge */
|
||||
(byte)0x04, (byte)0x80, /* max length of carholder certificate */
|
||||
(byte)0x00, (byte)0xff, /* max length of special DOs (private, login, url, KDF-DO) */
|
||||
(byte)0x00, (byte)0x20, /* max length of get challenge response in bytes (decimal: 32) */
|
||||
(byte)0x04, (byte)0x80, /* max length of cardholder certificate in bytes (decimal: 1152) */
|
||||
(byte)0x00, (byte)0xff, /* max length of special DOs (private, login, url, KDF-DO) in bytes (decimal: 255) */
|
||||
(byte)0x00, /* PIN format 2 is not supported */
|
||||
(byte)0x00 /* MSE not supported */
|
||||
};
|
||||
@@ -213,6 +225,7 @@ public final class Constants {
|
||||
protected static final byte ALGORITHM_ATTRIBUTES_MIN_LENGTH = 6;
|
||||
protected static final byte ALGORITHM_ATTRIBUTES_MAX_LENGTH = 13;
|
||||
|
||||
/* FIXME: Can I modify these to get safer private key generation? */
|
||||
protected static final byte[] ALGORITHM_ATTRIBUTES_DEFAULT = {
|
||||
(byte)0x01, /* RSA */
|
||||
(byte)0x08, (byte)0x00, /* 2048 bits modulus */
|
@@ -1,7 +1,8 @@
|
||||
/*
|
||||
SmartPGP : JavaCard implementation of OpenPGP card v3 specification
|
||||
https://github.com/ANSSI-FR/SmartPGP
|
||||
https://c0de.dev/c0de/SmartPGP
|
||||
Copyright (C) 2016 ANSSI
|
||||
Copyright (C) 2023 Code Fox <SmartPGP@c0de.dev>
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
@@ -18,7 +19,7 @@
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
package fr.anssi.smartpgp;
|
||||
package dev.c0de.smartpgp;
|
||||
|
||||
import javacard.framework.*;
|
||||
|
@@ -1,7 +1,8 @@
|
||||
/*
|
||||
SmartPGP : JavaCard implementation of OpenPGP card v3 specification
|
||||
https://github.com/ANSSI-FR/SmartPGP
|
||||
https://c0de.dev/c0de/SmartPGP
|
||||
Copyright (C) 2016 ANSSI
|
||||
Copyright (C) 2023 Code Fox <SmartPGP@c0de.dev>
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
@@ -18,7 +19,7 @@
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
package fr.anssi.smartpgp;
|
||||
package dev.c0de.smartpgp;
|
||||
|
||||
import javacard.framework.*;
|
||||
import javacard.security.*;
|
||||
@@ -178,6 +179,14 @@ public final class PGPKey {
|
||||
return Util.getShort(attributes, (short)3);
|
||||
}
|
||||
|
||||
/*
|
||||
* !!! WARNING !!! - Read this if your JavaCard is Infineon SLE78
|
||||
* The API called by this function is flawed and vulnerable to ROCA.
|
||||
* Malicious actors are able to determine the private key using ONLY the public key.
|
||||
*
|
||||
* It's HIGHLY recommended that you do NOT use this API; Instead, you
|
||||
* should generate your private key off-device, then import it later
|
||||
*/
|
||||
private final KeyPair generateRSA() {
|
||||
final PrivateKey priv = (PrivateKey)KeyBuilder.buildKey(KeyBuilder.TYPE_RSA_CRT_PRIVATE, rsaModulusBitSize(), false);
|
||||
final RSAPublicKey pub = (RSAPublicKey)KeyBuilder.buildKey(KeyBuilder.TYPE_RSA_PUBLIC, rsaModulusBitSize(), false);
|
@@ -1,7 +1,8 @@
|
||||
/*
|
||||
SmartPGP : JavaCard implementation of OpenPGP card v3 specification
|
||||
https://github.com/ANSSI-FR/SmartPGP
|
||||
https://c0de.dev/c0de/SmartPGP
|
||||
Copyright (C) 2016 ANSSI
|
||||
Copyright (C) 2023 Code Fox <SmartPGP@c0de.dev>
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
@@ -18,7 +19,7 @@
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
package fr.anssi.smartpgp;
|
||||
package dev.c0de.smartpgp;
|
||||
|
||||
import javacard.framework.*;
|
||||
import javacard.security.*;
|
||||
@@ -86,8 +87,7 @@ public final class Persistent {
|
||||
protected final OwnerPIN admin_pin; /* PW3 */
|
||||
protected byte admin_pin_length;
|
||||
|
||||
|
||||
|
||||
/* Called at Applet Install time; Reserve memory of persistent data objects */
|
||||
protected Persistent() {
|
||||
login = new byte[Constants.specialDoMaxLength()];
|
||||
login_length = 0;
|
||||
@@ -236,8 +236,8 @@ public final class Persistent {
|
||||
Common.beginTransaction(isRegistering);
|
||||
user_pin_length = (byte)Constants.USER_PIN_DEFAULT.length;
|
||||
user_pin.update(Constants.USER_PIN_DEFAULT, (short)0, user_pin_length);
|
||||
user_pin.resetAndUnblock();
|
||||
Common.commitTransaction(isRegistering);
|
||||
user_pin.resetAndUnblock();
|
||||
|
||||
Common.beginTransaction(isRegistering);
|
||||
user_puk_length = (short)0;
|
||||
@@ -253,8 +253,8 @@ public final class Persistent {
|
||||
Common.beginTransaction(isRegistering);
|
||||
admin_pin_length = (byte)Constants.ADMIN_PIN_DEFAULT.length;
|
||||
admin_pin.update(Constants.ADMIN_PIN_DEFAULT, (short)0, admin_pin_length);
|
||||
admin_pin.resetAndUnblock();
|
||||
Common.commitTransaction(isRegistering);
|
||||
admin_pin.resetAndUnblock();
|
||||
|
||||
isTerminated = false;
|
||||
}
|
@@ -1,7 +1,8 @@
|
||||
/*
|
||||
SmartPGP : JavaCard implementation of OpenPGP card v3 specification
|
||||
https://github.com/ANSSI-FR/SmartPGP
|
||||
https://c0de.dev/c0de/SmartPGP
|
||||
Copyright (C) 2016 ANSSI
|
||||
Copyright (C) 2023 Code Fox <SmartPGP@c0de.dev>
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
@@ -18,13 +19,14 @@
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
package fr.anssi.smartpgp;
|
||||
package dev.c0de.smartpgp;
|
||||
|
||||
import javacard.framework.*;
|
||||
import javacard.security.*;
|
||||
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 Persistent data;
|
||||
@@ -37,11 +39,13 @@ public final class SmartPGPApplet extends Applet {
|
||||
transients = new Transients();
|
||||
}
|
||||
|
||||
/* Called on applet install */
|
||||
public static final void install(byte[] buf, short off, byte len) {
|
||||
// Reserve memory immediately
|
||||
new SmartPGPApplet().register(buf, (short)(off + 1), buf[off]);
|
||||
}
|
||||
|
||||
private final PGPKey currentTagOccurenceToKey() {
|
||||
private final PGPKey currentTagOccurrenceToKey() {
|
||||
switch(transients.currentTagOccurrence()) {
|
||||
case 0:
|
||||
return data.pgp_keys[Persistent.PGP_KEYS_OFFSET_AUT];
|
||||
@@ -100,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();
|
||||
|
||||
@@ -419,7 +423,7 @@ public final class SmartPGPApplet extends Applet {
|
||||
break;
|
||||
|
||||
case Constants.TAG_CARDHOLDER_CERTIFICATE:
|
||||
k = currentTagOccurenceToKey();
|
||||
k = currentTagOccurrenceToKey();
|
||||
|
||||
if(k == null) {
|
||||
ISOException.throwIt(Constants.SW_REFERENCE_DATA_NOT_FOUND);
|
||||
@@ -469,7 +473,7 @@ public final class SmartPGPApplet extends Applet {
|
||||
return 0;
|
||||
}
|
||||
|
||||
final PGPKey k = currentTagOccurenceToKey();
|
||||
final PGPKey k = currentTagOccurrenceToKey();
|
||||
|
||||
if(k == null) {
|
||||
ISOException.throwIt(Constants.SW_REFERENCE_DATA_NOT_FOUND);
|
||||
@@ -766,11 +770,18 @@ public final class SmartPGPApplet extends Applet {
|
||||
|
||||
case (byte)0x02:
|
||||
assertAdmin();
|
||||
if(data.keyDerivationIsActive()) {
|
||||
if(lc != data.keyDerivationSize()) {
|
||||
ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
|
||||
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.setUserPinMode82(false);
|
||||
JCSystem.beginTransaction();
|
||||
@@ -1054,7 +1065,7 @@ public final class SmartPGPApplet extends Applet {
|
||||
|
||||
case Constants.TAG_CARDHOLDER_CERTIFICATE:
|
||||
assertAdmin();
|
||||
k = currentTagOccurenceToKey();
|
||||
k = currentTagOccurrenceToKey();
|
||||
if(k == null) {
|
||||
ISOException.throwIt(Constants.SW_REFERENCE_DATA_NOT_FOUND);
|
||||
return;
|
||||
@@ -1141,11 +1152,18 @@ public final class SmartPGPApplet extends Applet {
|
||||
|
||||
case Constants.TAG_RESETTING_CODE:
|
||||
assertAdmin();
|
||||
if(data.keyDerivationIsActive()) {
|
||||
if(lc != data.keyDerivationSize()) {
|
||||
ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if((lc < Constants.USER_PUK_MIN_SIZE) ||
|
||||
(lc > Constants.USER_PUK_MAX_SIZE)) {
|
||||
ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
|
||||
return;
|
||||
}
|
||||
}
|
||||
JCSystem.beginTransaction();
|
||||
data.user_puk_length = (byte)lc;
|
||||
data.user_puk.update(buf, (short)0, data.user_puk_length);
|
@@ -1,7 +1,8 @@
|
||||
/*
|
||||
SmartPGP : JavaCard implementation of OpenPGP card v3 specification
|
||||
https://github.com/ANSSI-FR/SmartPGP
|
||||
https://c0de.dev/c0de/SmartPGP
|
||||
Copyright (C) 2016 ANSSI
|
||||
Copyright (C) 2023 Code Fox <SmartPGP@c0de.dev>
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
@@ -18,7 +19,7 @@
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
package fr.anssi.smartpgp;
|
||||
package dev.c0de.smartpgp;
|
||||
|
||||
import javacard.framework.*;
|
||||
|
@@ -6,7 +6,7 @@
|
||||
<target name="convert">
|
||||
<javacard>
|
||||
<cap output="TestApplet.cap" sources="src" aid="aaaaaaaaaaaa" version="1.0">
|
||||
<applet class="fr.anssi.smartpgp.TestApplet" aid="aaaaaaaaaaaa00000000000000000000"/>
|
||||
<applet class="dev.c0de.smartpgp.TestApplet" aid="aaaaaaaaaaaa00000000000000000000"/>
|
||||
</cap>
|
||||
</javacard>
|
||||
</target>
|
||||
|
@@ -1,4 +1,4 @@
|
||||
package fr.anssi.smartpgp;
|
||||
package dev.c0de.smartpgp;
|
||||
|
||||
|
||||
public final class Data {
|
||||
|
@@ -1,4 +1,4 @@
|
||||
package fr.anssi.smartpgp;
|
||||
package dev.c0de.smartpgp;
|
||||
|
||||
public final class ECConstants {
|
||||
|
||||
|
@@ -1,4 +1,4 @@
|
||||
package fr.anssi.smartpgp;
|
||||
package dev.c0de.smartpgp;
|
||||
|
||||
|
||||
import javacard.framework.*;
|
||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user