blob: 1ff980349cf3a8b533f49c89421747ed7a08d038 [file] [log] [blame]
/*
*
* 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.qpid.server.security;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.lang.reflect.InvocationTargetException;
import java.net.InetAddress;
import java.net.InterfaceAddress;
import java.net.NetworkInterface;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.SecureRandom;
import java.security.cert.Certificate;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.security.spec.InvalidKeySpecException;
import java.util.Arrays;
import java.util.Base64;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import org.apache.qpid.server.configuration.IllegalConfigurationException;
import org.apache.qpid.server.logging.EventLogger;
import org.apache.qpid.server.logging.messages.KeyStoreMessages;
import org.apache.qpid.server.model.Broker;
import org.apache.qpid.server.model.Content;
import org.apache.qpid.server.model.CustomRestHeaders;
import org.apache.qpid.server.model.ManagedAttributeField;
import org.apache.qpid.server.model.ManagedObjectFactoryConstructor;
import org.apache.qpid.server.model.RestContentHeader;
import org.apache.qpid.server.model.State;
import org.apache.qpid.server.model.StateTransition;
import org.apache.qpid.server.transport.network.security.ssl.SSLUtil;
import org.apache.qpid.server.util.Strings;
public class AutoGeneratedSelfSignedKeyStoreImpl
extends AbstractKeyStore<AutoGeneratedSelfSignedKeyStoreImpl>
implements AutoGeneratedSelfSignedKeyStore<AutoGeneratedSelfSignedKeyStoreImpl>
{
private static final SecureRandom RANDOM = new SecureRandom();
private final Broker<?> _broker;
private final EventLogger _eventLogger;
@ManagedAttributeField
private volatile String _keyAlgorithm;
@ManagedAttributeField
private volatile String _signatureAlgorithm;
@ManagedAttributeField
private volatile int _keyLength;
@ManagedAttributeField
private volatile int _durationInMonths;
private volatile PrivateKey _privateKey;
private volatile X509Certificate _certificate;
private volatile KeyManager[] _keyManagers;
private volatile boolean _generated;
private volatile boolean _created;
@ManagedObjectFactoryConstructor(conditionallyAvailable = true)
public AutoGeneratedSelfSignedKeyStoreImpl(final Map<String, Object> attributes, Broker<?> broker)
{
super(attributes, broker);
_broker = broker;
_eventLogger = _broker.getEventLogger();
_eventLogger.message(KeyStoreMessages.CREATE(getName()));
}
@Override
public KeyManager[] getKeyManagers() throws GeneralSecurityException
{
KeyManager[] keyManagers = _keyManagers;
return keyManagers == null ? new KeyManager[0] : Arrays.copyOf(keyManagers, keyManagers.length);
}
@Override
public String getKeyAlgorithm()
{
return _keyAlgorithm;
}
@Override
public String getSignatureAlgorithm()
{
return _signatureAlgorithm;
}
@Override
public int getKeyLength()
{
return _keyLength;
}
@Override
public int getDurationInMonths()
{
return _durationInMonths;
}
@Override
public String getEncodedCertificate()
{
try
{
return Base64.getEncoder().encodeToString(_certificate.getEncoded());
}
catch (CertificateEncodingException e)
{
throw new IllegalConfigurationException("Cannot encode certificate", e);
}
}
@Override
public String getEncodedPrivateKey()
{
return Base64.getEncoder().encodeToString(_privateKey.getEncoded());
}
@Override
protected void postResolve()
{
super.postResolve();
if(getActualAttributes().containsKey(ENCODED_PRIVATE_KEY) && getActualAttributes().containsKey(ENCODED_CERTIFICATE))
{
loadPrivateKeyAndCertificate();
}
else
{
generatePrivateKeyAndCertificate();
}
generateKeyManagers();
}
private void loadPrivateKeyAndCertificate()
{
byte[] privateKeyEncoded = Strings.decodeBase64((String) getActualAttributes().get(ENCODED_PRIVATE_KEY));
byte[] certificateEncoded = Strings.decodeBase64((String) getActualAttributes().get(
ENCODED_CERTIFICATE));
try(ByteArrayInputStream input = new ByteArrayInputStream(certificateEncoded))
{
_certificate = (X509Certificate) SSLUtil.getCertificateFactory().generateCertificate(input);
}
catch (CertificateException | IOException e)
{
throw new IllegalConfigurationException("Could not decode certificate", e);
}
try
{
_privateKey = SSLUtil.readPrivateKey(privateKeyEncoded, _keyAlgorithm);
}
catch (NoSuchAlgorithmException | InvalidKeySpecException e)
{
throw new IllegalConfigurationException("Could not decode private key", e);
}
}
@Override
protected void onCreate()
{
super.onCreate();
_created = true;
}
@Override
protected void onOpen()
{
super.onOpen();
initializeExpiryChecking();
}
@StateTransition(currentState = { State.UNINITIALIZED, State.STOPPED, State.ERRORED}, desiredState = State.ACTIVE)
protected ListenableFuture<Void> activate()
{
if(!_created)
{
saveDerivedAttributesIfNecessary();
}
setState(State.ACTIVE);
return Futures.immediateFuture(null);
}
private void saveDerivedAttributesIfNecessary()
{
if(_generated)
{
final Object encodedCertificate = getEncodedCertificate();
attributeSet(ENCODED_CERTIFICATE, encodedCertificate, encodedCertificate);
final Object encodedPrivateKey = getEncodedPrivateKey();
attributeSet(ENCODED_PRIVATE_KEY, encodedPrivateKey, encodedPrivateKey);
_generated = false;
}
}
private void generatePrivateKeyAndCertificate()
{
try
{
Set<InetAddress> addresses = new HashSet<>();
for (NetworkInterface networkInterface : Collections.list(NetworkInterface.getNetworkInterfaces()))
{
for (InterfaceAddress inetAddress : networkInterface.getInterfaceAddresses())
{
addresses.add(inetAddress.getAddress());
}
}
Set<String> dnsNames = new HashSet<>();
for(InetAddress address : addresses)
{
String hostName = address.getHostName();
if (hostName != null)
{
dnsNames.add(hostName);
}
String canonicalHostName = address.getCanonicalHostName();
if (canonicalHostName != null)
{
dnsNames.add(canonicalHostName);
}
}
long startTime = System.currentTimeMillis();
Calendar calendar = Calendar.getInstance();
calendar.setTimeInMillis(startTime);
calendar.add(Calendar.MONTH, _durationInMonths);
long duration = (calendar.getTimeInMillis() - startTime)/1000;
final SSLUtil.KeyCertPair keyCertPair = SSLUtil.generateSelfSignedCertificate(_keyAlgorithm,
_signatureAlgorithm,
_keyLength,
startTime,
duration,
"CN=Qpid",
dnsNames,
addresses);
_privateKey = keyCertPair.getPrivateKey();
_certificate = keyCertPair.getCertificate();
_generated = true;
}
catch (InstantiationException | IllegalAccessException | InvocationTargetException | IOException e)
{
throw new IllegalConfigurationException("Unable to construct keystore", e);
}
}
@Override
protected void checkCertificateExpiry()
{
int expiryWarning = getCertificateExpiryWarnPeriod();
if(expiryWarning > 0)
{
long currentTime = System.currentTimeMillis();
Date expiryTestDate = new Date(currentTime + (ONE_DAY * (long) expiryWarning));
checkCertificatesExpiry(currentTime, expiryTestDate,
new X509Certificate[]{_certificate});
}
}
@Override
protected Collection<Certificate> getCertificates()
{
return Collections.singleton(_certificate);
}
private void generateKeyManagers()
{
try
{
X509Certificate[] certs = new X509Certificate[] { _certificate };
java.security.KeyStore inMemoryKeyStore = java.security.KeyStore.getInstance(java.security.KeyStore.getDefaultType());
byte[] bytes = new byte[64];
char[] chars = "".toCharArray();
RANDOM.nextBytes(bytes);
StandardCharsets.US_ASCII.decode(ByteBuffer.wrap(bytes)).get(chars);
inMemoryKeyStore.load(null, chars);
inMemoryKeyStore.setKeyEntry("1", _privateKey, chars, certs);
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(inMemoryKeyStore, chars);
_keyManagers = kmf.getKeyManagers();
}
catch (IOException | GeneralSecurityException e)
{
throw new IllegalConfigurationException("Cannot load private key or certificate(s): " + e, e);
}
}
static boolean isAvailable()
{
return SSLUtil.canGenerateCerts();
}
@Override
public void regenerateCertificate()
{
generatePrivateKeyAndCertificate();
saveDerivedAttributesIfNecessary();
}
@Override
public Content getClientTrustStore(String password)
{
try
{
KeyStore inMemoryKeyStore =
KeyStore.getInstance(KeyStore.getDefaultType());
inMemoryKeyStore.load(null, null);
inMemoryKeyStore.setCertificateEntry(getName(), _certificate);
return new TrustStoreContent(inMemoryKeyStore, getName(), password == null ? new char[0] : password.toCharArray());
}
catch (CertificateException | NoSuchAlgorithmException | IOException | KeyStoreException e)
{
throw new IllegalArgumentException(e);
}
}
@Override
public Content getCertificate()
{
try
{
return new CertificateContent(_certificate, getName());
}
catch (CertificateEncodingException e)
{
throw new IllegalArgumentException("Cannot decode encode the certificate");
}
}
private static class TrustStoreContent implements Content, CustomRestHeaders
{
private final KeyStore _keyStore;
private final char[] _password;
private final String _disposition;
public TrustStoreContent(final KeyStore inMemoryKeyStore,
final String name, final char[] password)
{
_keyStore = inMemoryKeyStore;
_password = password;
_disposition = "attachment; filename=\"" + name + ".jks\"";
}
@Override
public void write(final OutputStream outputStream) throws IOException
{
try
{
_keyStore.store(outputStream, _password);
}
catch (KeyStoreException | NoSuchAlgorithmException | CertificateException e)
{
throw new IllegalArgumentException(e);
}
}
@Override
public void release()
{
}
@RestContentHeader("Content-Type")
public String getContentType()
{
return "application/octet-stream";
}
@RestContentHeader("Content-Disposition")
public String getContentDisposition()
{
return _disposition;
}
}
private static class CertificateContent implements Content, CustomRestHeaders
{
private final String _disposition;
private final String _certString;
public CertificateContent(final X509Certificate certificate, final String name)
throws CertificateEncodingException
{
_disposition = "attachment; filename=\"" + name + ".pem\"";
StringBuilder certStringBuffer = new StringBuilder("-----BEGIN CERTIFICATE-----\n");
String cert = Base64.getEncoder().encodeToString(certificate.getEncoded());
int offset = 0;
while(cert.length()-offset > 64)
{
certStringBuffer.append(cert.substring(offset, offset+64));
offset+=64;
certStringBuffer.append("\n");
}
certStringBuffer.append(cert.substring(offset));
certStringBuffer.append("\n-----END CERTIFICATE-----\n");
_certString = certStringBuffer.toString();
}
@Override
public void write(final OutputStream outputStream) throws IOException
{
Writer w = new OutputStreamWriter(outputStream);
w.write(_certString);
w.flush();
}
@Override
public void release()
{
}
@RestContentHeader("Content-Type")
public String getContentType()
{
return "text/plain";
}
@RestContentHeader("Content-Disposition")
public String getContentDisposition()
{
return _disposition;
}
}
@Override
protected void logOperation(final String operation)
{
_broker.getEventLogger().message(KeyStoreMessages.OPERATION(operation));
}
}