/* * 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.brooklyn.util.core.crypto; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.StringWriter; import java.security.KeyPair; import java.security.PrivateKey; import java.security.PublicKey; import java.security.Security; import org.apache.brooklyn.core.internal.BrooklynInitialization; import org.apache.brooklyn.util.crypto.AuthorizedKeysParser; import org.apache.brooklyn.util.crypto.SecureKeysWithoutBouncyCastle; import org.apache.brooklyn.util.exceptions.Exceptions; import org.apache.brooklyn.util.stream.Streams; import org.bouncycastle.asn1.pkcs.PrivateKeyInfo; import org.bouncycastle.jce.X509Principal; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.openssl.PEMDecryptorProvider; import org.bouncycastle.openssl.PEMEncryptedKeyPair; import org.bouncycastle.openssl.PEMKeyPair; import org.bouncycastle.openssl.PEMParser; import org.bouncycastle.openssl.PEMWriter; import org.bouncycastle.openssl.PasswordFinder; import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter; import org.bouncycastle.openssl.jcajce.JcePEMDecryptorProviderBuilder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.base.Objects; import com.google.common.base.Throwables; /** * Utility methods for generating and working with keys, * extending the parent class with useful things provided by BouncyCastle crypto library. * (Parent class is in a different project where BC is not included as a dependency.) */ public class SecureKeys extends SecureKeysWithoutBouncyCastle { private static final Logger log = LoggerFactory.getLogger(SecureKeys.class); static { BrooklynInitialization.initSecureKeysBouncyCastleProvider(); } public static void initBouncyCastleProvider() { Security.addProvider(new BouncyCastleProvider()); } public static class PassphraseProblem extends IllegalStateException { private static final long serialVersionUID = -3382824813899223447L; public PassphraseProblem(String message) { super("Passphrase problem with this key: "+message); } public PassphraseProblem(String message, Exception cause) { super("Passphrase problem with this key: "+message, cause); } } private SecureKeys() {} /** RFC1773 order, with None for other values. Normally prefer X500Principal. */ public static X509Principal getX509PrincipalWithCommonName(String commonName) { return new X509Principal("" + "C=None," + "L=None," + "O=None," + "OU=None," + "CN=" + commonName); } /** reads RSA or DSA / pem style private key files (viz {@link #toPem(KeyPair)}), extracting also the public key if possible * @throws IllegalStateException on errors, in particular {@link PassphraseProblem} if that is the problem */ public static KeyPair readPem(InputStream input, final String passphrase) { // TODO cache is only for fallback "reader" strategy (2015-01); delete when Parser confirmed working byte[] cache = Streams.readFully(input); input = new ByteArrayInputStream(cache); try { PEMParser pemParser = new PEMParser(new InputStreamReader(input)); Object object = pemParser.readObject(); pemParser.close(); JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider("BC"); KeyPair kp = null; if (object==null) { throw new IllegalStateException("PEM parsing failed: missing or invalid data"); } else if (object instanceof PEMEncryptedKeyPair) { if (passphrase==null) throw new PassphraseProblem("passphrase required"); try { PEMDecryptorProvider decProv = new JcePEMDecryptorProviderBuilder().build(passphrase.toCharArray()); kp = converter.getKeyPair(((PEMEncryptedKeyPair) object).decryptKeyPair(decProv)); } catch (Exception e) { Exceptions.propagateIfFatal(e); throw new PassphraseProblem("wrong passphrase", e); } } else if (object instanceof PEMKeyPair) { kp = converter.getKeyPair((PEMKeyPair) object); } else if (object instanceof PrivateKeyInfo) { PrivateKey privKey = converter.getPrivateKey((PrivateKeyInfo) object); kp = new KeyPair(null, privKey); } else { throw new IllegalStateException("PEM parser support missing for: "+object); } return kp; } catch (Exception e) { Exceptions.propagateIfFatal(e); // older code relied on PEMReader, now deprecated // replaced with above based on http://stackoverflow.com/questions/14919048/bouncy-castle-pemreader-pemparser // passes the same tests (Jan 2015) but leaving the old code as a fallback for the time being input = new ByteArrayInputStream(cache); try { Security.addProvider(new BouncyCastleProvider()); @SuppressWarnings("deprecation") org.bouncycastle.openssl.PEMReader pr = new org.bouncycastle.openssl.PEMReader(new InputStreamReader(input), new PasswordFinder() { public char[] getPassword() { return passphrase!=null ? passphrase.toCharArray() : new char[0]; } }); @SuppressWarnings("deprecation") KeyPair result = (KeyPair) pr.readObject(); pr.close(); if (result==null) throw Exceptions.propagate(e); log.warn("PEMParser failed when deprecated PEMReader succeeded, with "+result+"; had: "+e); return result; } catch (Exception e2) { Exceptions.propagateIfFatal(e2); throw Exceptions.propagate(e); } } } /** because KeyPair.equals is not implemented :( */ public static boolean equal(KeyPair k1, KeyPair k2) { return Objects.equal(k2.getPrivate(), k1.getPrivate()) && Objects.equal(k2.getPublic(), k1.getPublic()); } /** returns the PEM (base64, ie for id_rsa) string for the private key / key pair; * this starts -----BEGIN PRIVATE KEY----- and ends similarly, like id_rsa. * also see {@link #readPem(InputStream, String)} */ public static String toPem(KeyPair key) { return stringPem(key); } /** returns id_rsa.pub style file, of public key */ public static String toPub(KeyPair key) { return AuthorizedKeysParser.encodePublicKey(key.getPublic()); } /** opposite of {@link #toPub(KeyPair)}, given text */ public static PublicKey fromPub(String pubText) { return AuthorizedKeysParser.decodePublicKey(pubText); } /** @deprecated since 0.7.0, use {@link #toPem(KeyPair)} */ @Deprecated public static String stringPem(KeyPair key) { try { StringWriter sw = new StringWriter(); PEMWriter w = new PEMWriter(sw); w.writeObject(key); w.close(); return sw.toString(); } catch (IOException e) { throw Throwables.propagate(e); } } }