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

Feature/export private key as jwk #528

Merged
merged 16 commits into from
Jan 3, 2025
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
96 changes: 96 additions & 0 deletions kse/src/main/java/org/kse/crypto/jwk/JwkExporter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/*
* Copyright 2004 - 2013 Wayne Grant
* 2013 - 2024 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.jwk;

import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
import java.util.Map;

import org.bouncycastle.jcajce.provider.asymmetric.edec.BCEdDSAPrivateKey;
import org.bouncycastle.jcajce.provider.asymmetric.edec.BCEdDSAPublicKey;
import org.kse.crypto.CryptoException;
import org.kse.crypto.keypair.KeyPairUtil;

import com.nimbusds.jose.jwk.Curve;

/**
* Implementers knows how to export an asymmetric key
*/
public interface JwkExporter {

/**
* Export a {@link java.security.PublicKey} or {@link java.security.PrivateKey} instance
*
* @param alias a key alias
* @return byte array with encoded key
*/
byte[] exportWithAlias(String alias);

abstract class ECKeyExporter implements JwkExporter {
protected static final Map<String, Curve> supportedCurvesMap =
Map.of(
"prime256v1", Curve.P_256,
"secp256r1", Curve.P_256,
"P-256", Curve.P_256,
"secp256k1", Curve.SECP256K1,
"secp384r1", Curve.P_384,
"P-384", Curve.P_384,
"secp521r1", Curve.P_521,
"P-521", Curve.P_521
);

public static boolean supportsCurve(String curveName) {
return supportedCurvesMap.containsKey(curveName);
}
protected Curve getCurve(ECPrivateKey privateKey) {
try {
String curveName = KeyPairUtil.getKeyInfo(privateKey).getDetailedAlgorithm();
return supportedCurvesMap.get(curveName);
} catch (CryptoException e) {
throw JwkExporterException.notSupported(privateKey.getAlgorithm(), e);
}
}

protected Curve getCurve(ECPublicKey publicKey) {
try {
String curveName = KeyPairUtil.getKeyInfo(publicKey).getDetailedAlgorithm();
return supportedCurvesMap.get(curveName);
} catch (CryptoException e) {
throw JwkExporterException.notSupported(publicKey.getAlgorithm(), e);
}
}
}

abstract class EdDSAKeyExporter implements JwkExporter {
protected final Map<String, Curve> supportedCurvesMap =
Map.of(
"Ed25519", Curve.Ed25519,
"Ed448", Curve.Ed448
);

protected Curve getCurve(BCEdDSAPublicKey bcEdDSAPublicKey) {
return supportedCurvesMap.get(bcEdDSAPublicKey.getAlgorithm());
}

protected Curve getCurve(BCEdDSAPrivateKey bcEdDSAPrivateKey) {
return supportedCurvesMap.get(bcEdDSAPrivateKey.getAlgorithm());
}
}
}
46 changes: 46 additions & 0 deletions kse/src/main/java/org/kse/crypto/jwk/JwkExporterException.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* Copyright 2004 - 2013 Wayne Grant
* 2013 - 2024 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.jwk;

import java.text.MessageFormat;

/** Thrown when export to JWK is not possible.
*
*/
public class JwkExporterException extends RuntimeException {

private JwkExporterException(String message, Throwable cause) {
super(message, cause);
}
private JwkExporterException(String message) {
super(message);
}
public static JwkExporterException keyExportFailed(String keyAlias, Throwable throwable){
return new JwkExporterException(MessageFormat.format("Key \"{0}\" export failed", keyAlias), throwable);
}
public static JwkExporterException notSupported(String notSupportedItem, Throwable throwable) {
String message = MessageFormat.format("Not supported: \"{0}\"", notSupportedItem);
if (throwable == null) {
return new JwkExporterException(message);
} else {
return new JwkExporterException(message, throwable);
}
}
}
10 changes: 10 additions & 0 deletions kse/src/main/java/org/kse/crypto/keypair/KeyPairUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,16 @@ public static KeyPairType getKeyPairType(PrivateKey privateKey) {
return KeyPairType.resolveJce(privateKey.getAlgorithm());
}

/**
* Determine the key pair type (algorithm).
*
* @param publicKey The private key
* @return KeyPairType type
*/
public static KeyPairType getKeyPairType(PublicKey publicKey) {
return KeyPairType.resolveJce(publicKey.getAlgorithm());
}

