/*
*
* Copyright (c) 2013 - 2017 Lijun Liao
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License version 3
* as published by the Free Software Foundation with the addition of the
* following permission added to Section 15 as permitted in Section 7(a):
*
* FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY
* THE AUTHOR LIJUN LIAO. LIJUN LIAO DISCLAIMS THE WARRANTY OF NON INFRINGEMENT
* OF THIRD PARTY RIGHTS.
*
* 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License.
*
* You can be released from the requirements of the license by purchasing
* a commercial license. Buying such a license is mandatory as soon as you
* develop commercial activities involving the XiPKI software without
* disclosing the source code of your own applications.
*
* For more information, please contact Lijun Liao at this
* address: lijun.liao@gmail.com
*/
package org.xipki.commons.security.util;
import java.io.IOException;
import java.math.BigInteger;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.interfaces.DSAPrivateKey;
import java.security.interfaces.DSAPublicKey;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
import java.security.interfaces.RSAPrivateCrtKey;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.AlgorithmParameterSpec;
import java.security.spec.DSAParameterSpec;
import java.security.spec.DSAPublicKeySpec;
import java.security.spec.ECParameterSpec;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.RSAKeyGenParameterSpec;
import java.security.spec.RSAPublicKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.HashMap;
import java.util.Map;
import org.bouncycastle.asn1.ASN1Encodable;
import org.bouncycastle.asn1.ASN1EncodableVector;
import org.bouncycastle.asn1.ASN1Integer;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.DERNull;
import org.bouncycastle.asn1.DERSequence;
import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.asn1.x9.X962Parameters;
import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
import org.bouncycastle.crypto.params.DSAParameters;
import org.bouncycastle.crypto.params.RSAKeyParameters;
import org.bouncycastle.crypto.params.RSAPrivateCrtKeyParameters;
import org.bouncycastle.jcajce.provider.asymmetric.dsa.DSAUtil;
import org.bouncycastle.jcajce.provider.asymmetric.util.EC5Util;
import org.bouncycastle.jcajce.provider.asymmetric.util.ECUtil;
import org.bouncycastle.jce.ECNamedCurveTable;
import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec;
import org.bouncycastle.jce.spec.ECPublicKeySpec;
import org.bouncycastle.math.ec.ECPoint;
import org.xipki.commons.common.util.ParamUtil;
/**
* @author Lijun Liao
* @since 2.0.0
*/
public class KeyUtil {
private static final Map<String, KeyFactory> KEY_FACTORIES = new HashMap<>();
private static final Map<String, KeyPairGenerator> KEYPAIR_GENERATORS = new HashMap<>();
private KeyUtil() {
}
public static KeyStore getKeyStore(final String storeType)
throws KeyStoreException, NoSuchProviderException {
ParamUtil.requireNonBlank("storeType", storeType);
if ("JKS".equalsIgnoreCase(storeType)) {
return KeyStore.getInstance(storeType);
} else {
return KeyStore.getInstance(storeType, "BC");
}
}
// CHECKSTYLE:SKIP
public static KeyPair generateRSAKeypair(final int keysize, final SecureRandom random)
throws NoSuchAlgorithmException, NoSuchProviderException,
InvalidAlgorithmParameterException {
return generateRSAKeypair(keysize, (BigInteger) null, random);
}
// CHECKSTYLE:SKIP
public static KeyPair generateRSAKeypair(final int keysize, final BigInteger publicExponent,
final SecureRandom random)
throws NoSuchAlgorithmException, NoSuchProviderException,
InvalidAlgorithmParameterException {
BigInteger tmpPublicExponent = publicExponent;
if (tmpPublicExponent == null) {
tmpPublicExponent = RSAKeyGenParameterSpec.F4;
}
AlgorithmParameterSpec params = new RSAKeyGenParameterSpec(keysize, tmpPublicExponent);
KeyPairGenerator kpGen = getKeyPairGenerator("RSA");
synchronized (kpGen) {
if (random == null) {
kpGen.initialize(params);
} else {
kpGen.initialize(params, random);
}
return kpGen.generateKeyPair();
}
}
// CHECKSTYLE:SKIP
public static KeyPair generateDSAKeypair(final int plength, final int qlength,
final SecureRandom random)
throws NoSuchAlgorithmException, NoSuchProviderException,
InvalidAlgorithmParameterException {
DSAParameterSpec dsaParamSpec = DSAParameterCache.getDSAParameterSpec(plength, qlength,
random);
KeyPairGenerator kpGen = getKeyPairGenerator("DSA");
synchronized (kpGen) {
kpGen.initialize(dsaParamSpec, random);
return kpGen.generateKeyPair();
}
}
// CHECKSTYLE:SKIP
public static KeyPair generateDSAKeypair(final DSAParameters dsaParams,
final SecureRandom random)
throws NoSuchAlgorithmException, NoSuchProviderException,
InvalidAlgorithmParameterException {
DSAParameterSpec dsaParamSpec = new DSAParameterSpec(dsaParams.getP(), dsaParams.getQ(),
dsaParams.getG());
KeyPairGenerator kpGen = getKeyPairGenerator("DSA");
synchronized (kpGen) {
kpGen.initialize(dsaParamSpec, random);
return kpGen.generateKeyPair();
}
}
// CHECKSTYLE:SKIP
public static DSAPublicKey generateDSAPublicKey(final DSAPublicKeySpec keySpec)
throws InvalidKeySpecException {
ParamUtil.requireNonNull("keySpec", keySpec);
KeyFactory kf = getKeyFactory("DSA");
synchronized (kf) {
return (DSAPublicKey) kf.generatePublic(keySpec);
}
}
// CHECKSTYLE:SKIP
public static KeyPair generateECKeypairForCurveNameOrOid(final String curveNameOrOid,
final SecureRandom random)
throws NoSuchAlgorithmException, NoSuchProviderException,
InvalidAlgorithmParameterException {
ASN1ObjectIdentifier oid = AlgorithmUtil.getCurveOidForCurveNameOrOid(curveNameOrOid);
if (oid == null) {
throw new IllegalArgumentException("invalid curveNameOrOid '" + curveNameOrOid + "'");
}
return generateECKeypair(oid, random);
}
// CHECKSTYLE:SKIP
public static KeyPair generateECKeypair(final ASN1ObjectIdentifier curveId,
final SecureRandom random)
throws NoSuchAlgorithmException, NoSuchProviderException,
InvalidAlgorithmParameterException {
ParamUtil.requireNonNull("curveId", curveId);
ECNamedCurveParameterSpec spec = ECNamedCurveTable.getParameterSpec(curveId.getId());
KeyPairGenerator kpGen = getKeyPairGenerator("EC");
synchronized (kpGen) {
if (random == null) {
kpGen.initialize(spec);
} else {
kpGen.initialize(spec, random);
}
return kpGen.generateKeyPair();
}
}
private static KeyFactory getKeyFactory(final String algorithm) throws InvalidKeySpecException {
String alg = algorithm.toUpperCase();
if ("ECDSA".equals(alg)) {
alg = "EC";
}
synchronized (KEY_FACTORIES) {
KeyFactory kf = KEY_FACTORIES.get(algorithm);
if (kf != null) {
return kf;
}
try {
kf = KeyFactory.getInstance(algorithm, "BC");
} catch (NoSuchAlgorithmException | NoSuchProviderException ex) {
throw new InvalidKeySpecException("could not find KeyFactory for " + algorithm
+ ": " + ex.getMessage());
}
KEY_FACTORIES.put(algorithm, kf);
return kf;
}
}
private static KeyPairGenerator getKeyPairGenerator(final String algorithm)
throws NoSuchAlgorithmException, NoSuchProviderException {
String alg = algorithm.toUpperCase();
if ("ECDSA".equals(alg)) {
alg = "EC";
}
synchronized (KEYPAIR_GENERATORS) {
KeyPairGenerator kg = KEYPAIR_GENERATORS.get(algorithm);
if (kg != null) {
return kg;
}
kg = KeyPairGenerator.getInstance(algorithm, "BC");
KEYPAIR_GENERATORS.put(algorithm, kg);
return kg;
}
}
public static PublicKey generatePublicKey(final SubjectPublicKeyInfo pkInfo)
throws NoSuchAlgorithmException, InvalidKeySpecException {
ParamUtil.requireNonNull("pkInfo", pkInfo);
X509EncodedKeySpec keyspec;
try {
keyspec = new X509EncodedKeySpec(pkInfo.getEncoded());
} catch (IOException ex) {
throw new InvalidKeySpecException(ex.getMessage(), ex);
}
ASN1ObjectIdentifier aid = pkInfo.getAlgorithm().getAlgorithm();
String algorithm;
if (PKCSObjectIdentifiers.rsaEncryption.equals(aid)) {
algorithm = "RSA";
} else if (X9ObjectIdentifiers.id_dsa.equals(aid)) {
algorithm = "DSA";
} else if (X9ObjectIdentifiers.id_ecPublicKey.equals(aid)) {
algorithm = "EC";
} else {
throw new InvalidKeySpecException("unsupported key algorithm: " + aid);
}
KeyFactory kf = getKeyFactory(algorithm);
synchronized (kf) {
return kf.generatePublic(keyspec);
}
}
// CHECKSTYLE:SKIP
public static RSAPublicKey generateRSAPublicKey(final RSAPublicKeySpec keySpec)
throws InvalidKeySpecException {
ParamUtil.requireNonNull("keySpec", keySpec);
KeyFactory kf = getKeyFactory("RSA");
synchronized (kf) {
return (RSAPublicKey) kf.generatePublic(keySpec);
}
}
// CHECKSTYLE:SKIP
public static ECPublicKey generateECPublicKeyForNameOrOid(final String curveNameOrOid,
final byte[] encodedQ)
throws InvalidKeySpecException {
ASN1ObjectIdentifier oid = AlgorithmUtil.getCurveOidForCurveNameOrOid(curveNameOrOid);
if (oid == null) {
throw new IllegalArgumentException("invalid curveNameOrOid '" + curveNameOrOid + "'");
}
return generateECPublicKey(oid, encodedQ);
}
// CHECKSTYLE:SKIP
public static ECPublicKey generateECPublicKey(final ASN1ObjectIdentifier curveOid,
final byte[] encodedQ)
throws InvalidKeySpecException {
ParamUtil.requireNonNull("curveOid", curveOid);
ParamUtil.requireNonNull("encoded", encodedQ);
ECNamedCurveParameterSpec spec = ECNamedCurveTable.getParameterSpec(curveOid.getId());
ECPoint pointQ = spec.getCurve().decodePoint(encodedQ);
ECPublicKeySpec keySpec = new ECPublicKeySpec(pointQ, spec);
KeyFactory kf = getKeyFactory("EC");
synchronized (kf) {
return (ECPublicKey) kf.generatePublic(keySpec);
}
}
public static AsymmetricKeyParameter generatePrivateKeyParameter(final PrivateKey key)
throws InvalidKeyException {
ParamUtil.requireNonNull("key", key);
if (key instanceof RSAPrivateCrtKey) {
RSAPrivateCrtKey rsaKey = (RSAPrivateCrtKey) key;
return new RSAPrivateCrtKeyParameters(rsaKey.getModulus(),
rsaKey.getPublicExponent(), rsaKey.getPrivateExponent(),
rsaKey.getPrimeP(), rsaKey.getPrimeQ(), rsaKey.getPrimeExponentP(),
rsaKey.getPrimeExponentQ(), rsaKey.getCrtCoefficient());
} else if (key instanceof RSAPrivateKey) {
RSAPrivateKey rsaKey = (RSAPrivateKey) key;
return new RSAKeyParameters(true, rsaKey.getModulus(), rsaKey.getPrivateExponent());
} else if (key instanceof ECPrivateKey) {
return ECUtil.generatePrivateKeyParameter(key);
} else if (key instanceof DSAPrivateKey) {
return DSAUtil.generatePrivateKeyParameter(key);
} else {
throw new InvalidKeyException("unknown key " + key.getClass().getName());
}
}
public static AsymmetricKeyParameter generatePublicKeyParameter(final PublicKey key)
throws InvalidKeyException {
ParamUtil.requireNonNull("key", key);
if (key instanceof RSAPublicKey) {
RSAPublicKey rsaKey = (RSAPublicKey) key;
return new RSAKeyParameters(false, rsaKey.getModulus(), rsaKey.getPublicExponent());
} else if (key instanceof ECPublicKey) {
return ECUtil.generatePublicKeyParameter(key);
} else if (key instanceof DSAPublicKey) {
return DSAUtil.generatePublicKeyParameter(key);
} else {
throw new InvalidKeyException("unknown key " + key.getClass().getName());
}
}
public static SubjectPublicKeyInfo createSubjectPublicKeyInfo(final PublicKey publicKey)
throws InvalidKeyException {
ParamUtil.requireNonNull("publicKey", publicKey);
if (publicKey instanceof DSAPublicKey) {
DSAPublicKey dsaPubKey = (DSAPublicKey) publicKey;
ASN1EncodableVector vec = new ASN1EncodableVector();
vec.add(new ASN1Integer(dsaPubKey.getParams().getP()));
vec.add(new ASN1Integer(dsaPubKey.getParams().getQ()));
vec.add(new ASN1Integer(dsaPubKey.getParams().getG()));
ASN1Sequence dssParams = new DERSequence(vec);
try {
return new SubjectPublicKeyInfo(
new AlgorithmIdentifier(X9ObjectIdentifiers.id_dsa, dssParams),
new ASN1Integer(dsaPubKey.getY()));
} catch (IOException ex) {
throw new InvalidKeyException(ex.getMessage(), ex);
}
} else if (publicKey instanceof RSAPublicKey) {
RSAPublicKey rsaPubKey = (RSAPublicKey) publicKey;
try {
return new SubjectPublicKeyInfo(
new AlgorithmIdentifier(PKCSObjectIdentifiers.rsaEncryption,
DERNull.INSTANCE),
new org.bouncycastle.asn1.pkcs.RSAPublicKey(rsaPubKey.getModulus(),
rsaPubKey.getPublicExponent()));
} catch (IOException ex) {
throw new InvalidKeyException(ex.getMessage(), ex);
}
} else if (publicKey instanceof ECPublicKey) {
ECPublicKey ecPubKey = (ECPublicKey) publicKey;
ECParameterSpec paramSpec = ecPubKey.getParams();
ASN1ObjectIdentifier curveOid = detectCurveOid(paramSpec);
if (curveOid == null) {
throw new InvalidKeyException("Cannot find namedCurve of the given public key");
}
java.security.spec.ECPoint pointW = ecPubKey.getW();
BigInteger wx = pointW.getAffineX();
if (wx.signum() != 1) {
throw new InvalidKeyException("Wx is not positive");
}
BigInteger wy = pointW.getAffineY();
if (wy.signum() != 1) {
throw new InvalidKeyException("Wy is not positive");
}
int keysize = (paramSpec.getOrder().bitLength() + 7) / 8;
byte[] wxBytes = wx.toByteArray();
byte[] wyBytes = wy.toByteArray();
byte[] pubKey = new byte[1 + keysize * 2];
pubKey[0] = 4; // uncompressed
int numBytesToCopy = Math.min(wxBytes.length, keysize);
int srcOffset = Math.max(0, wxBytes.length - numBytesToCopy);
int destOffset = 1 + Math.max(0, keysize - wxBytes.length);
System.arraycopy(wxBytes, srcOffset, pubKey, destOffset, numBytesToCopy);
numBytesToCopy = Math.min(wyBytes.length, keysize);
srcOffset = Math.max(0, wyBytes.length - numBytesToCopy);
destOffset = 1 + keysize + Math.max(0, keysize - wyBytes.length);
System.arraycopy(wyBytes, srcOffset, pubKey, destOffset, numBytesToCopy);
AlgorithmIdentifier algId = new AlgorithmIdentifier(X9ObjectIdentifiers.id_ecPublicKey,
curveOid);
return new SubjectPublicKeyInfo(algId, pubKey);
} else {
throw new InvalidKeyException(
"unknown publicKey class " + publicKey.getClass().getName());
}
}
// CHECKSTYLE:SKIP
public static ECPublicKey createECPublicKey(final byte[] encodedAlgorithmIdParameters,
final byte[] encodedPoint)
throws InvalidKeySpecException {
ParamUtil.requireNonNull("encodedAlgorithmIdParameters", encodedAlgorithmIdParameters);
ParamUtil.requireNonNull("encodedPoint", encodedPoint);
ASN1Encodable algParams;
if (encodedAlgorithmIdParameters[0] == 6) {
algParams = ASN1ObjectIdentifier.getInstance(encodedAlgorithmIdParameters);
} else {
algParams = X962Parameters.getInstance(encodedAlgorithmIdParameters);
}
AlgorithmIdentifier algId = new AlgorithmIdentifier(
X9ObjectIdentifiers.id_ecPublicKey, algParams);
SubjectPublicKeyInfo spki = new SubjectPublicKeyInfo(algId, encodedPoint);
X509EncodedKeySpec keySpec;
try {
keySpec = new X509EncodedKeySpec(spki.getEncoded());
} catch (IOException ex) {
throw new InvalidKeySpecException(ex.getMessage(), ex);
}
KeyFactory kf;
try {
kf = KeyFactory.getInstance("EC", "BC");
} catch (NoSuchAlgorithmException | NoSuchProviderException ex) {
throw new InvalidKeySpecException(ex.getMessage(), ex);
}
return (ECPublicKey) kf.generatePublic(keySpec);
}
private static ASN1ObjectIdentifier detectCurveOid(final ECParameterSpec paramSpec) {
org.bouncycastle.jce.spec.ECParameterSpec bcParamSpec =
EC5Util.convertSpec(paramSpec, false);
return ECUtil.getNamedCurveOid(bcParamSpec);
}
}