diff --git a/kse/src/main/java/org/kse/crypto/ecc/XDHCurves.java b/kse/src/main/java/org/kse/crypto/ecc/XDHCurves.java
new file mode 100644
index 000000000..e55ffe8e9
--- /dev/null
+++ b/kse/src/main/java/org/kse/crypto/ecc/XDHCurves.java
@@ -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 .
+ */
+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 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()));
+ }
+}
diff --git a/kse/src/main/java/org/kse/crypto/jwk/JwkUtil.java b/kse/src/main/java/org/kse/crypto/jwk/JwkUtil.java
index f424be15e..7ca3609af 100644
--- a/kse/src/main/java/org/kse/crypto/jwk/JwkUtil.java
+++ b/kse/src/main/java/org/kse/crypto/jwk/JwkUtil.java
@@ -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;
@@ -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;
@@ -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
@@ -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
@@ -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;
}
@@ -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) {
@@ -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:
@@ -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 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());
diff --git a/kse/src/main/java/org/kse/crypto/keypair/KeyPairType.java b/kse/src/main/java/org/kse/crypto/keypair/KeyPairType.java
index 59fbe01cf..eb572c0ac 100644
--- a/kse/src/main/java/org/kse/crypto/keypair/KeyPairType.java
+++ b/kse/src/main/java/org/kse/crypto/keypair/KeyPairType.java
@@ -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),
@@ -61,7 +64,8 @@ public enum KeyPairType {
/**
* Set of all EC key pair types (EC, ECDSA, ED25519, ED448)
*/
- public static final Set EC_TYPES_SET = EnumSet.of(EC, ECDSA, ED25519, ED448, ECGOST3410, ECGOST3410_2012);
+ public static final Set EC_TYPES_SET = EnumSet.of(EC, ECDSA, ED25519, ED448, X25519, X448, ECGOST3410,
+ ECGOST3410_2012);
/**
* Set of all ML-DSA key pair types
@@ -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.
*
diff --git a/kse/src/main/java/org/kse/crypto/keypair/KeyPairUtil.java b/kse/src/main/java/org/kse/crypto/keypair/KeyPairUtil.java
index b76160aae..c445de9a2 100644
--- a/kse/src/main/java/org/kse/crypto/keypair/KeyPairUtil.java
+++ b/kse/src/main/java/org/kse/crypto/keypair/KeyPairUtil.java
@@ -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;
@@ -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;
/**
@@ -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)) {
@@ -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
@@ -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))) {
diff --git a/kse/src/main/java/org/kse/crypto/privatekey/Pkcs8Util.java b/kse/src/main/java/org/kse/crypto/privatekey/Pkcs8Util.java
index 4c54394ee..d8618d449 100644
--- a/kse/src/main/java/org/kse/crypto/privatekey/Pkcs8Util.java
+++ b/kse/src/main/java/org/kse/crypto/privatekey/Pkcs8Util.java
@@ -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;
@@ -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
diff --git a/kse/src/main/java/org/kse/gui/KseFrame.java b/kse/src/main/java/org/kse/gui/KseFrame.java
index 3d9589f3e..e9e36a4ff 100644
--- a/kse/src/main/java/org/kse/gui/KseFrame.java
+++ b/kse/src/main/java/org/kse/gui/KseFrame.java
@@ -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);
@@ -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"));
diff --git a/kse/src/main/java/org/kse/gui/dialogs/DGenerateKeyPair.java b/kse/src/main/java/org/kse/gui/dialogs/DGenerateKeyPair.java
index b2412b60c..913a1b341 100644
--- a/kse/src/main/java/org/kse/gui/dialogs/DGenerateKeyPair.java
+++ b/kse/src/main/java/org/kse/gui/dialogs/DGenerateKeyPair.java
@@ -48,6 +48,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.crypto.keypair.KeyPairType;
import org.kse.crypto.keystore.KeyStoreType;
import org.kse.gui.MiGUtil;
@@ -82,6 +83,7 @@ public class DGenerateKeyPair extends JEscDialog {
private JRadioButton jrbEC;
private JRadioButton jrbEdDSA;
+ private JRadioButton jrbXDH;
private JLabel jlECCurveSet;
private JComboBox jcbECCurveSet;
private JLabel jlECCurve;
@@ -90,6 +92,9 @@ public class DGenerateKeyPair extends JEscDialog {
private JLabel jlEdDSACurve;
private JComboBox jcbEdDSACurve;
+ private JLabel jlXDHCurve;
+ private JComboBox jcbXDHCurve;
+
private MLDSAKeySelector mldsaKeySelector;
private MLKEMKeySelector mlkemKeySelector;
private SlhDsaKeySelector slhDsaKeySelector;
@@ -169,11 +174,20 @@ private void initComponents() {
PlatformUtil.setMnemonic(jrbEdDSA, res.getString("DGenerateKeyPair.jrbEdDSA.mnemonic").charAt(0));
jrbEdDSA.setToolTipText(res.getString("DGenerateKeyPair.jrbEdDSA.tooltip"));
+ jrbXDH = new JRadioButton(res.getString("DGenerateKeyPair.jrbXDH.text"), false);
+ PlatformUtil.setMnemonic(jrbXDH, res.getString("DGenerateKeyPair.jrbXDH.mnemonic").charAt(0));
+ jrbXDH.setToolTipText(res.getString("DGenerateKeyPair.jrbXDH.tooltip"));
+ if (isSelfSigned) {
+ jrbXDH.setEnabled(false);
+ jrbXDH.setToolTipText(res.getString("DGenerateKeyPair.jrbXDH.na.tooltip"));
+ }
+
ButtonGroup buttonGroup = new ButtonGroup();
buttonGroup.add(jrbRSA);
buttonGroup.add(jrbDSA);
buttonGroup.add(jrbEC);
buttonGroup.add(jrbEdDSA);
+ buttonGroup.add(jrbXDH);
jlECCurveSet = new JLabel(res.getString("DGenerateKeyPair.jlECCurveSet.text"));
jlECCurveSet.setToolTipText(res.getString("DGenerateKeyPair.jlECCurveSet.tooltip"));
@@ -196,7 +210,7 @@ private void initComponents() {
jlEdDSACurve = new JLabel(res.getString("DGenerateKeyPair.jlEdDSACurve.text"));
jlEdDSACurve.setDisplayedMnemonic(res.getString("DGenerateKeyPair.jlEdDSACurve.mnemonic").charAt(0));
- String[] edDsaCurveNames = Collections.list(EdDSACurves.getNames()).toArray(new String[0]);
+ String[] edDsaCurveNames = Collections.list(EdDSACurves.getNames()).toArray(String[]::new);
for (int i = 0; i < edDsaCurveNames.length; i++) {
KeyPairType type = KeyPairType.resolveJce(edDsaCurveNames[i]);
edDsaCurveNames[i] = DialogHelper.formatNameWithSize(edDsaCurveNames[i], type.maxSize());
@@ -205,6 +219,18 @@ private void initComponents() {
jcbEdDSACurve.setToolTipText(res.getString("DGenerateKeyPair.jcbEdDSACurve.tooltip"));
jlEdDSACurve.setLabelFor(jcbEdDSACurve);
+ jlXDHCurve = new JLabel(res.getString("DGenerateKeyPair.jlXDHCurve.text"));
+ jlXDHCurve.setDisplayedMnemonic(res.getString("DGenerateKeyPair.jlXDHCurve.mnemonic").charAt(0));
+
+ String[] xdhCurveNames = Collections.list(XDHCurves.getNames()).toArray(String[]::new);
+ for (int i = 0; i < xdhCurveNames.length; i++) {
+ KeyPairType type = KeyPairType.resolveJce(xdhCurveNames[i]);
+ xdhCurveNames[i] = DialogHelper.formatNameWithSize(xdhCurveNames[i], type.maxSize());
+ }
+ jcbXDHCurve = new JComboBox<>(xdhCurveNames);
+ jcbXDHCurve.setToolTipText(res.getString("DGenerateKeyPair.jcbXDHCurve.tooltip"));
+ jlXDHCurve.setLabelFor(jcbXDHCurve);
+
jbCancel = new JButton(res.getString("DGenerateKeyPair.jbCancel.text"));
jbCancel.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW)
.put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), CANCEL_KEY);
@@ -275,6 +301,11 @@ private void initComponents() {
enableDisableElements();
}
});
+ jrbXDH.addItemListener(evt -> {
+ if (jrbXDH.isSelected()) {
+ enableDisableElements();
+ }
+ });
enableDisableElements();
@@ -339,6 +370,12 @@ private void focusKeyPair(JTabbedPane jtpAlgorithms) {
keyPairType == KeyPairType.ED25519 ? EdDSACurves.ED25519.jce() : EdDSACurves.ED448.jce(),
keyPairType.maxSize()));
jtpAlgorithms.setSelectedIndex(1);
+ } else if (keyPairType == KeyPairType.X25519 || keyPairType == KeyPairType.X448) {
+ jrbXDH.setSelected(true);
+ jcbXDHCurve.setSelectedItem(DialogHelper.formatNameWithSize(
+ keyPairType == KeyPairType.X25519 ? XDHCurves.X25519.jce() : XDHCurves.X448.jce(),
+ keyPairType.maxSize()));
+ jtpAlgorithms.setSelectedIndex(1);
} else if (KeyPairType.isMlDSA(keyPairType)) {
mldsaKeySelector.setSelected(true);
jtpAlgorithms.setSelectedIndex(2);
@@ -390,6 +427,9 @@ private JPanel createEcPanel() {
jpEc.add(jrbEdDSA, "");
jpEc.add(jlEdDSACurve, "");
jpEc.add(jcbEdDSACurve, "growx, wrap");
+ jpEc.add(jrbXDH, "");
+ jpEc.add(jlXDHCurve, "");
+ jpEc.add(jcbXDHCurve, "growx, wrap");
return jpEc;
}
@@ -503,6 +543,7 @@ protected void enableDisableElements() {
boolean isEcSelected = jrbEC.isSelected();
boolean isEdDsaSelected = jrbEdDSA.isSelected();
+ boolean isXDHSelected = jrbXDH.isSelected();
jlECCurve.setEnabled(isEcSelected);
jcbECCurve.setEnabled(isEcSelected);
@@ -512,6 +553,9 @@ protected void enableDisableElements() {
jlEdDSACurve.setEnabled(isEdDsaSelected);
jcbEdDSACurve.setEnabled(isEdDsaSelected);
+ jlXDHCurve.setEnabled(isXDHSelected);
+ jcbXDHCurve.setEnabled(isXDHSelected);
+
// Selectors manage their own internal state
mldsaKeySelector.enableDisableElements();
mlkemKeySelector.enableDisableElements();
@@ -610,6 +654,8 @@ public String getCurveSet() {
public String getCurveName() {
if (jrbEdDSA.isSelected()) {
return DialogHelper.extractNameFromFormatted((String) jcbEdDSACurve.getModel().getSelectedItem());
+ } else if (jrbXDH.isSelected()) {
+ return DialogHelper.extractNameFromFormatted((String) jcbXDHCurve.getModel().getSelectedItem());
}
String selectedItem = (String) jcbECCurve.getModel().getSelectedItem();
return extractCurveNameFromFormatted(selectedItem);
@@ -666,6 +712,16 @@ public KeyPairType getKeyPairType() {
}
}
+ if (jrbXDH.isSelected()) {
+ String selectedCurve = DialogHelper
+ .extractNameFromFormatted((String) jcbXDHCurve.getModel().getSelectedItem());
+ if (XDHCurves.X25519.jce().equals(selectedCurve)) {
+ return KeyPairType.X25519;
+ } else {
+ return KeyPairType.X448;
+ }
+ }
+
return KeyPairType.EC;
}
diff --git a/kse/src/main/java/org/kse/gui/dialogs/DViewAsymmetricKeyFields.java b/kse/src/main/java/org/kse/gui/dialogs/DViewAsymmetricKeyFields.java
index 823d0bdaf..fc35bfd8b 100644
--- a/kse/src/main/java/org/kse/gui/dialogs/DViewAsymmetricKeyFields.java
+++ b/kse/src/main/java/org/kse/gui/dialogs/DViewAsymmetricKeyFields.java
@@ -68,6 +68,8 @@
import org.bouncycastle.jcajce.interfaces.MLKEMPublicKey;
import org.bouncycastle.jcajce.interfaces.SLHDSAPrivateKey;
import org.bouncycastle.jcajce.interfaces.SLHDSAPublicKey;
+import org.bouncycastle.jcajce.interfaces.XDHPrivateKey;
+import org.bouncycastle.jcajce.interfaces.XDHPublicKey;
import org.bouncycastle.pqc.crypto.mldsa.MLDSAPrivateKeyParameters;
import org.bouncycastle.pqc.crypto.mldsa.MLDSAPublicKeyParameters;
import org.bouncycastle.pqc.crypto.mlkem.MLKEMPrivateKeyParameters;
@@ -133,9 +135,13 @@ private static String getTitle(Key key) {
} else if (key instanceof ECPrivateKey) {
return MessageFormat.format(res.getString("DViewAsymmetricKeyFields.PrivateKey.title"), "EC");
} else if (key instanceof EdDSAPublicKey) {
- return MessageFormat.format(res.getString("DViewAsymmetricKeyFields.PublicKey.title"), getEdAlg(key));
+ return MessageFormat.format(res.getString("DViewAsymmetricKeyFields.PublicKey.title"), key.getAlgorithm());
} else if (key instanceof EdDSAPrivateKey) {
- return MessageFormat.format(res.getString("DViewAsymmetricKeyFields.PrivateKey.title"), getEdAlg(key));
+ return MessageFormat.format(res.getString("DViewAsymmetricKeyFields.PrivateKey.title"), key.getAlgorithm());
+ } else if (key instanceof XDHPublicKey) {
+ return MessageFormat.format(res.getString("DViewAsymmetricKeyFields.PublicKey.title"), key.getAlgorithm());
+ } else if (key instanceof XDHPrivateKey) {
+ return MessageFormat.format(res.getString("DViewAsymmetricKeyFields.PrivateKey.title"), key.getAlgorithm());
} else if (key instanceof MLDSAPublicKey) {
return MessageFormat.format(res.getString("DViewAsymmetricKeyFields.PublicKey.title"), "ML-DSA");
} else if (key instanceof MLDSAPrivateKey) {
@@ -152,21 +158,6 @@ private static String getTitle(Key key) {
throw new IllegalArgumentException("Unsupported key format for asymmetric fields viewer");
}
- private static String getEdAlg(Key key) {
- // Ed25519 or Ed448?
- String edAlg;
- if (key instanceof EdDSAPublicKey) {
- EdDSAPublicKey bcEdDSAPublicKey = (EdDSAPublicKey) key;
- edAlg = bcEdDSAPublicKey.getAlgorithm(); // Ed25519 or Ed448
- } else if (key instanceof EdDSAPrivateKey) {
- EdDSAPrivateKey edPrivateKey = (EdDSAPrivateKey) key;
- edAlg = edPrivateKey.getAlgorithm();
- } else {
- throw new IllegalArgumentException("Unsupported key format for asymmetric fields viewer");
- }
- return edAlg;
- }
-
private void initFields() throws IOException {
jlFields = new JLabel(res.getString("DViewAsymmetricKeyFields.jlFields.text"));
@@ -241,9 +232,9 @@ private void populateFields() throws IOException {
fields = getEcPubFields();
} else if (key instanceof ECPrivateKey) {
fields = getEcPrivateFields();
- } else if (key instanceof EdDSAPublicKey) {
+ } else if (key instanceof EdDSAPublicKey || key instanceof XDHPublicKey) {
fields = getEdPubFields();
- } else if (key instanceof EdECPrivateKey) {
+ } else if (key instanceof EdECPrivateKey || key instanceof XDHPrivateKey) {
fields = getEdPrivateFields();
} else if (key instanceof MLDSAPublicKey) {
fields = getMLDSAPublicFields();
diff --git a/kse/src/main/java/org/kse/gui/dialogs/DViewPrivateKey.java b/kse/src/main/java/org/kse/gui/dialogs/DViewPrivateKey.java
index 168b18ccb..d4aa3f64e 100644
--- a/kse/src/main/java/org/kse/gui/dialogs/DViewPrivateKey.java
+++ b/kse/src/main/java/org/kse/gui/dialogs/DViewPrivateKey.java
@@ -53,6 +53,7 @@
import org.bouncycastle.jcajce.interfaces.MLDSAPrivateKey;
import org.bouncycastle.jcajce.interfaces.MLKEMPrivateKey;
import org.bouncycastle.jcajce.interfaces.SLHDSAPrivateKey;
+import org.bouncycastle.jcajce.interfaces.XDHPrivateKey;
import org.kse.KSE;
import org.kse.crypto.CryptoException;
import org.kse.crypto.KeyInfo;
@@ -316,7 +317,7 @@ private void populateDialog() throws CryptoException {
jbFields.setEnabled((privateKey instanceof RSAPrivateKey) || (privateKey instanceof DSAPrivateKey)
|| (privateKey instanceof ECPrivateKey) || (privateKey instanceof EdECPrivateKey)
|| (privateKey instanceof MLDSAPrivateKey) || (privateKey instanceof MLKEMPrivateKey)
- || (privateKey instanceof SLHDSAPrivateKey));
+ || (privateKey instanceof SLHDSAPrivateKey || privateKey instanceof XDHPrivateKey));
}
private void pemEncodingPressed() {
diff --git a/kse/src/main/java/org/kse/gui/dialogs/DViewPublicKey.java b/kse/src/main/java/org/kse/gui/dialogs/DViewPublicKey.java
index 91171bad6..a3f1a65d8 100644
--- a/kse/src/main/java/org/kse/gui/dialogs/DViewPublicKey.java
+++ b/kse/src/main/java/org/kse/gui/dialogs/DViewPublicKey.java
@@ -58,6 +58,7 @@
import org.bouncycastle.jcajce.interfaces.MLDSAPublicKey;
import org.bouncycastle.jcajce.interfaces.MLKEMPublicKey;
import org.bouncycastle.jcajce.interfaces.SLHDSAPublicKey;
+import org.bouncycastle.jcajce.interfaces.XDHPublicKey;
import org.kse.KSE;
import org.kse.crypto.CryptoException;
import org.kse.crypto.KeyInfo;
@@ -302,7 +303,7 @@ private void populateDialog() throws CryptoException {
jbFields.setEnabled((publicKey instanceof RSAPublicKey) || (publicKey instanceof DSAPublicKey)
|| (publicKey instanceof ECPublicKey) || (publicKey instanceof EdDSAPublicKey)
|| publicKey instanceof MLDSAPublicKey || publicKey instanceof MLKEMPublicKey
- || publicKey instanceof SLHDSAPublicKey);
+ || publicKey instanceof SLHDSAPublicKey || publicKey instanceof XDHPublicKey);
}
private void exportPressed() {
diff --git a/kse/src/main/java/org/kse/gui/dialogs/importexport/DExportPrivateKeyType.java b/kse/src/main/java/org/kse/gui/dialogs/importexport/DExportPrivateKeyType.java
index 7f352c515..a671b3538 100644
--- a/kse/src/main/java/org/kse/gui/dialogs/importexport/DExportPrivateKeyType.java
+++ b/kse/src/main/java/org/kse/gui/dialogs/importexport/DExportPrivateKeyType.java
@@ -94,6 +94,8 @@ private boolean isJwkSupported() throws CryptoException {
switch (keyPairType) {
case ED448:
case ED25519:
+ case X25519:
+ case X448:
case RSA:
return true;
case EC:
diff --git a/kse/src/main/java/org/kse/gui/dialogs/importexport/DImportKeyPair.java b/kse/src/main/java/org/kse/gui/dialogs/importexport/DImportKeyPair.java
index 0109b230a..8099d8f64 100644
--- a/kse/src/main/java/org/kse/gui/dialogs/importexport/DImportKeyPair.java
+++ b/kse/src/main/java/org/kse/gui/dialogs/importexport/DImportKeyPair.java
@@ -895,8 +895,9 @@ private X509Certificate[] loadCertificates(PrivateKey privateKey) {
String certificatePath = jtfCertificatePath.getText().trim();
if (certificatePath.isEmpty()) {
- // ML-KEM certs cannot be self-signed -- so a cert cannot be generated
- boolean isSelfSignPossible = privateKey != null && !KeyPairType.isMlKEM(KeyPairUtil.getKeyPairType(privateKey));
+ // ML-KEM and XDH certs cannot be self-signed -- so a cert cannot be generated
+ boolean isSelfSignPossible = privateKey != null && !KeyPairType.isMlKEM(KeyPairUtil.getKeyPairType(privateKey))
+ && !KeyPairType.isXDH(KeyPairUtil.getKeyPairType(privateKey));
if (!isSelfSignPossible) {
JOptionPane.showMessageDialog(this, res.getString("DImportKeyPair.CertificateRequired.message"),
getTitle(), JOptionPane.WARNING_MESSAGE);
diff --git a/kse/src/main/resources/org/kse/gui/dialogs/resources.properties b/kse/src/main/resources/org/kse/gui/dialogs/resources.properties
index 2fde723bb..ce7530c1c 100644
--- a/kse/src/main/resources/org/kse/gui/dialogs/resources.properties
+++ b/kse/src/main/resources/org/kse/gui/dialogs/resources.properties
@@ -103,13 +103,19 @@ DGenerateKeyPair.jbCancel.text=Cancel
DGenerateKeyPair.jbOK.text=OK
DGenerateKeyPair.jcbECCurve.tooltip=A named curve (standardized domain parameters defining an elliptic curve)
DGenerateKeyPair.jcbECCurveSet.tooltip=Set of named curves defined in standard documents
+DGenerateKeyPair.jcbEdDSACurve.tooltip=Edwards-curve variant
+DGenerateKeyPair.jcbXDHCurve.tooltip=Edwards-curve variant
DGenerateKeyPair.jlECCurve.text=Named Curve:
DGenerateKeyPair.jlECCurve.tooltip=A named curve (standardized domain parameters defining an elliptic curve)
DGenerateKeyPair.jlECCurve.mnemonic=N
DGenerateKeyPair.jlECCurveSet.text=Set:
DGenerateKeyPair.jlECCurveSet.tooltip=Set of named curves defined in standard documents
DGenerateKeyPair.jlECCurveSet.mnemonic=t
+DGenerateKeyPair.jlEdDSACurve.text=Named Curve:
+DGenerateKeyPair.jlEdDSACurve.mnemonic=a
DGenerateKeyPair.jlKeySize.text=Key Size:
+DGenerateKeyPair.jlXDHCurve.text=Named Curve:
+DGenerateKeyPair.jlXDHCurve.mnemonic=m
DGenerateKeyPair.jpContent.text=Algorithm Selection
DGenerateKeyPair.jrbDSA.mnemonic=D
DGenerateKeyPair.jrbDSA.text=DSA
@@ -117,12 +123,17 @@ DGenerateKeyPair.jrbDSA.tooltip=DSA (Digital Signature Algorithm) key pair
DGenerateKeyPair.jrbEC.mnemonic=C
DGenerateKeyPair.jrbEC.text=EC/ECDSA
DGenerateKeyPair.jrbEC.tooltip=ECC (Elliptic Curve Cryptography) key pair
+DGenerateKeyPair.jrbEdDSA.mnemonic=d
+DGenerateKeyPair.jrbEdDSA.text=EdDSA
+DGenerateKeyPair.jrbEdDSA.tooltip=Edwards-curve Digital Signature Algorithm
DGenerateKeyPair.jrbRSA.mnemonic=R
DGenerateKeyPair.jrbRSA.text=RSA
DGenerateKeyPair.jrbRSA.tooltip=RSA (Rivest, Shamir, Adleman) key pair
+DGenerateKeyPair.jrbXDH.mnemonic=X
+DGenerateKeyPair.jrbXDH.text=XDH
+DGenerateKeyPair.jrbXDH.tooltip=Edwards-curve Key Exchange Algorithm
+DGenerateKeyPair.jrbXDH.na.tooltip=Edwards-curve Key Exchange Algorithm is only available when using Sign New Key Pair.
DGenerateKeyPair.jsKeySize.tooltip=Key size in bits
-DGenerateKeyPair.jlEdDSACurve.text=Named Curve:
-DGenerateKeyPair.jlEdDSACurve.mnemonic=a
DGenerateKeyPair.Tab.Standard=Standard
DGenerateKeyPair.Tab.Standard.mnemonic=S
DGenerateKeyPair.Tab.EC=Elliptic Curves
@@ -133,10 +144,6 @@ DGenerateKeyPair.Separator.Standard=Standard Algorithms
DGenerateKeyPair.Separator.EC=Elliptic Curve Algorithms
DGenerateKeyPair.Separator.PQC=Post-Quantum Algorithms
DGenerateKeyPair.Separator.EdwardsCurve=Edwards-curve
-DGenerateKeyPair.jrbEdDSA.mnemonic=d
-DGenerateKeyPair.jrbEdDSA.text=EdDSA
-DGenerateKeyPair.jrbEdDSA.tooltip=Edwards-curve Digital Signature Algorithm
-DGenerateKeyPair.jcbEdDSACurve.tooltip=Edwards-curve variant
DGenerateKeyPair.jrbSizeManual.text=Size
DGenerateKeyPair.jrbSizeManual.mnemonic=z
DGenerateKeyPair.Size.2048=2048 bits
diff --git a/kse/src/test/java/org/kse/crypto/KeyTestsBase.java b/kse/src/test/java/org/kse/crypto/KeyTestsBase.java
index 25059f91a..0234f9791 100644
--- a/kse/src/test/java/org/kse/crypto/KeyTestsBase.java
+++ b/kse/src/test/java/org/kse/crypto/KeyTestsBase.java
@@ -39,6 +39,8 @@
import org.bouncycastle.jcajce.interfaces.MLKEMPublicKey;
import org.bouncycastle.jcajce.interfaces.SLHDSAPrivateKey;
import org.bouncycastle.jcajce.interfaces.SLHDSAPublicKey;
+import org.bouncycastle.jcajce.interfaces.XDHPrivateKey;
+import org.bouncycastle.jcajce.interfaces.XDHPublicKey;
import org.junit.jupiter.api.BeforeAll;
import org.kse.KSE;
import org.kse.crypto.keypair.KeyPairType;
@@ -60,6 +62,8 @@ public abstract class KeyTestsBase extends CryptoTestsBase {
protected static ECPublicKey gostPublicKey;
protected static EdDSAPrivateKey eddsaPrivateKey;
protected static EdDSAPublicKey eddsaPublicKey;
+ protected static XDHPrivateKey xdhPrivateKey;
+ protected static XDHPublicKey xdhPublicKey;
protected static MLDSAPublicKey mldsaPublicKey;
/**
* MLDSA private keys come with different types of encoding, but for now only the combined form is
@@ -75,13 +79,13 @@ public abstract class KeyTestsBase extends CryptoTestsBase {
protected static List privateKeys() {
- return Arrays.asList(rsaPrivateKey, dsaPrivateKey, ecPrivateKey, gostPrivateKey, eddsaPrivateKey,
+ return Arrays.asList(rsaPrivateKey, dsaPrivateKey, ecPrivateKey, gostPrivateKey, eddsaPrivateKey, xdhPrivateKey,
mldsaPrivateKeySeedAndExpanded, mlkemPrivateKey, slhDsaPrivateKey);
}
protected static List publicKeys() {
- return Arrays.asList(rsaPublicKey, dsaPublicKey, ecPublicKey, gostPublicKey, eddsaPublicKey, mldsaPublicKey,
- mlkemPublicKey, slhDsaPublicKey);
+ return Arrays.asList(rsaPublicKey, dsaPublicKey, ecPublicKey, gostPublicKey, eddsaPublicKey, xdhPublicKey,
+ mldsaPublicKey, mlkemPublicKey, slhDsaPublicKey);
}
@BeforeAll
@@ -117,6 +121,12 @@ static void initKeys() throws CryptoException {
eddsaPublicKey = (EdDSAPublicKey) eddsaKeyPair.getPublic();
}
+ if (xdhPrivateKey == null) {
+ KeyPair eddsaKeyPair = KeyPairUtil.generateECKeyPair("X25519", KSE.BC);
+ xdhPrivateKey = (XDHPrivateKey) eddsaKeyPair.getPrivate();
+ xdhPublicKey = (XDHPublicKey) eddsaKeyPair.getPublic();
+ }
+
if (mldsaPublicKey == null) {
KeyPair mldsaKeyPair = KeyPairUtil.generateKeyPair(KeyPairType.MLDSA44, KSE.BC);
mldsaPrivateKeySeedAndExpanded = (MLDSAPrivateKey) mldsaKeyPair.getPrivate();
diff --git a/kse/src/test/java/org/kse/crypto/jwk/JwkUtilTest.java b/kse/src/test/java/org/kse/crypto/jwk/JwkUtilTest.java
index e82ce50e8..b5d73c845 100644
--- a/kse/src/test/java/org/kse/crypto/jwk/JwkUtilTest.java
+++ b/kse/src/test/java/org/kse/crypto/jwk/JwkUtilTest.java
@@ -55,6 +55,7 @@
import org.bouncycastle.asn1.DERSequence;
import org.bouncycastle.asn1.edec.EdECObjectIdentifiers;
import org.bouncycastle.jcajce.interfaces.EdDSAPrivateKey;
+import org.bouncycastle.jcajce.interfaces.XDHPrivateKey;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.util.encoders.Hex;
import org.junit.jupiter.api.BeforeEach;
@@ -81,6 +82,12 @@ private byte[] encodePrivateKey(byte[] privateKeyScalar, String curve) throws IO
case "Ed25519":
oid = EdECObjectIdentifiers.id_Ed25519;
break;
+ case "X448":
+ oid = EdECObjectIdentifiers.id_X448;
+ break;
+ case "X25519":
+ oid = EdECObjectIdentifiers.id_X25519;
+ break;
default:
throw new IllegalArgumentException(curve);
}
@@ -104,6 +111,14 @@ KeyPair createEdDSAPrivateKey(String name, byte[] d) throws Exception {
EdDSAPrivateKey privateKey = (EdDSAPrivateKey) keyFactory.generatePrivate(new PKCS8EncodedKeySpec(pkcs8EncodedKey));
return new KeyPair(privateKey.getPublicKey(), privateKey);
}
+ KeyPair createXDHPrivateKey(String name, byte[] d) throws Exception {
+ Security.addProvider(new BouncyCastleProvider());
+ byte[] pkcs8EncodedKey = encodePrivateKey(d, name);
+ KeyFactory keyFactory = KeyFactory.getInstance(name, "BC");
+
+ XDHPrivateKey privateKey = (XDHPrivateKey) keyFactory.generatePrivate(new PKCS8EncodedKeySpec(pkcs8EncodedKey));
+ return new KeyPair(privateKey.getPublicKey(), privateKey);
+ }
@Nested
class ForEd25519{
byte[] d = Hex.decode("DCAAACEADDAC99D31020B81795E1F64CA36D81701034EE598C161F2655368F76");
@@ -301,6 +316,130 @@ void shouldExportPublicWithProvidedAlias() throws Exception {
assertNull(jwk.getParsedX509CertChain());
}
}
+ @Nested
+ class ForX25519{
+ byte[] d = Hex.decode("DCAAACEADDAC99D31020B81795E1F64CA36D81701034EE598C161F2655368F76");
+ KeyPair keyPair = createXDHPrivateKey("X25519", d);
+
+ ForX25519() throws Exception {}
+
+ private final String expectedJwkPub =
+ "{\n" +
+ " \"kty\": \"OKP\",\n" +
+ " \"kid\": \"alias\",\n" +
+ " \"crv\": \"X25519\",\n" +
+ " \"x\": \"P5xUKxUM0UlNnCpwxtOzJXdkwC4HHMsMTXcXxBmY3W4\"\n" +
+ "}";
+ private final String expectedJwk =
+ "{\n" +
+ " \"kty\": \"OKP\",\n" +
+ " \"kid\": \"alias\",\n" +
+ " \"crv\": \"X25519\",\n" +
+ " \"x\": \"P5xUKxUM0UlNnCpwxtOzJXdkwC4HHMsMTXcXxBmY3W4\",\n" +
+ " \"d\": \"3Kqs6t2smdMQILgXleH2TKNtgXAQNO5ZjBYfJlU2j3Y\"\n" +
+ "}";
+ @Test
+ void shouldExportWithProvidedAlias() throws Exception {
+ String actualJwk = JwkUtil.get(keyPair.getPrivate(), "alias");
+ JSONAssert.assertEquals(expectedJwk, actualJwk, JSONCompareMode.STRICT);
+
+ JWK jwk = JwkUtil.load(actualJwk.getBytes());
+ PrivateKey actualPrivateKey = JwkUtil.toPrivateKey(jwk);
+ PublicKey actualPublicKey = JwkUtil.toPublicKey(jwk);
+ assertArrayEquals(keyPair.getPrivate().getEncoded(), actualPrivateKey.getEncoded());
+ assertArrayEquals(keyPair.getPublic().getEncoded(), actualPublicKey.getEncoded());
+ assertNull(jwk.getParsedX509CertChain());
+ }
+ @Test
+ void shouldExportAndGenerateAliasIfNotProvided() throws Exception {
+ String actualJwk = JwkUtil.get(keyPair.getPrivate(), null);
+ JSONAssert.assertEquals(
+ expectedJwk.replace("alias", "k2iMCMk9Jm9rHmTkBlzk_0UpOmC3FhQsv5RBUjYBqBA"),
+ actualJwk,
+ JSONCompareMode.STRICT);
+
+ JWK jwk = JwkUtil.load(actualJwk.getBytes());
+ PrivateKey actualPrivateKey = JwkUtil.toPrivateKey(jwk);
+ PublicKey actualPublicKey = JwkUtil.toPublicKey(jwk);
+ assertArrayEquals(keyPair.getPrivate().getEncoded(), actualPrivateKey.getEncoded());
+ assertArrayEquals(keyPair.getPublic().getEncoded(), actualPublicKey.getEncoded());
+ assertNull(jwk.getParsedX509CertChain());
+ }
+ @Test
+ void shouldExportPublicWithProvidedAlias() throws Exception {
+ String actualJwk = JwkUtil.get(keyPair.getPublic(), "alias");
+ JSONAssert.assertEquals(expectedJwkPub, actualJwk, JSONCompareMode.STRICT);
+
+ JWK jwk = JwkUtil.load(actualJwk.getBytes());
+ PrivateKey actualPrivateKey = JwkUtil.toPrivateKey(jwk);
+ PublicKey actualPublicKey = JwkUtil.toPublicKey(jwk);
+ assertNull(actualPrivateKey);
+ assertArrayEquals(keyPair.getPublic().getEncoded(), actualPublicKey.getEncoded());
+ assertNull(jwk.getParsedX509CertChain());
+ }
+ }
+ @Nested
+ class ForX448{
+ byte[] d = Hex.decode("0DAAE3F2A4AC597FD67BEE8F46F50AE24CD67D53F846BE5DEB6ACE0CB67FD9EA95604FBDEF9F566A4245C9A5BAB81FD8DC0AF3179CE94C40");
+ KeyPair keyPair = createXDHPrivateKey("X448", d);
+
+ ForX448() throws Exception {}
+
+ private final String expectedJwkPub =
+ "{\n" +
+ " \"kty\": \"OKP\",\n" +
+ " \"kid\": \"alias\",\n" +
+ " \"crv\": \"X448\",\n" +
+ " \"x\": \"c_Bns0IeTg2fax-1_NNzqKHHFE0WRordmvumcuD7-58gMAxNq2QGT59nRwc1a7VduRJF0JJ1vIc\"\n" +
+ "}";
+ private final String expectedJwk =
+ "{\n" +
+ " \"kty\": \"OKP\",\n" +
+ " \"kid\": \"alias\",\n" +
+ " \"crv\": \"X448\",\n" +
+ " \"x\": \"c_Bns0IeTg2fax-1_NNzqKHHFE0WRordmvumcuD7-58gMAxNq2QGT59nRwc1a7VduRJF0JJ1vIc\",\n" +
+ " \"d\": \"Darj8qSsWX_We-6PRvUK4kzWfVP4Rr5d62rODLZ_2eqVYE-9759WakJFyaW6uB_Y3ArzF5zpTEA\"\n" +
+ "}";
+ @Test
+ void shouldExportWithProvidedAlias() throws Exception {
+ String actualJwk = JwkUtil.get(keyPair.getPrivate(), "alias");
+ JSONAssert.assertEquals(expectedJwk, actualJwk, JSONCompareMode.STRICT);
+
+ JWK jwk = JwkUtil.load(actualJwk.getBytes());
+ PrivateKey actualPrivateKey = JwkUtil.toPrivateKey(jwk);
+ PublicKey actualPublicKey = JwkUtil.toPublicKey(jwk);
+ assertArrayEquals(keyPair.getPrivate().getEncoded(), actualPrivateKey.getEncoded());
+ assertArrayEquals(keyPair.getPublic().getEncoded(), actualPublicKey.getEncoded());
+ assertNull(jwk.getParsedX509CertChain());
+ }
+ @Test
+ void shouldExportAndGenerateAliasIfNotProvided() throws Exception {
+ String actualJwk = JwkUtil.get(keyPair.getPrivate(), null);
+ JSONAssert.assertEquals(
+ expectedJwk.replace("alias", "dClFYxfmFbJU4onZvadO2pgEMu5n4Q1r3A1lAPnBMa8"),
+ actualJwk,
+ JSONCompareMode.STRICT);
+
+ JWK jwk = JwkUtil.load(actualJwk.getBytes());
+ PrivateKey actualPrivateKey = JwkUtil.toPrivateKey(jwk);
+ PublicKey actualPublicKey = JwkUtil.toPublicKey(jwk);
+ assertArrayEquals(keyPair.getPrivate().getEncoded(), actualPrivateKey.getEncoded());
+ assertArrayEquals(keyPair.getPublic().getEncoded(), actualPublicKey.getEncoded());
+ assertNull(jwk.getParsedX509CertChain());
+ }
+ @Test
+ void shouldExportPublicWithProvidedAlias() throws Exception {
+ String actualJwk = JwkUtil.get(keyPair.getPublic(), "alias");
+ JSONAssert.assertEquals(expectedJwkPub, actualJwk, JSONCompareMode.STRICT);
+
+ JWK jwk = JwkUtil.load(actualJwk.getBytes());
+ PrivateKey actualPrivateKey = JwkUtil.toPrivateKey(jwk);
+ PublicKey actualPublicKey = JwkUtil.toPublicKey(jwk);
+ assertNull(actualPrivateKey);
+ assertArrayEquals(keyPair.getPublic().getEncoded(), actualPublicKey.getEncoded());
+ assertNull(jwk.getParsedX509CertChain());
+ }
+ }
@Test
void handlesAttemptToExportUnsupportedEdDSAKeyWithGrace() {