/* * The MIT License * * Copyright 2014 Karol Bucek. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package org.jruby.ext.openssl; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.Reader; import java.io.Writer; import java.security.GeneralSecurityException; import java.security.Key; import java.security.KeyFactory; import java.security.KeyPair; import java.security.KeyStore; import java.security.Provider; import java.security.SecureRandom; import java.security.cert.Certificate; import java.security.spec.AlgorithmParameterSpec; import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.X509EncodedKeySpec; import java.util.Collection; import javax.crypto.SecretKey; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.RC2ParameterSpec; import javax.crypto.spec.SecretKeySpec; import org.bouncycastle.crypto.PBEParametersGenerator; import org.bouncycastle.crypto.generators.OpenSSLPBEParametersGenerator; import org.bouncycastle.crypto.params.KeyParameter; //import org.bouncycastle.openssl.MiscPEMGenerator; import org.bouncycastle.openssl.PEMWriter; import org.bouncycastle.operator.OperatorCreationException; import org.jruby.ext.openssl.impl.pem.MiscPEMGenerator; import org.jruby.ext.openssl.impl.pem.PEMDecryptor; import org.jruby.ext.openssl.impl.pem.PEMDecryptorProvider; import org.jruby.ext.openssl.impl.pem.PEMEncryptedKeyPair; import org.jruby.ext.openssl.impl.pem.PEMException; import org.jruby.ext.openssl.impl.pem.PEMKeyPair; import org.jruby.ext.openssl.impl.pem.PEMParser; import org.jruby.ext.openssl.util.ByteArrayOutputStream; //import org.bouncycastle.util.io.pem.PemReader; import static org.jruby.ext.openssl.x509store.PEMInputOutput.getKeyFactory; /** * PEM Utilities, for now mostly to replace {@link PEMHandler}. * * @author kares */ public abstract class PEMUtils { /* private static boolean bcPEMParser; private static Class<?> pemReaderImpl; private static Reader newPemReader(final Reader reader) { if ( pemReaderImpl == null ) { synchronized(BouncyCastlePEMHandler.class) { if ( pemReaderImpl == null ) { try { pemReaderImpl = Class.forName("org.bouncycastle.openssl.PEMParser"); bcPEMParser = true; } catch (ClassNotFoundException ex) { pemReaderImpl = org.jruby.ext.openssl.impl.pem.PEMParser.class; } } } } try { Constructor<? extends PemReader> constructor = (Constructor<? extends PemReader>) pemReaderImpl.getConstructor(new Class[] { Reader.class }); return constructor.newInstance(reader); } catch (NoSuchMethodException e) { throw new IllegalStateException(e.getMessage(), e); } //catch (InstantiationException e) { //} catch (InvocationTargetException e) { throw new IllegalStateException(e.getTargetException()); } catch (Exception e) { if ( e instanceof RuntimeException ) throw (RuntimeException) e; throw new IllegalStateException(e); } } private static Object doInvoke(Object obj, String methodName, Class<?>[] paramTypes, Object... params) throws IOException { final Method method; try { method = obj.getClass().getDeclaredMethod(methodName, paramTypes); method.setAccessible(true); return method.invoke(obj, params); } catch (NoSuchMethodException e) { throw new IllegalStateException(e.getMessage(), e); } catch (InvocationTargetException e) { final Throwable target = e.getTargetException(); if ( target instanceof IOException ) throw (IOException) target; if ( target instanceof RuntimeException ) throw (RuntimeException) target; throw new IllegalStateException(target); } catch (Exception e) { if ( e instanceof IOException ) throw (IOException) e; if ( e instanceof RuntimeException ) throw (RuntimeException) e; throw new IllegalStateException(e); } } */ public static KeyPair readKeyPair(final Reader reader) throws IOException { return readKeyPair(reader, null); } public static KeyPair readKeyPair(final Reader reader, final char[] password) throws IOException { PEMKeyPair pemKeyPair = readInternal(reader, password); return toKeyPair(pemKeyPair); } static PEMKeyPair readInternal(final Reader reader, final char[] password) throws IOException { Object keyPair = new PEMParser(reader).readObject(); if ( keyPair instanceof PEMEncryptedKeyPair ) { return ((PEMEncryptedKeyPair) keyPair).decryptKeyPair(new PEMDecryptorImpl(password)); } return (PEMKeyPair) keyPair; } private static KeyPair toKeyPair(final PEMKeyPair pemKeyPair) throws IOException { try { KeyFactory keyFactory = getKeyFactory( pemKeyPair.getPrivateKeyInfo().getPrivateKeyAlgorithm() ); return new KeyPair( keyFactory.generatePublic( new X509EncodedKeySpec( pemKeyPair.getPublicKeyInfo().getEncoded() ) ), keyFactory.generatePrivate( new PKCS8EncodedKeySpec( pemKeyPair.getPrivateKeyInfo().getEncoded() ) ) ); } catch (Exception e) { throw new PEMException("unable to convert key pair: " + e.getMessage(), e); } } public static void writePEM(final Writer writer, final Object obj, final String algorithm, final char[] password) throws IOException { final PEMWriter pemWriter = new PEMWriter(writer); final SecureRandom random = SecurityHelper.getSecureRandom(); pemWriter.writeObject(MiscPEMGenerator.newInstance(obj, algorithm, password, random)); pemWriter.flush(); } public static void writePEM(final Writer writer, final Object obj) throws IOException { writePEM(writer, obj, null, null); } public static byte[] generatePKCS12(final Reader keyReader, final byte[] cert, final String aliasName, final char[] password) throws IOException, GeneralSecurityException { final Collection<? extends Certificate> certChain = SecurityHelper.getCertificateFactory("X.509").generateCertificates(new ByteArrayInputStream(cert)); final PEMKeyPair pemKeyPair = readInternal(keyReader, null); final KeyFactory keyFactory = getKeyFactory( pemKeyPair.getPrivateKeyInfo().getPrivateKeyAlgorithm() ); Key privateKey = keyFactory.generatePrivate( new PKCS8EncodedKeySpec( pemKeyPair.getPrivateKeyInfo().getEncoded() ) ); final KeyStore keyStore = SecurityHelper.getKeyStore("PKCS12"); keyStore.load(null, null); keyStore.setKeyEntry( aliasName, privateKey, null, certChain.toArray(new Certificate[certChain.size()]) ); final ByteArrayOutputStream pkcs12Out = new ByteArrayOutputStream(); keyStore.store(pkcs12Out, password == null ? new char[0] : password); return pkcs12Out.toByteArray(); } private static class PEMDecryptorImpl implements PEMDecryptorProvider, PEMDecryptor { PEMDecryptorImpl(char[] password) { this.password = password; } private char[] password; private String dekAlgName; public PEMDecryptor get(String dekAlgName) throws OperatorCreationException { this.dekAlgName = dekAlgName; return this; // PEMDecryptor } public byte[] decrypt(byte[] keyBytes, byte[] iv) throws PEMException { return decrypt(keyBytes, password, dekAlgName, iv); } static byte[] decrypt( byte[] bytes, char[] password, String dekAlgName, byte[] iv) throws PEMException { return decrypt(SecurityHelper.getSecurityProvider(), bytes, password, dekAlgName, iv); } static byte[] decrypt( Provider provider, byte[] bytes, char[] password, String dekAlgName, byte[] iv) throws PEMException { AlgorithmParameterSpec paramSpec = new IvParameterSpec(iv); String alg; String blockMode = "CBC"; String padding = "PKCS5Padding"; Key sKey; // Figure out block mode and padding. if (dekAlgName.endsWith("-CFB")) { blockMode = "CFB"; padding = "NoPadding"; } if (dekAlgName.endsWith("-ECB") || "DES-EDE".equals(dekAlgName) || "DES-EDE3".equals(dekAlgName)) { // ECB is actually the default (though seldom used) when OpenSSL // uses DES-EDE (des2) or DES-EDE3 (des3). blockMode = "ECB"; paramSpec = null; } if (dekAlgName.endsWith("-OFB")) { blockMode = "OFB"; padding = "NoPadding"; } // Figure out algorithm and key size. if (dekAlgName.startsWith("DES-EDE")) { alg = "DESede"; // "DES-EDE" is actually des2 in OpenSSL-speak! // "DES-EDE3" is des3. boolean des2 = !dekAlgName.startsWith("DES-EDE3"); sKey = secretKeySpec(password, alg, 24, iv, des2); } else if (dekAlgName.startsWith("DES-")) { alg = "DES"; sKey = secretKeySpec(password, alg, 8, iv); } else if (dekAlgName.startsWith("BF-")) { alg = "Blowfish"; sKey = secretKeySpec(password, alg, 16, iv); } else if (dekAlgName.startsWith("RC2-")) { alg = "RC2"; int keyBits = 128; if (dekAlgName.startsWith("RC2-40-")) { keyBits = 40; } else if (dekAlgName.startsWith("RC2-64-")) { keyBits = 64; } sKey = secretKeySpec(password, alg, keyBits / 8, iv); if (paramSpec == null) // ECB block mode { paramSpec = new RC2ParameterSpec(keyBits); } else { paramSpec = new RC2ParameterSpec(keyBits, iv); } } else if (dekAlgName.startsWith("AES-")) { alg = "AES"; byte[] salt = iv; if (salt.length > 8) { salt = new byte[8]; System.arraycopy(iv, 0, salt, 0, 8); } int keyBits; if (dekAlgName.startsWith("AES-128-")) { keyBits = 128; } else if (dekAlgName.startsWith("AES-192-")) { keyBits = 192; } else if (dekAlgName.startsWith("AES-256-")) { keyBits = 256; } else { throw new PEMException("unknown AES encryption with private key"); } sKey = secretKeySpec(password, "AES", keyBits / 8, salt); } else { throw new PEMException("unknown encryption with private key"); } String transformation = alg + "/" + blockMode + "/" + padding; try { javax.crypto.Cipher cipher = SecurityHelper.getCipher(transformation); final int decryptMode = javax.crypto.Cipher.DECRYPT_MODE; if (paramSpec == null) // ECB block mode { cipher.init(decryptMode, sKey); } else { cipher.init(decryptMode, sKey, paramSpec); } return cipher.doFinal(bytes); } catch (Exception e) { throw new PEMException("exception using cipher - please check password and data.", e); } } private static SecretKey secretKeySpec( char[] password, String algorithm, int keyLength, byte[] salt) { return secretKeySpec(password, algorithm, keyLength, salt, false); } private static SecretKey secretKeySpec( char[] password, String algorithm, int keyLength, byte[] salt, boolean des2) { OpenSSLPBEParametersGenerator pGen = new OpenSSLPBEParametersGenerator(); pGen.init(PBEParametersGenerator.PKCS5PasswordToBytes(password), salt); KeyParameter keyParam; keyParam = (KeyParameter)pGen.generateDerivedParameters(keyLength * 8); byte[] key = keyParam.getKey(); if (des2 && key.length >= 24) { // For DES2, we must copy first 8 bytes into the last 8 bytes. System.arraycopy(key, 0, key, 16, 8); } return new SecretKeySpec(key, algorithm); } } }