/**
* diqube: Distributed Query Base.
*
* Copyright (C) 2015 Bastian Gloeckle
*
* This file is part of diqube.
*
* diqube is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.diqube.ticket;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.annotation.PostConstruct;
import javax.inject.Inject;
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.crypto.params.RSAKeyParameters;
import org.bouncycastle.crypto.params.RSAPrivateCrtKeyParameters;
import org.bouncycastle.crypto.util.PrivateKeyFactory;
import org.bouncycastle.crypto.util.PublicKeyFactory;
import org.bouncycastle.openssl.PEMDecryptorProvider;
import org.bouncycastle.openssl.PEMEncryptedKeyPair;
import org.bouncycastle.openssl.PEMKeyPair;
import org.bouncycastle.openssl.PEMParser;
import org.bouncycastle.openssl.jcajce.JcePEMDecryptorProviderBuilder;
import org.diqube.context.AutoInstatiate;
import org.diqube.thrift.base.thrift.Ticket;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Loads and provides the RSA keys from {@link TicketRsaKeyFileProvider}.
*
* @author Bastian Gloeckle
*/
@AutoInstatiate
public class TicketRsaKeyManager {
private static final Logger logger = LoggerFactory.getLogger(TicketRsaKeyManager.class);
@Inject
private TicketRsaKeyFileProvider provider;
private RSAPrivateCrtKeyParameters privateSigningKey = null;
private List<RSAKeyParameters> publicValidationKeys = null;
private boolean initialized = false;
/**
* @return The private key that needs to be used to sign any {@link Ticket}s. May be <code>null</code> in case there
* is no private key available.
*/
public RSAPrivateCrtKeyParameters getPrivateSigningKey() {
if (!initialized)
throw new IllegalStateException("Not initialized");
return privateSigningKey;
}
/**
* @return Unmodifiable list of RSA public keys that can be used to validate signatures of {@link Ticket}s.
*/
public List<RSAKeyParameters> getPublicValidationKeys() {
if (!initialized)
throw new IllegalStateException("Not initialized");
return publicValidationKeys;
}
@PostConstruct
public void initialize() {
List<RSAKeyParameters> allPublicKeys = new ArrayList<>();
publicValidationKeys = Collections.unmodifiableList(allPublicKeys);
provider.getPemFiles().whenComplete((pemFiles, error) -> {
if (error != null)
throw new RuntimeException("Exception while identifying .pem files!", error);
if (pemFiles.isEmpty())
throw new RuntimeException("No .pem files configured that can be used to sign/validate tickets!");
for (int i = 0; i < pemFiles.size(); i++) {
String pemFileName = pemFiles.get(i).getLeft();
String pemPassword = pemFiles.get(i).getRight();
try (InputStream pemStream = pemFiles.get(i).getMiddle().get()) {
Reader pemReader = new InputStreamReader(pemStream);
try (PEMParser parser = new PEMParser(pemReader)) {
Object o = parser.readObject();
SubjectPublicKeyInfo publicKeyInfo = null;
PrivateKeyInfo privateKeyInfo = null;
if (o instanceof PEMEncryptedKeyPair) {
if (pemPassword == null)
throw new RuntimeException(
"PEM file '" + pemFileName + "' is password protected, but the password is not configured.");
PEMDecryptorProvider decryptionProvider =
new JcePEMDecryptorProviderBuilder().build(pemPassword.toCharArray());
PEMEncryptedKeyPair encryptedKeyPair = (PEMEncryptedKeyPair) o;
PEMKeyPair keyPair = encryptedKeyPair.decryptKeyPair(decryptionProvider);
publicKeyInfo = keyPair.getPublicKeyInfo();
privateKeyInfo = keyPair.getPrivateKeyInfo();
} else if (o instanceof PEMKeyPair) {
PEMKeyPair keyPair = (PEMKeyPair) o;
publicKeyInfo = keyPair.getPublicKeyInfo();
privateKeyInfo = keyPair.getPrivateKeyInfo();
} else if (o instanceof SubjectPublicKeyInfo)
publicKeyInfo = (SubjectPublicKeyInfo) o;
else
throw new RuntimeException(
"Could not identify content of pem file '" + pemFileName + "': " + o.toString());
if (publicKeyInfo == null)
throw new RuntimeException(
"Could not load '" + pemFileName + "' because it did not contain a public key.");
if (privateKeyInfo == null)
logger.info("Loading public key from '{}'.", pemFileName);
else if (!provider.filesWithPrivateKeyAreRequired())
throw new RuntimeException("File '" + pemFileName + "' contains a private key, but only public keys are "
+ "accepted. Please extract the public key from the current file and configure diqube to use "
+ "that new file.");
allPublicKeys.add((RSAKeyParameters) PublicKeyFactory.createKey(publicKeyInfo));
if (i == 0 && privateKeyInfo != null) {
logger.info("Loading private key from '{}' and will use that for signing tickets.", pemFileName);
privateSigningKey = (RSAPrivateCrtKeyParameters) PrivateKeyFactory.createKey(privateKeyInfo);
}
}
} catch (IOException e) {
throw new RuntimeException("Could not interact with '" + pemFileName + "'. Correct password?", e);
}
}
if (privateSigningKey == null && provider.filesWithPrivateKeyAreRequired())
throw new RuntimeException("A .pem file containing a private key for signing tickets is required. "
+ "Make sure that the first configured .pem file contains a private key.");
initialized = true;
});
}
}