KeyUtils.java
/*
* GovWay - A customizable API Gateway
* https://govway.org
*
* Copyright (c) 2005-2026 Link.it srl (https://link.it).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3, as published by
* the Free Software Foundation.
*
* 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/>.
*
*/
package org.openspcoop2.utils.certificate;
import java.io.ByteArrayInputStream;
import java.io.InputStreamReader;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.Provider;
import java.security.PublicKey;
import java.security.Security;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.commons.lang3.StringUtils;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
import org.bouncycastle.asn1.sec.ECPrivateKey;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
import org.bouncycastle.openssl.PEMDecryptorProvider;
import org.bouncycastle.openssl.PEMEncryptedKeyPair;
import org.bouncycastle.openssl.PEMKeyPair;
import org.bouncycastle.openssl.PEMParser;
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
import org.bouncycastle.openssl.jcajce.JceOpenSSLPKCS8DecryptorProviderBuilder;
import org.bouncycastle.openssl.jcajce.JcePEMDecryptorProviderBuilder;
import org.bouncycastle.operator.InputDecryptorProvider;
import org.bouncycastle.pkcs.PKCS8EncryptedPrivateKeyInfo;
import org.bouncycastle.util.io.pem.PemReader;
import org.openspcoop2.utils.UtilsException;
import org.openspcoop2.utils.UtilsMultiException;
import org.openspcoop2.utils.UtilsRuntimeException;
/**
* KeyUtils
*
* @author Poli Andrea (apoli@link.it)
* @author $Author$
* @version $Rev$, $Date$
*/
public class KeyUtils {
public static final String ALGO_RSA = "RSA";
public static final String ALGO_DSA = "DSA";
public static final String ALGO_DH = "DH"; // Diffie-Hellman
public static final String ALGO_EC = "EC"; // Elliptic Curve Digital Signature Algorithm o ECDH (Elliptic Curve Diffie-Hellman).
private static volatile boolean useBouncyCastleProvider = true;
public static boolean isUseBouncyCastleProvider() {
return useBouncyCastleProvider;
}
public static void setUseBouncyCastleProvider(boolean useBouncyCastleProvider) {
KeyUtils.useBouncyCastleProvider = useBouncyCastleProvider;
}
private static Provider provider = null;
private static synchronized Provider getProviderEngine() {
if ( provider == null )
provider = Security.getProvider(org.bouncycastle.jce.provider.BouncyCastleProvider.PROVIDER_NAME);
return provider;
}
private static Provider getProvider() {
Provider p = null;
if(!useBouncyCastleProvider) {
return p;
}
if ( provider == null )
return getProviderEngine();
return provider;
}
public static KeyUtils getInstance() throws UtilsException {
return new KeyUtils();
}
public static KeyUtils getInstance(String algo) throws UtilsException {
return new KeyUtils(algo);
}
private String algorithm;
private KeyFactory kf;
private Map<String, KeyFactory> keyFactoryMap = new ConcurrentHashMap<>();
public KeyUtils() throws UtilsException {
this(ALGO_RSA);
}
public KeyUtils(String algo) throws UtilsException {
try {
this.algorithm = algo;
this.kf = getKeyFactoryEngine(algo);
this.keyFactoryMap.put(algo, this.kf);
}catch(Exception e) {
throw new UtilsException(e.getMessage(),e);
}
}
private KeyFactory getKeyFactory(String algo) {
return this.keyFactoryMap.computeIfAbsent(algo, a -> {
try {
return getKeyFactoryEngine(a);
} catch (Exception e) {
throw new UtilsRuntimeException(e.getMessage(), e);
}
});
}
private KeyFactory getKeyFactoryEngine(String algo) throws NoSuchAlgorithmException {
Provider p = getProvider();
if(p!=null) {
return KeyFactory.getInstance(algo, p);
}
else{
return KeyFactory.getInstance(algo);
}
}
public String getAlgorithm() {
return this.algorithm;
}
// ** PUBLIC KEY **/
public PublicKey readPublicKeyPEMFormat(byte[] publicKey) throws UtilsException {
PEMReader pemArchive = new PEMReader(publicKey);
if(pemArchive.getPublicKey()!=null) {
publicKey = pemArchive.getPublicKey().getBytes();
}
try {
try (ByteArrayInputStream bin = new ByteArrayInputStream(publicKey);
InputStreamReader ir = new InputStreamReader(bin);
PemReader pemReader = new PemReader(ir);){
byte [] encoded = pemReader.readPemObject().getContent();
X509EncodedKeySpec specPub = new X509EncodedKeySpec(encoded);
return this.kf.generatePublic(specPub);
}
}catch(Exception e) {
throw new UtilsException(e.getMessage(),e);
}
}
public PublicKey readPublicKeyDERFormat(byte[] publicKey) throws UtilsException {
try {
X509EncodedKeySpec specPub = new X509EncodedKeySpec(publicKey);
return this.kf.generatePublic(specPub);
}catch(Exception e) {
throw new UtilsException(e.getMessage(),e);
}
}
public PublicKey readCertificate(byte[] publicKey) throws UtilsException {
PEMReader pemArchive = new PEMReader(publicKey);
if(pemArchive.getCertificates()!=null && !pemArchive.getCertificates().isEmpty()) {
String cert = pemArchive.getCertificates().get(0); // prendo il primo
if(cert!=null && StringUtils.isNotEmpty(cert)) {
publicKey = cert.getBytes();
}
}
return ArchiveLoader.load(publicKey).getCertificate().getCertificate().getPublicKey();
}
public PublicKey getPublicKey(byte[] publicKey) throws UtilsException {
PEMReader pemArchive = new PEMReader(publicKey);
if(pemArchive.getPublicKey()!=null) {
return this.readPublicKeyPEMFormat(pemArchive.getPublicKey().getBytes());
}
else if(pemArchive.getCertificates()!=null && !pemArchive.getCertificates().isEmpty()) {
String cert = pemArchive.getCertificates().get(0); // prendo il primo
if(cert!=null && StringUtils.isNotEmpty(cert)) {
return this.readCertificate(cert.getBytes());
}
}
try {
return readPublicKeyDERFormat(publicKey);
}catch(Exception eDer) {
// provo X509
try {
return readCertificate(publicKey);
}catch(Exception eX509) {
// rilancio entrambe le eccezioni
UtilsMultiException multi = new UtilsMultiException("Load public key failed (DER)", eDer, eX509);
throw new UtilsException(multi.getMultiMessage(), multi);
}
}
}
// ** PRIVATE KEY **/
public PrivateKey readPKCS1PrivateKeyPEMFormat(byte[] privateKey) throws UtilsException {
PEMReader pemArchive = new PEMReader(privateKey,true,false,false);
if(pemArchive.getPrivateKey()!=null) {
privateKey = pemArchive.getPrivateKey().getBytes();
}
// Legge nel formato PEM PKCS1 e lo porta in PKCS8
try {
try (ByteArrayInputStream bin = new ByteArrayInputStream(privateKey);
InputStreamReader ir = new InputStreamReader(bin);
PEMParser pemParser = new PEMParser(ir);){
JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider(org.bouncycastle.jce.provider.BouncyCastleProvider.PROVIDER_NAME);
Object object = pemParser.readObject();
KeyPair kp = converter.getKeyPair((PEMKeyPair) object);
return kp.getPrivate();
}
}catch(Exception e) {
throw new UtilsException(e.getMessage(),e);
}
}
public PrivateKey readPKCS8PrivateKeyPEMFormat(byte[] privateKey) throws UtilsException {
PEMReader pemArchive = new PEMReader(privateKey,false,true,false);
if(pemArchive.getPrivateKey()!=null) {
privateKey = pemArchive.getPrivateKey().getBytes();
}
try {
try (ByteArrayInputStream bin = new ByteArrayInputStream(privateKey);
InputStreamReader ir = new InputStreamReader(bin);
PemReader pemReader = new PemReader(ir);){
byte [] encoded = pemReader.readPemObject().getContent();
PKCS8EncodedKeySpec specPriv = new PKCS8EncodedKeySpec(encoded);
return this.kf.generatePrivate(specPriv);
}
}catch(Exception e) {
throw new UtilsException(e.getMessage(),e);
}
}
public PrivateKey readPKCS8PrivateKeyDERFormat(byte[] privateKey) throws UtilsException {
try {
PKCS8EncodedKeySpec specPriv = new PKCS8EncodedKeySpec(privateKey);
return this.kf.generatePrivate(specPriv);
}catch(Exception e) {
throw new UtilsException(e.getMessage(),e);
}
}
public PrivateKey readSEC1PrivateKeyDERFormat(byte[] privateKey) throws UtilsException {
// Legge chiavi EC in formato SEC1 DER (generato con: openssl ec -outform DER)
// SEC1 รจ un formato specifico per chiavi EC, quindi usa sempre KeyFactory EC
try {
/**System.out.println("[DEBUG-SEC1] Input byte array length: " + (privateKey != null ? privateKey.length : "null"));*/
/**System.out.println("[DEBUG-SEC1] Step 1: Parsing ASN1Sequence...");*/
ASN1Sequence seq = ASN1Sequence.getInstance(privateKey);
/**System.out.println("[DEBUG-SEC1] ASN1Sequence parsed, size: " + seq.size());*/
/**System.out.println("[DEBUG-SEC1] Step 2: Creating ECPrivateKey...");*/
ECPrivateKey ecPrivateKey = ECPrivateKey.getInstance(seq);
/**System.out.println("[DEBUG-SEC1] ECPrivateKey created successfully");*/
/**System.out.println("[DEBUG-SEC1] Step 3: Getting parameters object...");*/
Object params = ecPrivateKey.getParametersObject();
/**System.out.println("[DEBUG-SEC1] Parameters object: " + (params != null ? params.getClass().getName() + " = " + params : "null"));*/
if(params == null) {
throw new UtilsException("SEC1 EC private key does not contain curve parameters (tag [0])");
}
if(!(params instanceof ASN1ObjectIdentifier)) {
throw new UtilsException("SEC1 EC private key parameters is not an OID, found: " + params.getClass().getName());
}
ASN1ObjectIdentifier curveOid = (ASN1ObjectIdentifier) params;
/**System.out.println("[DEBUG-SEC1] Curve OID: " + curveOid.getId());*/
/**System.out.println("[DEBUG-SEC1] Step 4: Creating AlgorithmIdentifier...");*/
AlgorithmIdentifier algId = new AlgorithmIdentifier(X9ObjectIdentifiers.id_ecPublicKey, curveOid);
/**System.out.println("[DEBUG-SEC1] AlgorithmIdentifier created");*/
/**System.out.println("[DEBUG-SEC1] Step 5: Creating PrivateKeyInfo...");*/
PrivateKeyInfo privKeyInfo = new PrivateKeyInfo(algId, ecPrivateKey);
/**System.out.println("[DEBUG-SEC1] PrivateKeyInfo created");*/
/**System.out.println("[DEBUG-SEC1] Step 6: Creating PKCS8EncodedKeySpec...");*/
PKCS8EncodedKeySpec specPriv = new PKCS8EncodedKeySpec(privKeyInfo.getEncoded());
/**System.out.println("[DEBUG-SEC1] PKCS8EncodedKeySpec created, length: " + specPriv.getEncoded().length);*/
/**System.out.println("[DEBUG-SEC1] Step 7: Generating private key with EC KeyFactory...");*/
KeyFactory kfEC = getKeyFactory(ALGO_EC);
/**System.out.println("[DEBUG-SEC1] KeyFactory algorithm: " + kfEC.getAlgorithm());*/
PrivateKey result = kfEC.generatePrivate(specPriv);
if(result!=null) {
/**System.out.println("[DEBUG-SEC1] Private key generated successfully: " + result.getAlgorithm());*/
}
return result;
}catch(UtilsException e) {
throw e;
}catch(Exception e) {
/**System.out.println("[DEBUG-SEC1] ERROR: " + e.getClass().getName() + ": " + e.getMessage());*/
/**e.printStackTrace(System.out);*/
throw new UtilsException("Could not parse SEC1 EC private key: " + e.getMessage(), e);
}
}
public PrivateKey getPrivateKey(byte[] privateKey) throws UtilsException {
PEMReader pemArchive = new PEMReader(privateKey);
if(pemArchive.getPrivateKey()!=null) {
privateKey = pemArchive.getPrivateKey().getBytes();
if(pemArchive.isPkcs1() || pemArchive.isSec1ec()) {
return this.readPKCS1PrivateKeyPEMFormat(privateKey);
}
else if(pemArchive.isPkcs8()) {
return this.readPKCS8PrivateKeyPEMFormat(privateKey);
}
}
try {
return readPKCS8PrivateKeyDERFormat(privateKey);
}catch(Exception ePkcs8) {
// provo con SEC1 DER (chiavi EC)
try {
return readSEC1PrivateKeyDERFormat(privateKey);
}catch(Exception eSec1) {
// rilancio entrambe le eccezioni
UtilsMultiException multi = new UtilsMultiException("Load private key failed (DER)", ePkcs8, eSec1);
throw new UtilsException(multi.getMultiMessage(), multi);
}
}
}
// ** PRIVATE KEY ENCRYPTED **/
public PrivateKey readPKCS1EncryptedPrivateKeyPEMFormat(byte[] privateKey, String password) throws UtilsException{
PEMReader pemArchive = new PEMReader(privateKey,true,false,false);
if(pemArchive.getPrivateKey()!=null) {
privateKey = pemArchive.getPrivateKey().getBytes();
}
// Legge nel formato PEM PKCS1 e lo porta in PKCS8
try {
try (ByteArrayInputStream bin = new ByteArrayInputStream(privateKey);
InputStreamReader ir = new InputStreamReader(bin);
PEMParser pemParser = new PEMParser(ir);){
JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider(org.bouncycastle.jce.provider.BouncyCastleProvider.PROVIDER_NAME);
Object object = pemParser.readObject();
PEMEncryptedKeyPair pair = (PEMEncryptedKeyPair) object;
JcePEMDecryptorProviderBuilder jce = new JcePEMDecryptorProviderBuilder().setProvider(org.bouncycastle.jce.provider.BouncyCastleProvider.PROVIDER_NAME);
PEMDecryptorProvider decProv = jce.build(password.toCharArray());
KeyPair kp = converter.getKeyPair(pair.decryptKeyPair(decProv));
return kp.getPrivate();
}
}catch(Exception e) {
throw new UtilsException(e.getMessage(),e);
}
}
public PrivateKey readPKCS8EncryptedPrivateKeyPEMFormat(byte[] privateKey, String password) throws UtilsException{
PEMReader pemArchive = new PEMReader(privateKey,false,false,true);
if(pemArchive.getPrivateKey()!=null) {
privateKey = pemArchive.getPrivateKey().getBytes();
}
PKCS8EncryptedPrivateKeyInfo pair = null;
try {
try (ByteArrayInputStream bin = new ByteArrayInputStream(privateKey);
InputStreamReader ir = new InputStreamReader(bin);
PEMParser parser = new PEMParser(ir);){
pair = (PKCS8EncryptedPrivateKeyInfo)parser.readObject();
}
}catch(Exception e) {
throw new UtilsException(e.getMessage(),e);
}
return readPKCS8EncryptedPrivateKey(pair, password);
}
public PrivateKey readPKCS8EncryptedPrivateKeyDERFormat(byte[] privateKey, String password) throws UtilsException{
PKCS8EncryptedPrivateKeyInfo pair = null;
try {
pair = new PKCS8EncryptedPrivateKeyInfo(privateKey);
}catch(Exception e) {
throw new UtilsException(e.getMessage(),e);
}
return readPKCS8EncryptedPrivateKey(pair, password);
}
private PrivateKey readPKCS8EncryptedPrivateKey(PKCS8EncryptedPrivateKeyInfo pair, String password) throws UtilsException{
try {
JceOpenSSLPKCS8DecryptorProviderBuilder jce = new JceOpenSSLPKCS8DecryptorProviderBuilder().setProvider(org.bouncycastle.jce.provider.BouncyCastleProvider.PROVIDER_NAME);
InputDecryptorProvider decProv = jce.build(password.toCharArray());
PrivateKeyInfo keyInfo = pair.decryptPrivateKeyInfo(decProv);
JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider(org.bouncycastle.jce.provider.BouncyCastleProvider.PROVIDER_NAME);
return converter.getPrivateKey(keyInfo);
}catch(Exception e) {
throw new UtilsException(e.getMessage(),e);
}
}
public PrivateKey getPrivateKey(byte[] privateKey, String password) throws UtilsException {
PEMReader pemArchive = new PEMReader(privateKey);
if(pemArchive.getPrivateKey()!=null) {
byte[] pemPrivateKey = pemArchive.getPrivateKey().getBytes();
return getPrivateKeyFromPEM(pemPrivateKey, password, pemArchive);
}
return getPrivateKeyFromDER(privateKey, password);
}
private PrivateKey getPrivateKeyFromPEM(byte[] privateKey, String password, PEMReader pemArchive) throws UtilsException {
if(pemArchive.isPkcs8encrypted()) {
return this.readPKCS8EncryptedPrivateKeyPEMFormat(privateKey, password);
}
else if(pemArchive.isPkcs1() || pemArchive.isSec1ec()) {
return getPrivateKeyFromPEMPkcs1OrSec1(privateKey, password);
}
else if(pemArchive.isPkcs8()) {
return this.readPKCS8PrivateKeyPEMFormat(privateKey);
}
throw new UtilsException("Unsupported PEM private key format");
}
private PrivateKey getPrivateKeyFromPEMPkcs1OrSec1(byte[] privateKey, String password) throws UtilsException {
try {
return this.readPKCS1EncryptedPrivateKeyPEMFormat(privateKey, password);
}catch(Exception e) {
// provo senza password
try {
return this.readPKCS1PrivateKeyPEMFormat(privateKey);
}catch(Exception eNoPassword) {
// rilancio entrambe le eccezioni
UtilsMultiException multi = new UtilsMultiException("Load private key failed (PEM PKCS1/SEC1)", e, eNoPassword);
throw new UtilsException(multi.getMultiMessage(), multi);
}
}
}
private PrivateKey getPrivateKeyFromDER(byte[] privateKey, String password) throws UtilsException {
try {
return readPKCS8EncryptedPrivateKeyDERFormat(privateKey, password);
}catch(Exception ePkcs8Enc) {
return getPrivateKeyFromDERUnencrypted(privateKey, ePkcs8Enc);
}
}
private PrivateKey getPrivateKeyFromDERUnencrypted(byte[] privateKey, Exception ePkcs8Enc) throws UtilsException {
try {
return readPKCS8PrivateKeyDERFormat(privateKey);
}catch(Exception ePkcs8) {
// provo con SEC1 DER (chiavi EC)
try {
return readSEC1PrivateKeyDERFormat(privateKey);
}catch(Exception eSec1) {
// rilancio tutte le eccezioni
UtilsMultiException multi = new UtilsMultiException("Load private key failed (DER)", ePkcs8Enc, ePkcs8, eSec1);
throw new UtilsException(multi.getMultiMessage(), multi);
}
}
}
}