/** * 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.jooby.internal.ssl; import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; import java.nio.ByteBuffer; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.KeyException; import java.security.KeyFactory; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.cert.Certificate; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import java.security.spec.InvalidKeySpecException; import java.security.spec.PKCS8EncodedKeySpec; import java.util.ArrayList; import java.util.List; import javax.crypto.Cipher; import javax.crypto.EncryptedPrivateKeyInfo; import javax.crypto.NoSuchPaddingException; import javax.crypto.SecretKey; import javax.crypto.SecretKeyFactory; import javax.crypto.spec.PBEKeySpec; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLException; import javax.net.ssl.SSLSessionContext; import javax.net.ssl.TrustManagerFactory; import javax.security.auth.x500.X500Principal; /** * A secure socket protocol implementation which acts as a factory for {@link SSLEngine} and * {@link SslHandler}. * Internally, it is implemented via JDK's {@link SSLContext} or OpenSSL's {@code SSL_CTX}. * * <h3>Making your server support SSL/TLS</h3> * <pre> * // In your {@link ChannelInitializer}: * {@link ChannelPipeline} p = channel.pipeline(); * {@link SslContext} sslCtx = {@link SslContextBuilder#forServer(File, File) SslContextBuilder.forServer(...)}.build(); * p.addLast("ssl", {@link #newEngine(ByteBufAllocator) sslCtx.newEngine(channel.alloc())}); * ... * </pre> * * <h3>Making your client support SSL/TLS</h3> * <pre> * // In your {@link ChannelInitializer}: * {@link ChannelPipeline} p = channel.pipeline(); * {@link SslContext} sslCtx = {@link SslContextBuilder#forClient() SslContextBuilder.forClient()}.build(); * p.addLast("ssl", {@link #newEngine(ByteBufAllocator, String, int) sslCtx.newEngine(channel.alloc(), host, port)}); * ... * </pre> * * Kindly Borrowed from <a href="http://netty.io">Netty</a> */ public abstract class SslContext { static final CertificateFactory X509_CERT_FACTORY; static { try { X509_CERT_FACTORY = CertificateFactory.getInstance("X.509"); } catch (CertificateException e) { throw new IllegalStateException("unable to instance X.509 CertificateFactory", e); } } public static SslContext newServerContextInternal( final File trustCertChainFile, final File keyCertChainFile, final File keyFile, final String keyPassword, final long sessionCacheSize, final long sessionTimeout) throws SSLException { return new JdkSslServerContext(trustCertChainFile, keyCertChainFile, keyFile, keyPassword, sessionCacheSize, sessionTimeout); } /** * Returns the size of the cache used for storing SSL session objects. */ public abstract long sessionCacheSize(); public abstract long sessionTimeout(); public abstract SSLContext context(); /** * Returns the {@link SSLSessionContext} object held by this context. */ public abstract SSLSessionContext sessionContext(); /** * Generates a key specification for an (encrypted) private key. * * @param password characters, if {@code null} or empty an unencrypted key is assumed * @param key bytes of the DER encoded private key * * @return a key specification * * @throws IOException if parsing {@code key} fails * @throws NoSuchAlgorithmException if the algorithm used to encrypt {@code key} is unkown * @throws NoSuchPaddingException if the padding scheme specified in the decryption algorithm is * unkown * @throws InvalidKeySpecException if the decryption key based on {@code password} cannot be * generated * @throws InvalidKeyException if the decryption key based on {@code password} cannot be used to * decrypt * {@code key} * @throws InvalidAlgorithmParameterException if decryption algorithm parameters are somehow * faulty */ protected static PKCS8EncodedKeySpec generateKeySpec(final char[] password, final byte[] key) throws IOException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeySpecException, InvalidKeyException, InvalidAlgorithmParameterException { if (password == null || password.length == 0) { return new PKCS8EncodedKeySpec(key); } EncryptedPrivateKeyInfo encryptedPrivateKeyInfo = new EncryptedPrivateKeyInfo(key); SecretKeyFactory keyFactory = SecretKeyFactory .getInstance(encryptedPrivateKeyInfo.getAlgName()); PBEKeySpec pbeKeySpec = new PBEKeySpec(password); SecretKey pbeKey = keyFactory.generateSecret(pbeKeySpec); Cipher cipher = Cipher.getInstance(encryptedPrivateKeyInfo.getAlgName()); cipher.init(Cipher.DECRYPT_MODE, pbeKey, encryptedPrivateKeyInfo.getAlgParameters()); return encryptedPrivateKeyInfo.getKeySpec(cipher); } /** * Generates a new {@link KeyStore}. * * @param certChainFile a X.509 certificate chain file in PEM format, * @param keyFile a PKCS#8 private key file in PEM format, * @param keyPasswordChars the password of the {@code keyFile}. * {@code null} if it's not password-protected. * @return generated {@link KeyStore}. */ static KeyStore buildKeyStore(final File certChainFile, final File keyFile, final char[] keyPasswordChars) throws KeyStoreException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeySpecException, InvalidAlgorithmParameterException, CertificateException, KeyException, IOException { ByteBuffer encodedKeyBuf = PemReader.readPrivateKey(keyFile); byte[] encodedKey = encodedKeyBuf.array(); PKCS8EncodedKeySpec encodedKeySpec = generateKeySpec(keyPasswordChars, encodedKey); PrivateKey key; try { key = KeyFactory.getInstance("RSA").generatePrivate(encodedKeySpec); } catch (InvalidKeySpecException ignore) { try { key = KeyFactory.getInstance("DSA").generatePrivate(encodedKeySpec); } catch (InvalidKeySpecException ignore2) { try { key = KeyFactory.getInstance("EC").generatePrivate(encodedKeySpec); } catch (InvalidKeySpecException e) { throw new InvalidKeySpecException("Neither RSA, DSA nor EC worked", e); } } } CertificateFactory cf = CertificateFactory.getInstance("X.509"); List<ByteBuffer> certs = PemReader.readCertificates(certChainFile); List<Certificate> certChain = new ArrayList<Certificate>(certs.size()); for (ByteBuffer buf : certs) { certChain.add(cf.generateCertificate(new ByteArrayInputStream(buf.array()))); } KeyStore ks = KeyStore.getInstance("JKS"); ks.load(null, null); ks.setKeyEntry("key", key, keyPasswordChars, certChain.toArray(new Certificate[certChain.size()])); return ks; } /** * Build a {@link TrustManagerFactory} from a certificate chain file. * * @param certChainFile The certificate file to build from. * @param trustManagerFactory The existing {@link TrustManagerFactory} that will be used if not * {@code null}. * @return A {@link TrustManagerFactory} which contains the certificates in {@code certChainFile} */ protected static TrustManagerFactory buildTrustManagerFactory(final File certChainFile, TrustManagerFactory trustManagerFactory) throws NoSuchAlgorithmException, CertificateException, KeyStoreException, IOException { KeyStore ks = KeyStore.getInstance("JKS"); ks.load(null, null); CertificateFactory cf = CertificateFactory.getInstance("X.509"); List<ByteBuffer> certs = PemReader.readCertificates(certChainFile); for (ByteBuffer buf : certs) { X509Certificate cert = (X509Certificate) cf .generateCertificate(new ByteArrayInputStream(buf.array())); X500Principal principal = cert.getSubjectX500Principal(); ks.setCertificateEntry(principal.getName("RFC2253"), cert); } // Set up trust manager factory to use our key store. if (trustManagerFactory == null) { trustManagerFactory = TrustManagerFactory .getInstance(TrustManagerFactory.getDefaultAlgorithm()); } trustManagerFactory.init(ks); return trustManagerFactory; } }