Java tutorial
/* Copyright (c) 1996-2015, OPC Foundation. All rights reserved. The source code in this file is covered under a dual-license scenario: - RCL: for OPC Foundation members in good-standing - GPL V2: everybody else RCL license terms accompanied with this source code. See http://opcfoundation.org/License/RCL/1.00/ GNU General Public License as published by the Free Software Foundation; version 2 of the License are accompanied with this source code. See http://opcfoundation.org/License/GPLv2 This source code 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. */ package org.opcfoundation.ua.utils; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStreamWriter; import java.io.UnsupportedEncodingException; import java.math.BigInteger; import java.security.GeneralSecurityException; import java.security.PrivateKey; import java.security.PublicKey; import java.security.cert.CertificateParsingException; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Collection; import java.util.Date; import java.util.List; import javax.security.auth.x500.X500Principal; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.bouncycastle.asn1.x500.X500Name; import org.bouncycastle.asn1.x509.AuthorityKeyIdentifier; import org.bouncycastle.asn1.x509.BasicConstraints; import org.bouncycastle.asn1.x509.ExtendedKeyUsage; import org.bouncycastle.asn1.x509.Extension; import org.bouncycastle.asn1.x509.GeneralName; import org.bouncycastle.asn1.x509.GeneralNames; import org.bouncycastle.asn1.x509.KeyPurposeId; import org.bouncycastle.asn1.x509.KeyUsage; import org.bouncycastle.openssl.PEMEncryptor; import org.bouncycastle.openssl.jcajce.JcaPEMWriter; import org.bouncycastle.openssl.jcajce.JcePEMEncryptorBuilder; import org.bouncycastle.operator.ContentSigner; import org.bouncycastle.operator.OperatorCreationException; import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; import org.bouncycastle.util.encoders.Base64; import org.bouncycastle.x509.extension.X509ExtensionUtil; import org.bouncycastle.cert.X509v3CertificateBuilder; import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; import org.bouncycastle.cert.jcajce.JcaX509ExtensionUtils; import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder; import org.bouncycastle.crypto.PBEParametersGenerator; import org.opcfoundation.ua.transport.security.KeyPair; /** * BouncyCastle specific implementations of certain Crypto Utilities. * Called normally from the {@link CryptoUtil} or {@link CertificateUtils} class, * so use those methods instead. * */ public class BouncyCastleUtils { static Logger logger = LoggerFactory.getLogger(BouncyCastleUtils.class); // /** // * Build a V1 certificate to use as a CA root certificate // * @param commonName // * @param days // */ // public static X509Certificate buildV1RootCert(java.security.KeyPair keyPair, // String commonName, long days) throws Exception { // X500Name dn = new X500Name("CN=" + commonName); // Date startDate = new Date(System.currentTimeMillis()); // Date expiryDate = new Date( // System.currentTimeMillis() + days*24*60*60*1000); // BigInteger serialNr = BigInteger.valueOf(System.currentTimeMillis()); // JcaX509v1CertificateBuilder certBldr = new JcaX509v1CertificateBuilder( // dn, serialNr, // startDate, expiryDate, dn, // keyPair.getPublic()); // ContentSigner signer = new JcaContentSignerBuilder("SHA1withRSA") // .setProvider("BC").build(keyPair.getPrivate()); // return new JcaX509CertificateConverter().setProvider("BC") // .getCertificate(certBldr.build(signer)); // } /** * Build a X509 V3 certificate to use as an issuer (CA) certificate. The * certificate does not define OPC UA specific fields, so it cannot be used * for an application instance certificate. * * @param publicKey * the public key to use for the certificate * @param privateKey * the private key corresponding to the publicKey * @param issuerKeys * the certificate and private key of the certificate issuer: if * null a self-signed certificate is created. * @param commonName * the CommonName to use for the subject of the certificate. * @param serialNr * @param startDate * @param expiryDate * @throws OperatorCreationException */ public static X509Certificate generateIssuerCert(PublicKey publicKey, PrivateKey privateKey, KeyPair issuerKeys, String commonName, BigInteger serialNr, Date startDate, Date expiryDate) throws GeneralSecurityException, IOException { JcaX509v3CertificateBuilder certBldr; JcaX509ExtensionUtils extUtils = new JcaX509ExtensionUtils(); AuthorityKeyIdentifier authorityKeyIdentifier; if (issuerKeys == null) { X500Name dn = new X500Name(commonName); certBldr = new JcaX509v3CertificateBuilder(dn, serialNr, startDate, expiryDate, dn, publicKey); authorityKeyIdentifier = extUtils.createAuthorityKeyIdentifier(publicKey); } else { X509Certificate caCert = issuerKeys.getCertificate().getCertificate(); certBldr = new JcaX509v3CertificateBuilder(caCert, serialNr, startDate, expiryDate, new X500Principal(commonName), publicKey); authorityKeyIdentifier = extUtils.createAuthorityKeyIdentifier(caCert); } certBldr.addExtension(Extension.authorityKeyIdentifier, false, authorityKeyIdentifier) .addExtension(Extension.subjectKeyIdentifier, false, extUtils.createSubjectKeyIdentifier(publicKey)) .addExtension(Extension.basicConstraints, true, new BasicConstraints(0)) .addExtension(Extension.keyUsage, true, new KeyUsage(KeyUsage.digitalSignature | KeyUsage.keyCertSign | KeyUsage.cRLSign)); ContentSigner signer; try { signer = new JcaContentSignerBuilder(CertificateUtils.getCertificateSignatureAlgorithm()) .setProvider("BC").build(privateKey); } catch (OperatorCreationException e) { throw new GeneralSecurityException("Failed to sign the certificate", e); } return new JcaX509CertificateConverter().setProvider("BC").getCertificate(certBldr.build(signer)); } /** * Generates a new certificate using the Bouncy Castle implementation. * <p> * The method is used from * {@link CertificateUtils#createApplicationInstanceCertificate(String, String, String, int, String...)} * and * {@link CertificateUtils#renewApplicationInstanceCertificate(String, String, String, int, org.opcfoundation.ua.transport.security.KeyPair, String...)} * * @param domainName * the X500 domain name for the certificate * @param publicKey * the public key of the cert * @param privateKey * the private key of the cert * @param issuerKeys * the certificate and private key of the issuer * @param from * validity start time * @param to * validity end time * @param serialNumber * a unique serial number for the certificate * @param applicationUri * the OPC UA ApplicationUri of the application - added to * SubjectAlternativeName * @param hostNames * the additional host names to ass to SubjectAlternativeName * @return the generated certificate * @throws GeneralSecurityException * if the generation fails * @throws IOException * if the generation fails due to an IO exception */ public static X509Certificate generateCertificate(String domainName, PublicKey publicKey, PrivateKey privateKey, KeyPair issuerKeys, Date from, Date to, BigInteger serial, String applicationUri, String... hostNames) throws IOException, GeneralSecurityException { JcaX509ExtensionUtils extUtils = new JcaX509ExtensionUtils(); X509v3CertificateBuilder certBldr; AuthorityKeyIdentifier authorityKeyIdentifier; PrivateKey signerKey; if (issuerKeys == null) { X500Name dn = new X500Name(domainName); certBldr = new JcaX509v3CertificateBuilder(dn, serial, from, to, dn, publicKey); authorityKeyIdentifier = extUtils.createAuthorityKeyIdentifier(publicKey); signerKey = privateKey; } else { X509Certificate caCert = issuerKeys.getCertificate().getCertificate(); certBldr = new JcaX509v3CertificateBuilder(caCert, serial, from, to, new X500Principal(domainName), publicKey); authorityKeyIdentifier = extUtils.createAuthorityKeyIdentifier(caCert); signerKey = issuerKeys.getPrivateKey().getPrivateKey(); } certBldr.addExtension(Extension.authorityKeyIdentifier, false, authorityKeyIdentifier) .addExtension(Extension.subjectKeyIdentifier, false, extUtils.createSubjectKeyIdentifier(publicKey)) .addExtension(Extension.basicConstraints, false, new BasicConstraints(false)).addExtension( Extension.keyUsage, false, new KeyUsage(KeyUsage.digitalSignature | KeyUsage.keyEncipherment | KeyUsage.nonRepudiation | KeyUsage.dataEncipherment | KeyUsage.keyCertSign)); certBldr.addExtension(Extension.extendedKeyUsage, false, new ExtendedKeyUsage( new KeyPurposeId[] { KeyPurposeId.id_kp_serverAuth, KeyPurposeId.id_kp_clientAuth })); // Vector<KeyPurposeId> extendedKeyUsages = new Vector<KeyPurposeId>(); // extendedKeyUsages.add(KeyPurposeId.id_kp_serverAuth); // extendedKeyUsages.add(KeyPurposeId.id_kp_clientAuth); // certBldr.addExtension(Extension.extendedKeyUsage, false, // new ExtendedKeyUsage(extendedKeyUsages)); // BC 1.49: // certBldr.addExtension(X509Extension.extendedKeyUsage, false, // new ExtendedKeyUsage(new KeyPurposeId[] { // KeyPurposeId.id_kp_serverAuth, // KeyPurposeId.id_kp_clientAuth })); // create the extension value // URI Name List<GeneralName> names = new ArrayList<GeneralName>(); names.add(new GeneralName(GeneralName.uniformResourceIdentifier, applicationUri)); // Add DNS name from ApplicationUri boolean hasDNSName = false; String uriHostName = null; try { String[] appUriParts = applicationUri.split("[:/]"); if (appUriParts.length > 1) { uriHostName = appUriParts[1]; if (!uriHostName.toLowerCase().equals("localhost")) { GeneralName dnsName = new GeneralName(GeneralName.dNSName, uriHostName); names.add(dnsName); hasDNSName = true; } } } catch (Exception e) { logger.warn("Cannot initialize DNS Name to Certificate from ApplicationUri {}", applicationUri); } // Add other DNS Names List<GeneralName> ipAddressNames = new ArrayList<GeneralName>(); if (hostNames != null) for (String hostName : hostNames) { boolean isIpAddress = hostName.matches("^[0-9.]+$"); if (!hostName.equals(uriHostName) && !hostName.toLowerCase().equals("localhost")) { GeneralName dnsName = new GeneralName( hostName.matches("^[0-9.]+$") ? GeneralName.iPAddress : GeneralName.dNSName, hostName); if (isIpAddress) ipAddressNames.add(dnsName); else { names.add(dnsName); hasDNSName = true; } } } // Add IP Addresses, if no host names are defined if (!hasDNSName) for (GeneralName n : ipAddressNames) names.add(n); final GeneralNames subjectAltNames = new GeneralNames(names.toArray(new GeneralName[0])); certBldr.addExtension(Extension.subjectAlternativeName, false, subjectAltNames); //***** generate certificate ***********/ try { ContentSigner signer = new JcaContentSignerBuilder(CertificateUtils.getCertificateSignatureAlgorithm()) .setProvider("BC").build(signerKey); return new JcaX509CertificateConverter().setProvider("BC").getCertificate(certBldr.build(signer)); } catch (OperatorCreationException e) { throw new GeneralSecurityException(e); } } /** * @param key certificate of private key * @param savePath * @param password * @param algorithm * @throws FileNotFoundException * @throws IOException */ public static void writeToPem(Object key, File savePath, String password, String algorithm) throws IOException { CryptoUtil.getSecurityProviderName(); JcaPEMWriter pemWrt = new JcaPEMWriter( new OutputStreamWriter(new FileOutputStream(savePath.getCanonicalPath()))); if (password == null) pemWrt.writeObject(key); else { char[] pw = password.toCharArray(); PEMEncryptor encryptor = new JcePEMEncryptorBuilder(algorithm).setSecureRandom(CryptoUtil.getRandom()) .build(pw); pemWrt.writeObject(key, encryptor); } pemWrt.close(); } public static byte[] base64Decode(String string) { return Base64.decode(string); } public static String base64Encode(byte[] bytes) { try { return new String(Base64.encode(bytes), "UTF-8"); } catch (UnsupportedEncodingException e) { throw new RuntimeException(e); } } @SuppressWarnings("unchecked") public static Collection<List<?>> getSubjectAlternativeNames(X509Certificate cert) throws CertificateParsingException { return X509ExtensionUtil.getSubjectAlternativeNames(cert); // try { // byte[] ext = cert.getExtensionValue(SUBJECT_ALT_NAME_OID); // if (ext == null) // return null; // DerValue val = new DerValue(ext); // byte[] data = val.getOctetString(); // // SubjectAlternativeNameExtension subjectAltNameExt = new SubjectAlternativeNameExtension( // Boolean.FALSE, data); // // GeneralNames names; // try { // names = (GeneralNames) subjectAltNameExt // .get(SubjectAlternativeNameExtension.SUBJECT_NAME); // } catch (IOException ioe) { // // should not occur // return Collections.<List<?>> emptySet(); // } // return makeAltNames(names); // } catch (IOException ioe) { // CertificateParsingException cpe = new CertificateParsingException(); // cpe.initCause(ioe); // throw cpe; // } } /** * Converts a password to a byte array according to the scheme in PKCS5 (ascii, no padding) * @param password a character array representing the password. * @return a byte array representing the password. */ public static byte[] PKCS5PasswordToBytes(char[] password) { return PBEParametersGenerator.PKCS5PasswordToBytes(password); } }