Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add XDH and EdDSA #25

Merged
merged 8 commits into from
Apr 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ jobs:

strategy:
matrix:
java: [ "11", "17", "21" ]
java: [ "17", "21", "22" ]
name: Build reader on Java ${{ matrix.java }}
steps:
- uses: actions/checkout@v4
Expand Down Expand Up @@ -88,7 +88,7 @@ jobs:

strategy:
matrix:
java: [ "11", "17", "21" ]
java: [ "17", "21", "22"]
env:
# ffs: https://github.com/adoptium/adoptium-support/issues/485 !!!
# also, add the wolfcrypt JNI path
Expand Down
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,15 @@ There are three parts of ECTester, the JavaCard applet used for testing, the rea
standalone app which tests software libraries. The target platform for ECTester is Linux, but things should work on
Windows as well, although testing of standalone libraries will be limited to Java libraries and Microsoft CNG library.

The ECTester parts require different Java versions. Reader and standalone parts require Java >= 15 while the applet build
will be able to target different JavaCard versions based on the Java version, see [this list](https://github.com/martinpaljak/ant-javacard/wiki/JavaCard-SDK-and-JDK-version-compatibility).

To build ECTester simply do:
```bash
git submodule update --init --recursive # To initialize submodules (JavaCard SDKs, Microsoft CNG, BoringSSL, ...)
./gradlew :applet:buildJavaCard # To build the applet (cap) -> "applet/build/javacard/applet[221,222,305].cap".
./gradlew :reader:uberJar # To build the reader tool (jar) -> "reader/build/libs/ECTesterReader.jar"
./gradlew :standalone:libs # To build the native library shims. (Necessary
./gradlew :standalone:libs # To build the native library shims.
./gradlew :standalone:uberJar # To build the standalone tool (jar) -> "standalone/build/libs/ECTesterStandalone.jar"
```
The applet comes in several flavors, targeting JavaCard `2.2.1`, `2.2.2` and `3.0.5`. The `2.2.2` and later flavors
Expand Down
2 changes: 1 addition & 1 deletion common/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,5 @@ dependencies {
}

java {
sourceCompatibility = JavaVersion.VERSION_11
sourceCompatibility = JavaVersion.VERSION_15
}
54 changes: 48 additions & 6 deletions common/src/main/java/cz/crcs/ectester/common/util/ECUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,22 @@

import cz.crcs.ectester.common.ec.*;
import cz.crcs.ectester.data.EC_Store;
import org.bouncycastle.asn1.ASN1OctetString;
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
import org.bouncycastle.crypto.digests.SHA1Digest;
import org.bouncycastle.crypto.signers.PlainDSAEncoding;
import org.bouncycastle.crypto.signers.StandardDSAEncoding;
import org.bouncycastle.jcajce.interfaces.EdDSAPrivateKey;
import org.bouncycastle.jcajce.interfaces.EdDSAPublicKey;
import org.bouncycastle.jcajce.interfaces.XDHPrivateKey;
import org.bouncycastle.jcajce.interfaces.XDHPublicKey;

import java.io.FileInputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.KeyPair;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.interfaces.ECKey;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
import java.security.*;
import java.security.interfaces.*;
import java.security.spec.*;
import java.util.LinkedList;
import java.util.List;
Expand Down Expand Up @@ -456,6 +458,46 @@ public static ECKey loadKey(short params, String named, String file, AlgorithmPa
return null;
}

public static byte[] pubkeyToBytes(PublicKey pubkey) {
if (pubkey instanceof ECPublicKey) {
ECPublicKey ecPublicKey = (ECPublicKey) pubkey;
return ECUtil.toX962Uncompressed(ecPublicKey.getW(), ecPublicKey.getParams());
} else if (pubkey instanceof XECPublicKey) {
XECPublicKey xedPublicKey = (XECPublicKey) pubkey;
return xedPublicKey.getU().toByteArray();
} else if (pubkey instanceof EdECPublicKey) {
EdECPublicKey edECPublicKey = (EdECPublicKey) pubkey;
return edECPublicKey.getPoint().getY().toByteArray();
} else if (pubkey instanceof XDHPublicKey) {
XDHPublicKey xdhPublicKey = (XDHPublicKey) pubkey;
return xdhPublicKey.getU().toByteArray();
// Special-case BouncyCastle XDH
} else if (pubkey instanceof EdDSAPublicKey) {
EdDSAPublicKey edDSAPublicKey = (EdDSAPublicKey) pubkey;
// Special-case BouncyCastle EdDSA
return edDSAPublicKey.getPointEncoding();
}
return null;
}

public static byte[] privkeyToBytes(PrivateKey privkey) {
if (privkey instanceof ECPrivateKey) {
ECPrivateKey ecPrivateKey = (ECPrivateKey) privkey;
return ecPrivateKey.getS().toByteArray();
} else if (privkey instanceof XECPrivateKey) {
XECPrivateKey xecPrivateKey = (XECPrivateKey) privkey;
return xecPrivateKey.getScalar().get();
} else if (privkey instanceof EdECPrivateKey) {
EdECPrivateKey edECPrivateKey = (EdECPrivateKey) privkey;
return edECPrivateKey.getBytes().get();
} else if (privkey instanceof XDHPrivateKey || privkey instanceof EdDSAPrivateKey) {
// Special-case BouncyCastle XDH and EdDSA
PrivateKeyInfo xpkinfo = PrivateKeyInfo.getInstance(privkey.getEncoded());
return ASN1OctetString.getInstance(xpkinfo.getPrivateKey().getOctets()).getOctets();
}
return null;
}

public static boolean equalKeyPairParameters(ECPrivateKey priv, ECPublicKey pub) {
if (priv == null || pub == null) {
return false;
Expand Down
2 changes: 1 addition & 1 deletion reader/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ dependencies {
}

java {
sourceCompatibility = JavaVersion.VERSION_11
sourceCompatibility = JavaVersion.VERSION_15
}

application {
Expand Down
2 changes: 1 addition & 1 deletion standalone/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ dependencies {
}

java {
sourceCompatibility = JavaVersion.VERSION_11
sourceCompatibility = JavaVersion.VERSION_15
}

application {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,6 @@
import cz.crcs.ectester.standalone.consts.SignatureIdent;
import cz.crcs.ectester.standalone.libs.*;
import cz.crcs.ectester.standalone.output.FileTestWriter;
import cz.crcs.ectester.standalone.output.TextTestWriter;
import cz.crcs.ectester.standalone.output.XMLTestWriter;
import cz.crcs.ectester.standalone.output.YAMLTestWriter;
import cz.crcs.ectester.standalone.test.suites.*;
import org.apache.commons.cli.*;

Expand All @@ -55,8 +52,7 @@
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.*;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
import java.security.interfaces.*;
import java.security.spec.AlgorithmParameterSpec;
import java.security.spec.ECGenParameterSpec;
import java.security.spec.ECParameterSpec;
Expand Down Expand Up @@ -306,19 +302,19 @@ private void listLibraries() {
System.out.println(Colors.bold("\t\t- Supports native timing: ") + lib.getNativeTimingSupport().toString());
Set<KeyPairGeneratorIdent> kpgs = lib.getKPGs();
if (!kpgs.isEmpty()) {
System.out.println(Colors.bold("\t\t- KeyPairGenerators: ") + kpgs.stream().map(KeyPairGeneratorIdent::getName).collect(Collectors.joining(", ")));
System.out.println(Colors.bold("\t\t- KeyPairGenerators: ") + kpgs.stream().map(KeyPairGeneratorIdent::getName).sorted().collect(Collectors.joining(", ")));
}
Set<KeyAgreementIdent> eckas = lib.getKAs();
if (!eckas.isEmpty()) {
System.out.println(Colors.bold("\t\t- KeyAgreements: ") + eckas.stream().map(KeyAgreementIdent::getName).collect(Collectors.joining(", ")));
System.out.println(Colors.bold("\t\t- KeyAgreements: ") + eckas.stream().map(KeyAgreementIdent::getName).sorted().collect(Collectors.joining(", ")));
}
Set<SignatureIdent> sigs = lib.getSigs();
if (!sigs.isEmpty()) {
System.out.println(Colors.bold("\t\t- Signatures: ") + sigs.stream().map(SignatureIdent::getName).collect(Collectors.joining(", ")));
System.out.println(Colors.bold("\t\t- Signatures: ") + sigs.stream().map(SignatureIdent::getName).sorted().collect(Collectors.joining(", ")));
}
Set<String> curves = lib.getCurves();
if (!curves.isEmpty()) {
System.out.println(Colors.bold("\t\t- Curves: ") + String.join(", ", curves));
System.out.println(Colors.bold("\t\t- Curves: ") + curves.stream().sorted().collect(Collectors.joining(", ")));
}
System.out.println();
}
Expand Down Expand Up @@ -458,8 +454,8 @@ private void ecdh() throws NoSuchAlgorithmException, InvalidAlgorithmParameterEx
other = kpg.genKeyPair();
}

ECPrivateKey privkey = (ECPrivateKey) ECUtil.loadKey(EC_Consts.PARAMETER_S, cli.getOptionValue("ecdh.named-private"), cli.getOptionValue("ecdh.private"), spec);
ECPublicKey pubkey = (ECPublicKey) ECUtil.loadKey(EC_Consts.PARAMETER_W, cli.getOptionValue("ecdh.named-public"), cli.getOptionValue("ecdh.public"), spec);
PrivateKey privkey = (ECPrivateKey) ECUtil.loadKey(EC_Consts.PARAMETER_S, cli.getOptionValue("ecdh.named-private"), cli.getOptionValue("ecdh.private"), spec);
PublicKey pubkey = (ECPublicKey) ECUtil.loadKey(EC_Consts.PARAMETER_W, cli.getOptionValue("ecdh.named-public"), cli.getOptionValue("ecdh.public"), spec);

int amount = Integer.parseInt(cli.getOptionValue("ecdh.amount", "1"));
for (int i = 0; i < amount || amount == 0; ++i) {
Expand All @@ -471,11 +467,11 @@ private void ecdh() throws NoSuchAlgorithmException, InvalidAlgorithmParameterEx
}

if (!cli.hasOption("ecdh.named-private") && !cli.hasOption("ecdh.private")) {
privkey = (ECPrivateKey) one.getPrivate();
privkey = one.getPrivate();
}

if (!cli.hasOption("ecdh.named-public") && !cli.hasOption("ecdh.public")) {
pubkey = (ECPublicKey) other.getPublic();
pubkey = other.getPublic();
}

long elapsed = -System.nanoTime();
Expand All @@ -501,8 +497,8 @@ private void ecdh() throws NoSuchAlgorithmException, InvalidAlgorithmParameterEx
}
ka = kaIdent.getInstance(lib.getProvider());

String pub = ByteUtil.bytesToHex(ECUtil.toX962Uncompressed(pubkey.getW(), pubkey.getParams()), false);
String priv = ByteUtil.bytesToHex(privkey.getS().toByteArray(), false);
String pub = ByteUtil.bytesToHex(ECUtil.pubkeyToBytes(pubkey), false);
String priv = ByteUtil.bytesToHex(ECUtil.privkeyToBytes(privkey), false);
String dh = ByteUtil.bytesToHex(result, false);
out.printf("%d;%d;%s;%s;%s%n", i, elapsed, pub, priv, dh);
}
Expand Down Expand Up @@ -605,17 +601,17 @@ private void ecdsa() throws NoSuchAlgorithmException, InvalidAlgorithmParameterE
String hashAlgoOut = sigIdent.getHashAlgo() != null ? String.format("[%s]", sigIdent.getHashAlgo()) : "";
out.printf("index;signTime[%s];verifyTime[%s];data;pubW;privS;signature%s;nonce;verified%n", timeUnit, timeUnit, hashAlgoOut);

ECPrivateKey privkey = (ECPrivateKey) ECUtil.loadKey(EC_Consts.PARAMETER_S, cli.getOptionValue("ecdsa.named-private"), cli.getOptionValue("ecdsa.private"), spec);
ECPublicKey pubkey = (ECPublicKey) ECUtil.loadKey(EC_Consts.PARAMETER_W, cli.getOptionValue("ecdsa.named-public"), cli.getOptionValue("ecdsa.public"), spec);
PrivateKey privkey = (ECPrivateKey) ECUtil.loadKey(EC_Consts.PARAMETER_S, cli.getOptionValue("ecdsa.named-private"), cli.getOptionValue("ecdsa.private"), spec);
PublicKey pubkey = (ECPublicKey) ECUtil.loadKey(EC_Consts.PARAMETER_W, cli.getOptionValue("ecdsa.named-public"), cli.getOptionValue("ecdsa.public"), spec);

KeyPair one;
if (cli.hasOption("ecdsa.fixed")) {
one = kpg.genKeyPair();
if (!cli.hasOption("ecdsa.named-private")) {
privkey = (ECPrivateKey) one.getPrivate();
privkey = one.getPrivate();
}
if (!cli.hasOption("ecdsa.named-public")) {
pubkey = (ECPublicKey) one.getPublic();
pubkey = one.getPublic();
}
}

Expand All @@ -626,10 +622,10 @@ private void ecdsa() throws NoSuchAlgorithmException, InvalidAlgorithmParameterE
one = kpg.genKeyPair();

if (!cli.hasOption("ecdsa.named-private")) {
privkey = (ECPrivateKey) one.getPrivate();
privkey = one.getPrivate();
}
if (!cli.hasOption("ecdsa.named-public")) {
pubkey = (ECPublicKey) one.getPublic();
pubkey = one.getPublic();
}
}

Expand All @@ -653,27 +649,31 @@ private void ecdsa() throws NoSuchAlgorithmException, InvalidAlgorithmParameterE
verifyTime = lib.getLastNativeTiming();
}

