Skip to content
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
71 changes: 71 additions & 0 deletions kse/src/main/java/org/kse/crypto/ecc/XDHCurves.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/*
* Copyright 2004 - 2013 Wayne Grant
* 2013 - 2026 Kai Kramer
*
* This file is part of KeyStore Explorer.
*
* KeyStore Explorer 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, either version 3 of the License, or
* (at your option) any later version.
*
* KeyStore Explorer 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 KeyStore Explorer. If not, see <http://www.gnu.org/licenses/>.
*/
package org.kse.crypto.ecc;

import java.security.InvalidParameterException;
import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;

import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.edec.EdECObjectIdentifiers;

/**
* Provide something like BC's SECNamedCurves/NISTNamedCurves/X962NamedCurves for X25519 and X448.
*/
public enum XDHCurves {

X25519("X25519", EdECObjectIdentifiers.id_X25519, 256),
X448("X448", EdECObjectIdentifiers.id_X448, 456);

private String jce;
private ASN1ObjectIdentifier oid;
private int bitLength;

XDHCurves(String jce, ASN1ObjectIdentifier oid, int bitLength) {
this.jce = jce;
this.oid = oid;
this.bitLength = bitLength;
}

public ASN1ObjectIdentifier oid() {
return oid;
}

public String jce() {
return jce;
}

public int bitLength() {
return bitLength;
}

/**
* @return an enumeration containing the jce strings for curves contained in this structure.
*/
public static Enumeration<String> getNames() {
return Collections.enumeration(Arrays.asList(X25519.jce, X448.jce));
}

public static XDHCurves resolve(ASN1ObjectIdentifier oid) {
return Arrays.stream(XDHCurves.values()).filter(e -> e.oid.equals(oid)).findFirst()
.orElseThrow(() -> new InvalidParameterException("Unknown OID: " + oid.getId()));
}
}
67 changes: 46 additions & 21 deletions kse/src/main/java/org/kse/crypto/jwk/JwkUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
import java.security.spec.InvalidKeySpecException;
import java.security.spec.NamedParameterSpec;
import java.security.spec.RSAPublicKeySpec;
import java.security.spec.XECPrivateKeySpec;
import java.text.MessageFormat;
import java.text.ParseException;
import java.util.ArrayList;
Expand All @@ -52,9 +53,10 @@
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.jcajce.interfaces.EdDSAKey;
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 org.bouncycastle.jce.ECNamedCurveTable;
import org.bouncycastle.jce.spec.ECParameterSpec;
import org.bouncycastle.openssl.PEMException;
Expand Down Expand Up @@ -120,8 +122,8 @@ public static String get(PublicKey publicKey, String alias) {
jwkExporter = new RSAKeyExporter(publicKey);
} else if (publicKey instanceof ECPublicKey) {
jwkExporter = new ECKeyExporter(publicKey);
} else if (publicKey instanceof EdDSAPublicKey) {
jwkExporter = new EdDSAKeyExporter(publicKey);
} else if (publicKey instanceof EdDSAPublicKey || publicKey instanceof XDHPublicKey) {
jwkExporter = new EdKeyExporter(publicKey);
} else {
// Don't bother to translate this exception. This condition will never be encountered
// since DExportPrivateKeyType.isJwkSupported and JwkPublicKeyExporter.isPublicKeyTypeExportable
Expand Down Expand Up @@ -160,8 +162,8 @@ public static String get(PrivateKey privateKey, String alias, X509Certificate[]
jwkExporter = new RSAKeyExporter(privateKey);
} else if (privateKey instanceof ECPrivateKey) {
jwkExporter = new ECKeyExporter(privateKey);
} else if (privateKey instanceof EdDSAPrivateKey) {
jwkExporter = new EdDSAKeyExporter(privateKey);
} else if (privateKey instanceof EdDSAPrivateKey || privateKey instanceof XDHPrivateKey) {
jwkExporter = new EdKeyExporter(privateKey);
} else {
// Don't bother to translate this exception. This condition will never be encountered
// since DExportPrivateKeyType.isJwkSupported and JwkPublicKeyExporter.isPublicKeyTypeExportable
Expand Down Expand Up @@ -332,10 +334,20 @@ public static PrivateKey toPrivateKey(JWK jwkKey) throws CryptoException {
OctetKeyPair okp = jwkKey.toOctetKeyPair();
if (okp.getD() != null) {
Curve curve = okp.getCurve();
privateKey = KeyFactory.getInstance("EdDSA").generatePrivate(
new EdECPrivateKeySpec(new NamedParameterSpec(curve.getStdName()), okp.getDecodedD()));
// KSE uses the BC interfaces for EdDSA keys so convert BC
privateKey = Pkcs8Util.convert(privateKey);
if (Curve.Ed25519.equals(curve) || Curve.Ed448.equals(curve)) {
privateKey = KeyFactory.getInstance("EdDSA").generatePrivate(
new EdECPrivateKeySpec(new NamedParameterSpec(curve.getStdName()), okp.getDecodedD()));
// KSE uses the BC interfaces for EdDSA keys so convert BC
privateKey = Pkcs8Util.convert(privateKey);
} else if (Curve.X25519.equals(curve) || Curve.X448.equals(curve)) {
privateKey = KeyFactory.getInstance("XDH").generatePrivate(
new XECPrivateKeySpec(new NamedParameterSpec(curve.getStdName()), okp.getDecodedD()));
// KSE uses the BC interfaces for EdDSA keys so convert BC
privateKey = Pkcs8Util.convert(privateKey);
} else {
throw new CryptoException(MessageFormat
.format(res.getString("UnsupportedJwkKeyType.exception.message"), jwkKey.getKeyType()));
}
} else {
privateKey = null;
}
Expand Down Expand Up @@ -388,6 +400,8 @@ private static PublicKey toPublicKey(OctetKeyPair okp) throws CryptoException {
ASN1ObjectIdentifier curveIdentifier =
Curve.Ed25519.equals(crv) ? EdECObjectIdentifiers.id_Ed25519 :
Curve.Ed448.equals(crv) ? EdECObjectIdentifiers.id_Ed448 :
Curve.X25519.equals(crv) ? EdECObjectIdentifiers.id_X25519 :
Curve.X448.equals(crv) ? EdECObjectIdentifiers.id_X448 :
null;

if (curveIdentifier == null) {
Expand All @@ -413,6 +427,8 @@ public static boolean isPublicKeyTypeExportable(PublicKey publicKey) {
switch (KeyPairUtil.getKeyPairType(publicKey)) {
case ED448:
case ED25519:
case X25519:
case X448:
case RSA:
return true;
case EC:
Expand Down Expand Up @@ -461,33 +477,42 @@ public static EncryptionType getEncryptionType(byte[] jwkData) {
return null;
}

private static class EdDSAKeyExporter implements JwkExporter {
private static class EdKeyExporter implements JwkExporter {
protected static final Map<String, Curve> SUPPORTED_CURVES =
Map.of(
KeyPairType.ED25519.jce(), Curve.Ed25519,
KeyPairType.ED448.jce(), Curve.Ed448
KeyPairType.ED448.jce(), Curve.Ed448,
KeyPairType.X25519.jce(), Curve.X25519,
KeyPairType.X448.jce(), Curve.X448
);

private final EdDSAPrivateKey privateKey;
private final EdDSAPublicKey publicKey;
private final PrivateKey privateKey;
private final PublicKey publicKey;

private EdDSAKeyExporter(PrivateKey privateKey) {
this.privateKey = (EdDSAPrivateKey) privateKey;
this.publicKey = this.privateKey.getPublicKey();
private EdKeyExporter(PrivateKey privateKey) {
this.privateKey = privateKey;
if (privateKey instanceof EdDSAPrivateKey) {
publicKey = ((EdDSAPrivateKey) privateKey).getPublicKey();
} else if (privateKey instanceof XDHPrivateKey) {
publicKey = ((XDHPrivateKey) privateKey).getPublicKey();
} else {
// Should never get here, but in case, throw an exception
throw JwkExporterException.notSupported(privateKey.getAlgorithm(), null);
}
}

private EdDSAKeyExporter(PublicKey publicKey) {
private EdKeyExporter(PublicKey publicKey) {
this.privateKey = null;
this.publicKey = (EdDSAPublicKey) publicKey;
this.publicKey = publicKey;
}

private Curve getCurve(EdDSAKey bcEdDSAPublicKey) {
return SUPPORTED_CURVES.get(bcEdDSAPublicKey.getAlgorithm());
private Curve getCurve() {
return SUPPORTED_CURVES.get(publicKey.getAlgorithm());
}

@Override
public String export(String alias, X509Certificate[] chain) {
Curve curve = getCurve(publicKey);
Curve curve = getCurve();
try {
SubjectPublicKeyInfo subjectPublicKeyInfo = SubjectPublicKeyInfo.getInstance(
publicKey.getEncoded());
Expand Down
15 changes: 14 additions & 1 deletion kse/src/main/java/org/kse/crypto/keypair/KeyPairType.java
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ public enum KeyPairType {
ECGOST3410("ECGOST3410", "1.2.643.2.2.19", 256, 256, 0),
ECGOST3410_2012("ECGOST3410-2012", "", 256, 512, 256), // There are two OIDs: One for 256-bit, one for 512-bit.

X25519("X25519", "1.3.101.110", 256, 256, 0),
X448("X448", "1.3.101.111", 456, 456, 0),

MLDSA44("ML-DSA-44", "2.16.840.1.101.3.4.3.17", 10_496, 10_496, 0),
MLDSA65("ML-DSA-65", "2.16.840.1.101.3.4.3.18", 15_616, 15_616, 0),
MLDSA87("ML-DSA-87", "2.16.840.1.101.3.4.3.19", 20_736, 20_736, 0),
Expand All @@ -61,7 +64,8 @@ public enum KeyPairType {
/**
* Set of all EC key pair types (EC, ECDSA, ED25519, ED448)
*/
public static final Set<KeyPairType> EC_TYPES_SET = EnumSet.of(EC, ECDSA, ED25519, ED448, ECGOST3410, ECGOST3410_2012);
public static final Set<KeyPairType> EC_TYPES_SET = EnumSet.of(EC, ECDSA, ED25519, ED448, X25519, X448, ECGOST3410,
ECGOST3410_2012);

/**
* Set of all ML-DSA key pair types
Expand Down Expand Up @@ -183,6 +187,15 @@ public static boolean isSlhDsa(KeyPairType keyPairType) {
return SLHDSA_TYPES_SET.contains(keyPairType);
}

/**
*
* @param keyPairType The KeyPairType to check.
* @return True if keyPairType is a XDH key type.
*/
public static boolean isXDH(KeyPairType keyPairType) {
return X25519 == keyPairType || X448 == keyPairType;
}

/**
* Gets the ECGOST key pair type for the given curve name.
*
Expand Down
14 changes: 9 additions & 5 deletions kse/src/main/java/org/kse/crypto/keypair/KeyPairUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
import static org.kse.crypto.KeyType.ASYMMETRIC;
import static org.kse.crypto.ecc.EdDSACurves.ED25519;
import static org.kse.crypto.ecc.EdDSACurves.ED448;
import static org.kse.crypto.ecc.XDHCurves.X25519;
import static org.kse.crypto.ecc.XDHCurves.X448;
import static org.kse.crypto.keypair.KeyPairType.DSA;
import static org.kse.crypto.keypair.KeyPairType.EC;
import static org.kse.crypto.keypair.KeyPairType.ECDSA;
Expand Down Expand Up @@ -73,6 +75,7 @@
import org.kse.crypto.ecc.CurveSet;
import org.kse.crypto.ecc.EccUtil;
import org.kse.crypto.ecc.EdDSACurves;
import org.kse.crypto.ecc.XDHCurves;
import org.kse.utilities.rng.RNG;

/**
Expand Down Expand Up @@ -139,7 +142,8 @@ public static KeyPair generateECKeyPair(String curveName, Provider provider) thr
provider = KSE.BC;
}

if (EdDSACurves.ED25519.jce().equals(curveName) || EdDSACurves.ED448.jce().equals(curveName)) {
if (EdDSACurves.ED25519.jce().equals(curveName) || EdDSACurves.ED448.jce().equals(curveName)
|| XDHCurves.X25519.jce().equals(curveName) || XDHCurves.X448.jce().equals(curveName)) {
keyPairGen = KeyPairGenerator.getInstance(curveName, provider);
keyPairGen.initialize(new ECGenParameterSpec(curveName), RNG.newInstanceForLongLivedSecrets());
} else if (CurveSet.ECGOST.getAllCurveNames().contains(curveName)) {
Expand Down Expand Up @@ -257,9 +261,9 @@ public static KeyInfo getKeyInfo(PublicKey publicKey) throws CryptoException {
ECPublicKey pubk = (ECPublicKey) publicKey;
int size = pubk.getParams().getOrder().bitLength();
return new KeyInfo(ASYMMETRIC, algorithm, size, EccUtil.getNamedCurve(publicKey));
} else if (ED25519.jce().equalsIgnoreCase(algorithm)) {
} else if (ED25519.jce().equalsIgnoreCase(algorithm) || X25519.jce().equalsIgnoreCase(algorithm)) {
return new KeyInfo(ASYMMETRIC, algorithm, ED25519.bitLength());
} else if (ED448.jce().equalsIgnoreCase(algorithm)) {
} else if (ED448.jce().equalsIgnoreCase(algorithm) || X448.jce().equalsIgnoreCase(algorithm)) {
return new KeyInfo(ASYMMETRIC, algorithm, ED448.bitLength());
} else if (ECGOST3410.jce().equalsIgnoreCase(algorithm) || ECGOST3410_2012.jce().equalsIgnoreCase(algorithm)) {
// ECGOST parameters are ASN1Sequence so use ECNamedCurveSpec to get the curve name
Expand Down Expand Up @@ -316,9 +320,9 @@ public static KeyInfo getKeyInfo(PrivateKey privateKey) throws CryptoException {
ECParameterSpec spec = privk.getParams();
int size = spec.getOrder().bitLength();
return new KeyInfo(ASYMMETRIC, algorithm, size, EccUtil.getNamedCurve(privateKey));
} else if (ED25519.jce().equalsIgnoreCase(algorithm)) {
} else if (ED25519.jce().equalsIgnoreCase(algorithm) || X25519.jce().equalsIgnoreCase(algorithm)) {
return new KeyInfo(ASYMMETRIC, algorithm, ED25519.bitLength());
} else if (ED448.jce().equalsIgnoreCase(algorithm)) {
} else if (ED448.jce().equalsIgnoreCase(algorithm) || X448.jce().equalsIgnoreCase(algorithm)) {
return new KeyInfo(ASYMMETRIC, algorithm, ED448.bitLength());
} else if (isMlDSA(getKeyPairType(privateKey)) || isMlKEM(getKeyPairType(privateKey))
|| isSlhDsa(getKeyPairType(privateKey))) {
Expand Down
3 changes: 2 additions & 1 deletion kse/src/main/java/org/kse/crypto/privatekey/Pkcs8Util.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.interfaces.EdECPrivateKey;
import java.security.interfaces.XECPrivateKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.text.MessageFormat;
Expand Down Expand Up @@ -489,7 +490,7 @@ public static PrivateKey convert(PrivateKey privateKey) throws CryptoException {
// to the NamedPKCS8Key class for testing instanceof, but this hack is more
// efficient at the cost of correctness.
if ("sun.security.pkcs.NamedPKCS8Key".equals(privateKey.getClass().getName())
|| privateKey instanceof EdECPrivateKey) {
|| privateKey instanceof EdECPrivateKey || privateKey instanceof XECPrivateKey) {

try {
// Shortest way to convert to a BC EdDSA key. Doesn't require importing any
Expand Down
5 changes: 3 additions & 2 deletions kse/src/main/java/org/kse/gui/KseFrame.java
Original file line number Diff line number Diff line change
Expand Up @@ -2699,7 +2699,8 @@ private void showKeyStoreContextMenu(JTable jtKeyStore, int row, int x, int y) {
// Not possible to sign if there is a keystore exception.
}

boolean isSignAvailable = publicKey != null && !KeyPairType.isMlKEM(KeyPairUtil.getKeyPairType(publicKey));
boolean isSignAvailable = publicKey != null && !KeyPairType.isMlKEM(KeyPairUtil.getKeyPairType(publicKey))
&& !KeyPairType.isXDH(KeyPairUtil.getKeyPairType(publicKey));
if (isSignAvailable) {
jmiKeyPairGenerateCsr.setEnabled(true);
jmiKeyPairGenerateCsr.setToolTipText(null);
Expand All @@ -2713,7 +2714,7 @@ private void showKeyStoreContextMenu(JTable jtKeyStore, int row, int x, int y) {
signJwtAction.setEnabled(signJwtAction.isKeySupported(publicKey));
jmiKeyPairSignJwt.setToolTipText(signJwtAction.getToolTip());
} else {
// Cannot request PKCS#10 CSR for ML-KEM since a signature is required for proof of possession
// Cannot request PKCS#10 CSR for ML-KEM or XDH since a signature is required for proof of possession
// Generating a CSR can be re-enabled once CRMF is supported
jmiKeyPairGenerateCsr.setEnabled(false);
jmiKeyPairGenerateCsr.setToolTipText(res.getString("KseFrame.jmiKeyPairGenerateCsr.tooltip"));
Expand Down
Loading
Loading