blob: 03bd3fa782bd703d62bec8bdc620d23c396668cb [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.knox.gateway.util;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.math.BigInteger;
import java.net.InetAddress;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.security.KeyPair;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.cert.Certificate;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Date;
import org.apache.commons.codec.binary.Base64;
import org.apache.knox.gateway.i18n.GatewayUtilCommonMessages;
import org.apache.knox.gateway.i18n.messages.MessagesFactory;
public class X509CertificateUtil {
private static GatewayUtilCommonMessages LOG = MessagesFactory.get(GatewayUtilCommonMessages.class);
/**
* Create a self-signed X.509 Certificate
* @param dn the X.509 Distinguished Name, eg "CN=Test, L=London, C=GB"
* @param pair the KeyPair
* @param days how many days from now the Certificate is valid for
* @param algorithm the signing algorithm, eg "SHA1withRSA"
* @return self-signed X.509 certificate
*/
public static X509Certificate generateCertificate(String dn, KeyPair pair, int days, String algorithm) {
PrivateKey privkey = pair.getPrivate();
Object x509CertImplObject = null;
try {
Date from = new Date();
Date to = new Date(from.getTime() + days * 86400000L);
Class<?> certInfoClass = Class.forName(getX509CertInfoModuleName());
Constructor<?> certInfoConstr = certInfoClass.getConstructor();
Object certInfoObject = certInfoConstr.newInstance();
// CertificateValidity interval = new CertificateValidity(from, to);
Class<?> certValidityClass = Class.forName(getX509CertifValidityModuleName());
Constructor<?> certValidityConstr = certValidityClass.getConstructor(Date.class, Date.class);
Object certValidityObject = certValidityConstr.newInstance(from, to);
BigInteger sn = new BigInteger(64, new SecureRandom());
// X500Name owner = new X500Name(dn);
Class<?> x500NameClass = Class.forName(getX509X500NameModuleName());
Constructor<?> x500NameConstr = x500NameClass.getConstructor(String.class);
Object x500NameObject = x500NameConstr.newInstance(dn);
Method methodSET = certInfoObject.getClass().getMethod("set", String.class, Object.class);
// info.set(X509CertInfo.VALIDITY, interval);
methodSET.invoke(certInfoObject, getSetField(certInfoObject, "VALIDITY"),certValidityObject);
// info.set(X509CertInfo.SERIAL_NUMBER, new CertificateSerialNumber(sn));
Class<?> certificateSerialNumberClass = Class.forName(getCertificateSerialNumberModuleName());
Constructor<?> certificateSerialNumberConstr = certificateSerialNumberClass
.getConstructor(BigInteger.class);
Object certificateSerialNumberObject = certificateSerialNumberConstr.newInstance(sn);
methodSET.invoke(certInfoObject, getSetField(certInfoObject, "SERIAL_NUMBER"),
certificateSerialNumberObject);
// info.set(X509CertInfo.SUBJECT, new CertificateSubjectName(owner));
try {
Class<?> certificateSubjectNameClass = Class.forName(getCertificateSubjectNameModuleName());
Constructor<?> certificateSubjectNameConstr = certificateSubjectNameClass
.getConstructor(x500NameClass);
Object certificateSubjectNameObject = certificateSubjectNameConstr
.newInstance(x500NameObject);
methodSET.invoke(certInfoObject, getSetField(certInfoObject, "SUBJECT"),
certificateSubjectNameObject);
}
catch (InvocationTargetException ite) {
methodSET.invoke(certInfoObject, getSetField(certInfoObject, "SUBJECT"),
x500NameObject);
}
// info.set(X509CertInfo.ISSUER, new CertificateIssuerName(owner));
try {
Class<?> certificateIssuerNameClass = Class.forName(getCertificateIssuerNameModuleName());
Constructor<?> certificateIssuerNameConstr = certificateIssuerNameClass
.getConstructor(x500NameClass);
Object certificateIssuerNameObject = certificateIssuerNameConstr.newInstance(x500NameObject);
methodSET.invoke(certInfoObject, getSetField(certInfoObject, "ISSUER"),
certificateIssuerNameObject);
}
catch (InvocationTargetException ite) {
methodSET.invoke(certInfoObject, getSetField(certInfoObject, "ISSUER"),
x500NameObject);
}
// info.set(X509CertInfo.KEY, new CertificateX509Key(pair.getPublic()));
Class<?> certificateX509KeyClass = Class.forName(getCertificateX509KeyModuleName());
Constructor<?> certificateX509KeyConstr = certificateX509KeyClass
.getConstructor(PublicKey.class);
Object certificateX509KeyObject = certificateX509KeyConstr.newInstance(pair.getPublic());
methodSET.invoke(certInfoObject, getSetField(certInfoObject, "KEY"),
certificateX509KeyObject);
// info.set(X509CertInfo.VERSION, new CertificateVersion(CertificateVersion.V3));
Class<?> certificateVersionClass = Class.forName(getCertificateVersionModuleName());
Constructor<?> certificateVersionConstr = certificateVersionClass.getConstructor(int.class);
Constructor<?> certificateVersionConstr0 = certificateVersionClass.getConstructor();
Object certInfoObject0 = certificateVersionConstr0.newInstance();
Field v3IntField = certInfoObject0.getClass().getDeclaredField("V3");
v3IntField.setAccessible(true);
int fValue = v3IntField.getInt(certInfoObject0);
Object certificateVersionObject = certificateVersionConstr.newInstance(fValue);
methodSET.invoke(certInfoObject, getSetField(certInfoObject, "VERSION"),
certificateVersionObject);
// AlgorithmId algo = new AlgorithmId(AlgorithmId.md5WithRSAEncryption_oid);
Class<?> algorithmIdClass = Class.forName(getAlgorithmIdModuleName());
Field md5WithRSAField = algorithmIdClass.getDeclaredField("md5WithRSAEncryption_oid");
md5WithRSAField.setAccessible(true);
Class<?> objectIdentifierClass = Class.forName(getObjectIdentifierModuleName());
Object md5WithRSAValue = md5WithRSAField.get(algorithmIdClass);
Constructor<?> algorithmIdConstr = algorithmIdClass.getConstructor(objectIdentifierClass);
Object algorithmIdObject = algorithmIdConstr.newInstance(md5WithRSAValue);
// info.set(X509CertInfo.ALGORITHM_ID, new CertificateAlgorithmId(algo));
Class<?> certificateAlgorithmIdClass = Class.forName(getCertificateAlgorithmIdModuleName());
Constructor<?> certificateAlgorithmIdConstr = certificateAlgorithmIdClass
.getConstructor(algorithmIdClass);
Object certificateAlgorithmIdObject = certificateAlgorithmIdConstr
.newInstance(algorithmIdObject);
methodSET.invoke(certInfoObject, getSetField(certInfoObject, "ALGORITHM_ID"),
certificateAlgorithmIdObject);
// Set the SAN extension
Class<?> generalNameInterfaceClass = Class.forName(getGeneralNameInterfaceModuleName());
Class<?> generalNameClass = Class.forName(getGeneralNameModuleName());
Constructor<?> generalNameConstr = generalNameClass.getConstructor(generalNameInterfaceClass);
// GeneralNames generalNames = new GeneralNames();
Class<?> generalNamesClass = Class.forName(getGeneralNamesModuleName());
Constructor<?> generalNamesConstr = generalNamesClass.getConstructor();
Object generalNamesObject = generalNamesConstr.newInstance();
Method generalNamesAdd = generalNamesObject.getClass().getMethod("add", generalNameClass);
Class<?> dnsNameClass = Class.forName(getDNSNameModuleName());
Constructor<?> dnsNameConstr = dnsNameClass.getConstructor(String.class);
// Pull the hostname out of the DN
String hostname = dn.split(",", 2)[0].split("=", 2)[1];
if("localhost".equals(hostname)) {
String detectedHostname = InetAddress.getLocalHost().getHostName();
// DNSName dnsName = new DNSName(detectedHostname);
Object dnsNameObject = dnsNameConstr.newInstance(detectedHostname);
// GeneralName generalName = new GeneralName(dnsName);
Object generalNameObject = generalNameConstr.newInstance(dnsNameObject);
// generalNames.add(generalName);
generalNamesAdd.invoke(generalNamesObject, generalNameObject);
}
// DNSName dnsName = new DNSName(hostname);
Object dnsNameObject = dnsNameConstr.newInstance(hostname);
// GeneralName generalName = new GeneralName(dnsName);
Object generalNameObject = generalNameConstr.newInstance(dnsNameObject);
// generalNames.add(generalName);
generalNamesAdd.invoke(generalNamesObject, generalNameObject);
// SubjectAlternativeNameExtension san = new SubjectAlternativeNameExtension(generalNames);
Class<?> subjectAlternativeNameExtensionClass = Class.forName(
getSubjectAlternativeNameExtensionModuleName());
Constructor<?> subjectAlternativeNameExtensionConstr =
subjectAlternativeNameExtensionClass.getConstructor(generalNamesClass);
Object subjectAlternativeNameExtensionObject = subjectAlternativeNameExtensionConstr
.newInstance(generalNamesObject);
// CertificateExtensions certificateExtensions = new CertificateExtensions();
Class<?> certificateExtensionsClass = Class.forName(getCertificateExtensionsModuleName());
Constructor<?> certificateExtensionsConstr = certificateExtensionsClass.getConstructor();
Object certificateExtensionsObject = certificateExtensionsConstr.newInstance();
// certificateExtensions.set(san.getExtensionId().toString(), san);
Method getExtensionIdMethod = subjectAlternativeNameExtensionObject.getClass()
.getMethod("getExtensionId");
String sanExtensionId = getExtensionIdMethod.invoke(subjectAlternativeNameExtensionObject)
.toString();
Method certificateExtensionsSet = certificateExtensionsObject.getClass().getMethod("set",
String.class, Object.class);
certificateExtensionsSet.invoke(certificateExtensionsObject, sanExtensionId,
subjectAlternativeNameExtensionObject);
// info.set(X509CertInfo.EXTENSIONS, certificateExtensions);
methodSET.invoke(certInfoObject, getSetField(certInfoObject, "EXTENSIONS"),
certificateExtensionsObject);
// Sign the cert to identify the algorithm that's used.
// X509CertImpl cert = new X509CertImpl(info);
Class<?> x509CertImplClass = Class.forName(getX509CertImplModuleName());
Constructor<?> x509CertImplConstr = x509CertImplClass.getConstructor(certInfoClass);
x509CertImplObject = x509CertImplConstr.newInstance(certInfoObject);
// cert.sign(privkey, algorithm);
Method methoSIGN = x509CertImplObject.getClass().getMethod("sign",
PrivateKey.class, String.class);
methoSIGN.invoke(x509CertImplObject, privkey, algorithm);
// Update the algorith, and resign.
// algo = (AlgorithmId)cert.get(X509CertImpl.SIG_ALG);
Method methoGET = x509CertImplObject.getClass().getMethod("get", String.class);
String sig_alg = getSetField(x509CertImplObject, "SIG_ALG");
String certAlgoIdNameValue = getSetField(certificateAlgorithmIdObject, "NAME");
String certAlgoIdAlgoValue = getSetField(certificateAlgorithmIdObject, "ALGORITHM");
// info.set(CertificateAlgorithmId.NAME + "." + CertificateAlgorithmId.ALGORITHM, algo);
methodSET.invoke(certInfoObject, certAlgoIdNameValue + "." + certAlgoIdAlgoValue,
methoGET.invoke(x509CertImplObject, sig_alg));
// cert = new X509CertImpl(info);
x509CertImplObject = x509CertImplConstr.newInstance(certInfoObject);
// cert.sign(privkey, algorithm);
methoSIGN.invoke(x509CertImplObject, privkey, algorithm);
} catch (Exception e) {
LOG.failedToGenerateCertificate(e);
}
return (X509Certificate) x509CertImplObject;
}
private static String getX509CertInfoModuleName() {
return System.getProperty("java.vendor").contains("IBM") ? "com.ibm.security.x509.X509CertInfo"
: "sun.security.x509.X509CertInfo";
}
private static String getX509CertifValidityModuleName() {
return System.getProperty("java.vendor").contains("IBM") ?
"com.ibm.security.x509.CertificateValidity" :
"sun.security.x509.CertificateValidity";
}
private static String getX509X500NameModuleName() {
return System.getProperty("java.vendor").contains("IBM") ?
"com.ibm.security.x509.X500Name" :
"sun.security.x509.X500Name";
}
private static String getCertificateSerialNumberModuleName() {
return System.getProperty("java.vendor").contains("IBM") ?
"com.ibm.security.x509.CertificateSerialNumber" :
"sun.security.x509.CertificateSerialNumber";
}
private static String getCertificateSubjectNameModuleName() {
return System.getProperty("java.vendor").contains("IBM") ?
"com.ibm.security.x509.CertificateSubjectName" :
"sun.security.x509.CertificateSubjectName";
}
private static String getCertificateIssuerNameModuleName() {
return System.getProperty("java.vendor").contains("IBM") ?
"com.ibm.security.x509.CertificateIssuerName" :
"sun.security.x509.CertificateIssuerName";
}
private static String getCertificateX509KeyModuleName() {
return System.getProperty("java.vendor").contains("IBM") ?
"com.ibm.security.x509.CertificateX509Key" :
"sun.security.x509.CertificateX509Key";
}
private static String getCertificateVersionModuleName() {
return System.getProperty("java.vendor").contains("IBM") ?
"com.ibm.security.x509.CertificateVersion" :
"sun.security.x509.CertificateVersion";
}
private static String getAlgorithmIdModuleName() {
return System.getProperty("java.vendor").contains("IBM") ?
"com.ibm.security.x509.AlgorithmId" :
"sun.security.x509.AlgorithmId";
}
private static String getObjectIdentifierModuleName() {
return System.getProperty("java.vendor").contains("IBM") ?
"com.ibm.security.util.ObjectIdentifier" :
"sun.security.util.ObjectIdentifier";
}
private static String getCertificateAlgorithmIdModuleName() {
return System.getProperty("java.vendor").contains("IBM") ?
"com.ibm.security.x509.CertificateAlgorithmId" :
"sun.security.x509.CertificateAlgorithmId";
}
private static String getGeneralNameInterfaceModuleName() {
return System.getProperty("java.vendor").contains("IBM") ?
"com.ibm.security.x509.GeneralNameInterface" :// TODO
"sun.security.x509.GeneralNameInterface";
}
private static String getGeneralNameModuleName() {
return System.getProperty("java.vendor").contains("IBM") ?
"com.ibm.security.x509.GeneralName" : // TODO
"sun.security.x509.GeneralName";
}
private static String getGeneralNamesModuleName() {
return System.getProperty("java.vendor").contains("IBM") ?
"com.ibm.security.x509.GeneralNames" : // TODO
"sun.security.x509.GeneralNames";
}
private static String getDNSNameModuleName() {
return System.getProperty("java.vendor").contains("IBM") ?
"com.ibm.security.x509.DNSName" : // TODO
"sun.security.x509.DNSName";
}
private static String getSubjectAlternativeNameExtensionModuleName() {
return System.getProperty("java.vendor").contains("IBM") ?
"com.ibm.security.x509.SubjectAlternativeNameExtension" : // TODO
"sun.security.x509.SubjectAlternativeNameExtension";
}
private static String getCertificateExtensionsModuleName() {
return System.getProperty("java.vendor").contains("IBM") ?
"com.ibm.security.x509.CertificateExtensions" : // TODO
"sun.security.x509.CertificateExtensions";
}
private static String getX509CertImplModuleName() {
return System.getProperty("java.vendor").contains("IBM") ?
"com.ibm.security.x509.X509CertImpl" :
"sun.security.x509.X509CertImpl";
}
private static String getSetField(Object obj, String setString)
throws Exception {
Field privateStringField = obj.getClass().getDeclaredField(setString);
privateStringField.setAccessible(true);
return (String) privateStringField.get(obj);
}
public static void writeCertificateToFile(Certificate cert, final File file)
throws CertificateEncodingException, IOException {
byte[] bytes = cert.getEncoded();
Base64 encoder = new Base64( 76, "\n".getBytes( StandardCharsets.US_ASCII ) );
try(OutputStream out = Files.newOutputStream(file.toPath()) ) {
out.write( "-----BEGIN CERTIFICATE-----\n".getBytes( StandardCharsets.US_ASCII ) );
out.write( encoder.encodeToString( bytes ).getBytes( StandardCharsets.US_ASCII ) );
out.write( "-----END CERTIFICATE-----\n".getBytes( StandardCharsets.US_ASCII ) );
}
}
/*
* Writes one certificate into the given keystore file protected by the default password
*/
private static void writeCertificateToKeyStore(Certificate cert, final File file, String type)
throws IOException, KeyStoreException, NoSuchAlgorithmException, CertificateException {
writeCertificateToKeyStore(cert, file, type, null);
}
/*
* Writes one certificate into the given keystore file protected by the given password
*/
private static void writeCertificateToKeyStore(Certificate cert, final File file, String type, String keystorePassword)
throws IOException, KeyStoreException, NoSuchAlgorithmException, CertificateException {
writeCertificatesToKeyStore(new Certificate[] { cert }, file, type, keystorePassword);
}
/*
* Writes an arbitrary number of certificates into the given keystore file protected by the given password
*/
private static void writeCertificatesToKeyStore(Certificate[] certs, final File file, String type, String keystorePassword)
throws IOException, KeyStoreException, NoSuchAlgorithmException, CertificateException {
if (certs != null) {
KeyStore ks = KeyStore.getInstance(type);
char[] password = keystorePassword == null ? "changeit".toCharArray() : keystorePassword.toCharArray();
ks.load(null, password);
int counter = 0;
for (Certificate cert : certs) {
ks.setCertificateEntry("gateway-identity" + (++counter), cert); //it really does not matter what we set as alias for the certificate
}
/* Coverity Scan CID 1361992 */
try (OutputStream fos = Files.newOutputStream(file.toPath())) {
ks.store(fos, password);
}
}
}
public static void writeCertificateToJks(Certificate cert, final File file)
throws IOException, KeyStoreException, NoSuchAlgorithmException, CertificateException {
writeCertificateToKeyStore(cert, file, "jks");
}
public static void writeCertificateToJks(Certificate cert, final File file, String keystorePassword)
throws IOException, KeyStoreException, NoSuchAlgorithmException, CertificateException {
writeCertificateToKeyStore(cert, file, "jks", keystorePassword);
}
public static void writeCertificatesToJks(Certificate[] certs, final File file, String keystorePassword)
throws IOException, KeyStoreException, NoSuchAlgorithmException, CertificateException {
writeCertificatesToKeyStore(certs, file, "jks", keystorePassword);
}
public static void writeCertificateToJceks(Certificate cert, final File file)
throws IOException, KeyStoreException, NoSuchAlgorithmException, CertificateException {
writeCertificateToKeyStore(cert, file, "jceks");
}
public static void writeCertificateToPkcs12(Certificate cert, final File file)
throws IOException, KeyStoreException, NoSuchAlgorithmException, CertificateException {
writeCertificateToKeyStore(cert, file, "pkcs12");
}
/**
* Tests the X509 certificate to see if it was self-signed.
* <p>
* The certificate is determined to be self-signed if the subject DN is the same as the issuer DN
*
* @param certificate the {@link X509Certificate} to test
* @return <code>true</code> if the X509 certficate is self-signed; otherwise <code>false</code>
*/
public static boolean isSelfSignedCertificate(Certificate certificate) {
if (certificate instanceof X509Certificate) {
X509Certificate x509Certificate = (X509Certificate) certificate;
return x509Certificate.getSubjectDN().equals(x509Certificate.getIssuerDN());
} else {
return false;
}
}
}