String pub = ByteUtil.bytesToHex(ECUtil.toX962Uncompressed(pubkey.getW(), pubkey.getParams()), false);
String priv = ByteUtil.bytesToHex(privkey.getS().toByteArray(), false);
String pub = ByteUtil.bytesToHex(ECUtil.pubkeyToBytes(pubkey), false);
String priv = ByteUtil.bytesToHex(ECUtil.privkeyToBytes(privkey), false);
String sign = ByteUtil.bytesToHex(signature, false);
String k = "";
ECParameterSpec kSpec = spec;
if (kSpec == null) {
kSpec = privkey.getParams();
}
if (kSpec != null) {
// Parse the types out of SignatureIdent.
String hashAlgo = sigIdent.getHashAlgo();
String sigType = sigIdent.getSigType();
if (sigType == null) {
sigType = sigIdent.toString();
if (privkey instanceof ECPrivateKey) {
ECPrivateKey ecPrivateKey = (ECPrivateKey) privkey;
ECParameterSpec kSpec = spec;
if (kSpec == null) {
kSpec = ecPrivateKey.getParams();
}
if (kSpec != null) {
// Parse the types out of SignatureIdent.
String hashAlgo = sigIdent.getHashAlgo();
String sigType = sigIdent.getSigType();
if (sigType == null) {
sigType = sigIdent.toString();
}

BigInteger kValue = ECUtil.recoverSignatureNonce(signature, data, privkey.getS(), kSpec, hashAlgo, sigType);
if (kValue != null) {
k = ByteUtil.bytesToHex(kValue.toByteArray(), false);
BigInteger kValue = ECUtil.recoverSignatureNonce(signature, data, ecPrivateKey.getS(), kSpec, hashAlgo, sigType);
if (kValue != null) {
k = ByteUtil.bytesToHex(kValue.toByteArray(), false);
}
}
}

out.printf("%d;%d;%d;%s;%s;%s;%s;%s;%d%n", i, signTime, verifyTime, dataString, pub, priv, sign, k, verified ? 1 : 0);
}

Expand Down Expand Up @@ -744,11 +744,17 @@ private void generate() throws NoSuchAlgorithmException, InvalidAlgorithmParamet
if (!lib.getNativeTimingSupport().isEmpty()) {
elapsed = lib.getLastNativeTiming();
}
ECPublicKey publicKey = (ECPublicKey) kp.getPublic();
ECPrivateKey privateKey = (ECPrivateKey) kp.getPrivate();

String pub = ByteUtil.bytesToHex(ECUtil.toX962Uncompressed(publicKey.getW(), publicKey.getParams()), false);
String priv = ByteUtil.bytesToHex(privateKey.getS().toByteArray(), false);
PublicKey pubkey = kp.getPublic();
PrivateKey privkey = kp.getPrivate();
byte[] pubBytes = ECUtil.pubkeyToBytes(pubkey);
byte[] privBytes = ECUtil.privkeyToBytes(privkey);
String pub = ByteUtil.bytesToHex(pubBytes, false);
String priv = ByteUtil.bytesToHex(privBytes, false);
if (pubBytes == null || privBytes == null) {
System.err.println(pubkey.getClass().getCanonicalName());
System.err.println(privkey.getClass().getCanonicalName());
break;
}
out.printf("%d;%d;%s;%s%n", i, elapsed, pub, priv);
}

Expand All @@ -763,7 +769,6 @@ private void generate() throws NoSuchAlgorithmException, InvalidAlgorithmParamet
private void test() throws TestException, ParserConfigurationException, FileNotFoundException {
TestWriter writer = new FileTestWriter(cli.getOptionValue("test.format", "text"), !cli.hasOption("test.quiet"), cli.getOptionValues("test.output"));
StandaloneTestSuite suite;

switch (cli.getArg(0).toLowerCase()) {
case "test-vectors":
suite = new StandaloneTestVectorSuite(writer, cfg, cli);
Expand Down Expand Up @@ -798,9 +803,9 @@ private void test() throws TestException, ParserConfigurationException, FileNotF
case "performance":
suite = new StandalonePerformanceSuite(writer, cfg, cli);
break;
case "default":
default:
suite = new StandaloneDefaultSuite(writer, cfg, cli);
break;
}

suite.run();
Expand Down
Loading