/**
* Check that the supplied private and public keys actually comprise a valid
* key pair.
Expand Down
155 changes: 155 additions & 0 deletions kse/src/main/java/org/kse/crypto/privatekey/JwkPrivateKeyExporter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
/*
* Copyright 2004 - 2013 Wayne Grant
* 2013 - 2024 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.privatekey;

import java.nio.charset.StandardCharsets;
import java.security.PrivateKey;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.RSAPrivateCrtKey;
import java.security.spec.ECPoint;

import org.bouncycastle.asn1.ASN1Primitive;
import org.bouncycastle.asn1.DEROctetString;
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.jcajce.interfaces.EdDSAPublicKey;
import org.bouncycastle.jcajce.provider.asymmetric.edec.BCEdDSAPrivateKey;
import org.kse.crypto.jwk.JwkExporter;
import org.kse.crypto.jwk.JwkExporterException;

import com.nimbusds.jose.jwk.Curve;
import com.nimbusds.jose.jwk.ECKey;
import com.nimbusds.jose.jwk.OctetKeyPair;
import com.nimbusds.jose.jwk.RSAKey;
import com.nimbusds.jose.util.Base64URL;

public class JwkPrivateKeyExporter {
private final String alias;
private final JwkExporter jwkExporter;

private JwkPrivateKeyExporter(PrivateKey privateKey, String alias) {
this.alias = alias;
if (privateKey instanceof RSAPrivateCrtKey) {
this.jwkExporter = new RSAPrivateKeyExporter(privateKey);
} else if (privateKey instanceof ECPrivateKey) {
this.jwkExporter = new ECPrivateKeyExporter(privateKey);
} else if (privateKey instanceof BCEdDSAPrivateKey) {
this.jwkExporter = new EdDSAPrivateKeyExporter(privateKey);
} else {
throw new IllegalArgumentException("Not supported key type: " + privateKey.getClass().getName());
}
}

public static JwkPrivateKeyExporter from(PrivateKey privateKey, String alias) {
return new JwkPrivateKeyExporter(privateKey, alias);
}

public byte[] get() {
return jwkExporter.exportWithAlias(alias);
}

private static class EdDSAPrivateKeyExporter extends JwkExporter.EdDSAKeyExporter {
private final BCEdDSAPrivateKey privateKey;

private EdDSAPrivateKeyExporter(PrivateKey privateKey) {
this.privateKey = (BCEdDSAPrivateKey) privateKey;
}

@Override
public byte[] exportWithAlias(String alias) {
Curve curve = getCurve(privateKey);
try {
EdDSAPublicKey edDSAPublicKey = privateKey.getPublicKey();
SubjectPublicKeyInfo subjectPublicKeyInfo = SubjectPublicKeyInfo.getInstance(
edDSAPublicKey.getEncoded());
byte[] rawKey = subjectPublicKeyInfo.getPublicKeyData().getBytes();
OctetKeyPair.Builder builder = new OctetKeyPair.Builder(curve, Base64URL.encode(rawKey));
ASN1Primitive privateKeyOctetString = DEROctetString.fromByteArray(
PrivateKeyInfo.getInstance(privateKey.getEncoded()).getPrivateKey().getOctets());
byte[] d = DEROctetString.getInstance(privateKeyOctetString.getEncoded()).getOctets();
builder.d(Base64URL.encode(d));
if (alias != null) {
builder.keyID(alias);
} else {
builder.keyIDFromThumbprint();
}
OctetKeyPair key = builder.build();
return key.toJSONString().getBytes(StandardCharsets.UTF_8);
} catch (Exception e) {
throw JwkExporterException.keyExportFailed(alias, e);
}
}
}

private static class ECPrivateKeyExporter extends JwkExporter.ECKeyExporter {
private final ECPrivateKey privateKey;

private ECPrivateKeyExporter(PrivateKey privateKey) {
this.privateKey = (ECPrivateKey) privateKey;
}

@Override
public byte[] exportWithAlias(String alias) {
Curve curve = getCurve(this.privateKey);
try {
final ECPoint generator = this.privateKey.getParams().getGenerator();
ECKey.Builder builder = new ECKey.Builder(curve, Base64URL.encode(generator.getAffineX()),
Base64URL.encode(generator.getAffineY()));
builder.d(Base64URL.encode(this.privateKey.getS()));
if (alias != null) {
builder.keyID(alias);
} else {
builder.keyIDFromThumbprint();
}
ECKey key = builder.build();
return key.toJSONString().getBytes(StandardCharsets.UTF_8);
} catch (Exception e) {
throw JwkExporterException.keyExportFailed(alias, e);
}
}
}

private static class RSAPrivateKeyExporter implements JwkExporter {
private final RSAPrivateCrtKey privateKey;

private RSAPrivateKeyExporter(PrivateKey privateKey) {
this.privateKey = (RSAPrivateCrtKey) privateKey;
}

@Override
public byte[] exportWithAlias(String alias) {
try {
final var n = Base64URL.encode(this.privateKey.getModulus().toByteArray());
final var e = Base64URL.encode(this.privateKey.getPublicExponent().toByteArray());
RSAKey.Builder builder = new RSAKey.Builder(n, e);
if (alias != null) {
builder.keyID(alias);
} else {
builder.keyIDFromThumbprint();
}
builder.privateKey(privateKey);
RSAKey rsaKey = builder.build();
return rsaKey.toJSONString().getBytes(StandardCharsets.UTF_8);
} catch (Exception e) {
throw JwkExporterException.keyExportFailed(alias, e);
}
}
}
}
Loading
Loading