/**
* Copyright (c) 2009 - 2012 Red Hat, Inc.
*
* This software is licensed to you under the GNU General Public License,
* version 2 (GPLv2). There is NO WARRANTY for this software, express or
* implied, including the implied warranties of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. You should have received a copy of GPLv2
* along with this software; if not, see
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt.
*
* Red Hat trademarks are not licensed under GPLv2. No permission is
* granted to use or replicate Red Hat trademarks that are incorporated
* in this software or its documentation.
*/
package org.candlepin.pki.impl;
import org.candlepin.common.config.Configuration;
import org.candlepin.config.ConfigProperties;
import org.candlepin.pki.PKIReader;
import org.candlepin.util.Util;
import com.google.inject.Inject;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.openssl.PEMReader;
import org.bouncycastle.openssl.PasswordFinder;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.security.GeneralSecurityException;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.Security;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.HashSet;
import java.util.Set;
/**
* The default {@link PKIReader} for Candlepin. This reads the file paths for
* the CA certificate and CA private key, as well as an optional password, from
* the config system. These values are customizable via /etc/candlepin/candlepin.conf.
*
* All code that imports bouncycastle should live either in this module,
* or in {@link BouncyCastlePKIUtility}
*
* (March 24, 2011) Notes on implementing a PKIReader with NSS/JSS:
*
* The only code here that's bouncycastle specific is the PEMReader.
* JSS doesn't provide any code for reading/writing PEM, so we'd need to read the input
* file, parse the headers related to private key storage (to make sure we have the right
* file type and determine if we have to decrypt with a password), base64 decode the input
* into DER, optionally decrypt, then build an RSAKeySpec and pass it on to java's
* keyfactory to get a key.
*
* As of March 24, 2011, we're only using bouncycastle to read and write encodings, not
* perform any "real" crypto work. if we tell the PEMReader to use a different JSSE
* provider (say, SunJSSE), and don't load the bouncycastle provider, we're not technically
* using bouncycastle for crypto. We could also take only the subset of classes we require
* (none of the crypto ones) and use those.
*
* See also {@link BouncyCastlePKIUtility} for more notes.
*/
public class BouncyCastlePKIReader implements PKIReader, PasswordFinder {
private CertificateFactory certFactory;
private String caCertPath;
private String upstreamCaCertPath;
private String caKeyPath;
private String caKeyPassword;
private final X509Certificate x509Certificate;
private final Set<X509Certificate> upstreamX509Certificates;
private final PrivateKey privateKey;
static {
Security.addProvider(new BouncyCastleProvider());
}
@Inject
public BouncyCastlePKIReader(Configuration config) throws CertificateException {
certFactory = CertificateFactory.getInstance("X.509");
this.caCertPath = config.getString(ConfigProperties.CA_CERT);
this.upstreamCaCertPath = config.getString(ConfigProperties.CA_CERT_UPSTREAM);
this.caKeyPath = config.getString(ConfigProperties.CA_KEY);
Util.assertNotNull(this.caCertPath,
"caCertPath cannot be null. Unable to load CA Certificate");
Util.assertNotNull(this.caKeyPath,
"caKeyPath cannot be null. Unable to load PrivateKey");
this.caKeyPassword = config.getString(ConfigProperties.CA_KEY_PASSWORD, null);
this.x509Certificate = loadCACertificate(this.caCertPath);
this.upstreamX509Certificates = loadUpstreamCACertificates(upstreamCaCertPath);
this.privateKey = loadPrivateKey();
}
/**
* @return
*/
private X509Certificate loadCACertificate(String path) {
InputStream inStream = null;
try {
inStream = new FileInputStream(path);
X509Certificate cert = (X509Certificate) this.certFactory
.generateCertificate(inStream);
inStream.close();
return cert;
}
catch (Exception e) {
throw new RuntimeException(e);
}
finally {
try {
if (inStream != null) {
inStream.close();
}
}
catch (IOException e) {
// ignore. there's nothing we can do.
}
}
}
private Set<X509Certificate> loadUpstreamCACertificates(String path) {
InputStream inStream = null;
Set<X509Certificate> result = new HashSet<X509Certificate>();
File dir = new File(path);
if (!dir.exists()) {
return result;
}
for (File file : dir.listFiles()) {
try {
inStream = new FileInputStream(file.getAbsolutePath());
X509Certificate cert = (X509Certificate) this.certFactory
.generateCertificate(inStream);
inStream.close();
result.add(cert);
}
catch (Exception e) {
throw new RuntimeException(e);
}
finally {
try {
if (inStream != null) {
inStream.close();
}
}
catch (IOException e) {
// ignore. there's nothing we can do.
}
}
}
return result;
}
/**
* @return
*/
private PrivateKey loadPrivateKey() {
try {
InputStreamReader inStream = new InputStreamReader(
new FileInputStream(this.caKeyPath));
PEMReader reader = null;
try {
if (this.caKeyPassword != null) {
reader = new PEMReader(inStream, this);
}
else {
reader = new PEMReader(inStream);
}
Object caKeyObj = reader.readObject();
if (caKeyObj == null) {
throw new GeneralSecurityException(
"Reading CA private key failed");
}
if (caKeyObj instanceof KeyPair) {
KeyPair caKeyPair = (KeyPair) caKeyObj;
return caKeyPair.getPrivate();
}
else if (caKeyObj instanceof PrivateKey) {
return (PrivateKey) caKeyObj;
}
else {
throw new GeneralSecurityException("Unexepected CA key object: " +
caKeyObj.getClass().getName());
}
}
finally {
reader.close();
}
}
catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public X509Certificate getCACert() throws IOException, CertificateException {
return this.x509Certificate;
}
@Override
public Set<X509Certificate> getUpstreamCACerts()
throws IOException, CertificateException {
return this.upstreamX509Certificates;
}
/**
* {@inheritDoc}
*
* Reads the {@link KeyPair} from the CA's private key file specified in the
* candlepin config.
*
* @return private key for the CA cert
*/
@Override
public PrivateKey getCaKey() throws IOException, GeneralSecurityException {
return this.privateKey;
}
@Override
public char[] getPassword() {
// just grab the key password that was pulled from the config
return (caKeyPassword != null) ? caKeyPassword.toCharArray() : null;
}
}