/**
* Copyright (c) 2013-2016, The SeedStack authors <http://seedstack.org>
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.seedstack.seed.core.internal.crypto;
import org.seedstack.seed.crypto.CryptoConfig;
import org.seedstack.seed.crypto.EncryptionService;
import org.seedstack.seed.SeedException;
import org.seedstack.shed.ClassLoaders;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.URL;
import java.security.Key;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.UnrecoverableKeyException;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
/**
* Factory to create a new {@link EncryptionService} object. This factory checks the KeyStore state (connection)
* if a KeyStore is used.
*/
class EncryptionServiceFactory {
private static final Logger LOGGER = LoggerFactory.getLogger(EncryptionServiceFactory.class);
private static final String DEFAULT_CERTIFICATE_TYPE = "X.509";
private final CryptoConfig cryptoConfig;
private final KeyStore keyStore;
/**
* Constructs an encryption service factory for a specific KeyStore.
*
* @param cryptoConfig the crypto configuration
* @param keyStore the KeyStore which holds the key pairs
*/
EncryptionServiceFactory(CryptoConfig cryptoConfig, KeyStore keyStore) {
this.cryptoConfig = cryptoConfig;
if (keyStore == null) {
throw SeedException.createNew(CryptoErrorCode.NO_KEYSTORE_CONFIGURED);
}
this.keyStore = keyStore;
}
/**
* Creates an encryption service for the key pair associated to the alias.
* <p>
* The certificate will be loaded from an external file if a location is specified
* in the configuration.
* </p>
*
* @param alias the alias corresponding to the key store entry
* @param password the password protecting the private key
* @return encryption service
*/
EncryptionService create(String alias, char[] password) {
PublicKey pk = getPublicKey(alias);
Key key;
try {
key = this.keyStore.getKey(alias, password);
} catch (UnrecoverableKeyException e) {
throw SeedException.wrap(e, CryptoErrorCode.UNRECOVERABLE_KEY);
} catch (KeyStoreException e) {
throw SeedException.wrap(e, CryptoErrorCode.UNEXPECTED_EXCEPTION);
} catch (NoSuchAlgorithmException e) {
throw SeedException.wrap(e, CryptoErrorCode.ALGORITHM_CANNOT_BE_FOUND);
}
return new EncryptionServiceImpl(alias, pk, key);
}
/**
* Creates an encryption service for the certificate associated to the alias.
* No private key will be associated as no password is provided.
* <p>
* The certificate will be loaded from an external file if a location is specified
* in the configuration.
* </p>
*
* @param alias the alias corresponding to the key store entry
* @return encryption service
*/
EncryptionService create(String alias) {
return new EncryptionServiceImpl(alias, getPublicKey(alias), null);
}
private PublicKey getPublicKey(String alias) {
Certificate certificate;
// Look for an external certificate
String certLocation = getCertificateLocation(alias);
if (certLocation != null) {
certificate = loadCertificateFromFile(certLocation);
} else {
// Otherwise load the certificate from the key store
try {
certificate = keyStore.getCertificate(alias);
} catch (KeyStoreException e) {
throw SeedException.createNew(CryptoErrorCode.NO_KEYSTORE_CONFIGURED);
}
}
if (certificate != null) {
return certificate.getPublicKey();
}
return null;
}
private String getCertificateLocation(String alias) {
CryptoConfig.CertificateConfig certificateConfig = cryptoConfig.certificates().get(alias);
if (certificateConfig != null) {
// Find the certificate location from the classpath
String resource = certificateConfig.getResource();
if (resource != null) {
URL urlResource = ClassLoaders.findMostCompleteClassLoader().getResource(resource);
if (urlResource == null) {
throw SeedException.createNew(CryptoErrorCode.CERTIFICATE_NOT_FOUND)
.put("alias", alias).put("certResource", resource);
}
return urlResource.getFile();
} else {
// Otherwise get the file path from the configuration
return certificateConfig.getFile();
}
} else {
return null;
}
}
private Certificate loadCertificateFromFile(String certLocation) {
Certificate certificate = null;
if (certLocation != null) {
FileInputStream in = null;
try {
in = new FileInputStream(certLocation);
certificate = CertificateFactory.getInstance(DEFAULT_CERTIFICATE_TYPE).generateCertificate(in);
} catch (Exception e) {
throw SeedException.wrap(e, CryptoErrorCode.UNABLE_TO_READ_CERTIFICATE)
.put("location", certLocation);
} finally {
try {
if (in != null) {
in.close();
}
} catch (IOException e) {
LOGGER.warn("Unable to close certificate input stream", e);
}
}
}
return certificate;
}
}