| /* |
| * 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); |
| |
| boolean generalNameAdded = false; |
| // Pull the hostname out of the DN |
| String hostname = dn.split(",", 2)[0].split("=", 2)[1]; |
| if("localhost".equals(hostname)) { |
| // Add short hostname |
| String detectedHostname = InetAddress.getLocalHost().getHostName(); |
| if (Character.isAlphabetic(detectedHostname.charAt(0))) { |
| // 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); |
| generalNameAdded = true; |
| } |
| |
| // Add fully qualified hostname |
| String detectedFullyQualifiedHostname = InetAddress.getLocalHost().getCanonicalHostName(); |
| if (Character.isAlphabetic(detectedFullyQualifiedHostname.charAt(0))) { |
| // DNSName dnsName = new DNSName(detectedFullyQualifiedHostname); |
| Object fullyQualifiedDnsNameObject = dnsNameConstr.newInstance(detectedFullyQualifiedHostname); |
| // GeneralName generalName = new GeneralName(fullyQualifiedDnsNameObject); |
| Object fullyQualifiedGeneralNameObject = generalNameConstr.newInstance(fullyQualifiedDnsNameObject); |
| // generalNames.add(fullyQualifiedGeneralNameObject); |
| generalNamesAdd.invoke(generalNamesObject, fullyQualifiedGeneralNameObject); |
| generalNameAdded = true; |
| } |
| } |
| |
| if (Character.isAlphabetic(hostname.charAt(0))) { |
| // 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); |
| generalNameAdded = true; |
| } |
| |
| if (generalNameAdded) { |
| // 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; |
| } |
| } |
| } |