Skip to content

Commit

Permalink
QPID-8678 - [Broker-J] Broker REST API returns certificate details fo…
Browse files Browse the repository at this point in the history
…r truststores but not keystores (#248)
  • Loading branch information
dakirily authored Sep 30, 2024
1 parent 9f3d4f9 commit de2c347
Show file tree
Hide file tree
Showing 10 changed files with 229 additions and 63 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -197,13 +197,13 @@ public int getCertificateExpiryCheckFrequency()
@Override
public List<CertificateDetails> getCertificateDetails()
{
Collection<Certificate> certificates = getCertificates();
final Collection<Certificate> certificates = getCertificates();
if (!certificates.isEmpty())
{
return certificates.stream()
.filter(cert -> cert instanceof X509Certificate)
.map(x509cert -> new CertificateDetailsImpl((X509Certificate) x509cert))
.collect(Collectors.toList());
.filter(X509Certificate.class::isInstance)
.map(x509cert -> new CertificateDetailsImpl((X509Certificate) x509cert))
.collect(Collectors.toList());
}
return Collections.emptyList();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,14 +90,12 @@ public class FileKeyStoreImpl extends AbstractKeyStore<FileKeyStoreImpl> impleme
Handler.register();
}


@ManagedObjectFactoryConstructor
public FileKeyStoreImpl(Map<String, Object> attributes, Broker<?> broker)
{
super(attributes, broker);
}


@Override
public void onValidate()
{
Expand Down Expand Up @@ -137,7 +135,7 @@ private void initialize()
{
try
{
_certificates = Collections.unmodifiableMap(SSLUtil.getCertificates(getInitializedKeyStore(this)));
_certificates = Collections.unmodifiableMap(SSLUtil.getCertificates(getInitializedKeyStore(this), true));
}
catch (GeneralSecurityException | IOException e)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,7 @@ protected void initialize()
{
final KeyStore ts = initializeKeyStore(this);
trustManagers = createTrustManagers(ts);
certificates = Collections.unmodifiableMap(SSLUtil.getCertificates(ts));
certificates = Collections.unmodifiableMap(SSLUtil.getCertificates(ts, false));
}
catch (Exception e)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -322,4 +322,10 @@ protected Collection<Certificate> getCertificates()
final Collection<Certificate> certificates = _certificates;
return certificates == null ? List.of() : certificates;
}

@Override
public List<CertificateDetails> getCertificateDetails()
{
return _certificate == null ? List.of() : List.of(new CertificateDetailsImpl(_certificate));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -932,14 +932,14 @@ public X509Certificate getCertificate()
};
}

public static Map<String, Certificate> getCertificates(final KeyStore ks) throws KeyStoreException
public static Map<String, Certificate> getCertificates(final KeyStore ks, final boolean keyCertificates) throws KeyStoreException
{
final Map<String ,Certificate> certificates = new HashMap<>();
final Enumeration<String> aliases = ks.aliases();
while (aliases.hasMoreElements())
{
final String alias = aliases.nextElement();
if (ks.isCertificateEntry(alias))
if ((keyCertificates && ks.isKeyEntry(alias)) || (!keyCertificates && ks.isCertificateEntry(alias)))
{
certificates.put(alias, ks.getCertificate(alias));
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
/*
* 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 static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.nio.charset.StandardCharsets;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.interfaces.RSAPublicKey;
import java.util.Base64;
import java.util.List;
import java.util.Map;

import javax.net.ssl.KeyManager;

import org.junit.jupiter.api.Test;

import org.apache.qpid.server.model.Broker;
import org.apache.qpid.server.model.BrokerModel;
import org.apache.qpid.server.model.BrokerTestHelper;
import org.apache.qpid.server.model.ConfiguredObjectFactory;
import org.apache.qpid.server.model.KeyStore;
import org.apache.qpid.test.utils.UnitTestBase;

class AutoGeneratedSelfSignedKeyStoreTest extends UnitTestBase
{
private static final Broker<?> BROKER = BrokerTestHelper.createBrokerMock();
private static final ConfiguredObjectFactory FACTORY = BrokerModel.getInstance().getObjectFactory();
private static final String NAME = "myKeyStore";

private final AutoGeneratedSelfSignedKeyStore<?> _keyStore = createAutoGeneratedSelfSignedKeyStore();

@Test
void creation() throws Exception
{
final KeyManager[] keyManager = _keyStore.getKeyManagers();

assertNotNull(keyManager);
assertEquals(1, keyManager.length, "Unexpected number of key managers");
assertNotNull(keyManager[0], "Key manager unexpected null");
}

@Test
void regenerate()
{
final String privateKey = _keyStore.getEncodedPrivateKey();
final String certificate = _keyStore.getEncodedCertificate();

assertNotNull(privateKey);
assertNotNull(certificate);

_keyStore.regenerateCertificate();

final String regeneratedPrivateKey = _keyStore.getEncodedPrivateKey();
final String regeneratedCertificate = _keyStore.getEncodedCertificate();

assertNotNull(regeneratedPrivateKey);
assertNotNull(regeneratedCertificate);

assertNotEquals(privateKey, regeneratedPrivateKey, "Regenerated private key shouldn't be equal to the original private key");
assertNotEquals(certificate, regeneratedCertificate, "Regenerated certificate shouldn't be equal to the original certificate");
}

@Test
void privateKeyEntryCertificate()
{
final List<CertificateDetails> certificateDetails = _keyStore.getCertificateDetails();

assertEquals(1, certificateDetails.size(), "Unexpected number of certificates");
assertEquals("myKeyStore", certificateDetails.get(0).getAlias(), "Unexpected alias name");
}

@Test
void content() throws Exception
{
try (final ByteArrayOutputStream baos = new ByteArrayOutputStream())
{
_keyStore.getCertificate().write(baos);

final CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
final X509Certificate certificate = (X509Certificate) certFactory
.generateCertificate(new ByteArrayInputStream(baos.toByteArray()));
final String encodedCertificate = new String(Base64.getEncoder().encode(certificate.getEncoded()), StandardCharsets.UTF_8);

assertEquals("X.509", certificate.getType(), "Certificate type mismatch");
assertTrue(_keyStore.getSignatureAlgorithm().equalsIgnoreCase(certificate.getSigAlgName()),
"Certificate signature algorithm mismatch");
assertInstanceOf(RSAPublicKey.class, certificate.getPublicKey(), "Key class mismatch");
assertEquals(_keyStore.getKeyLength(), ((RSAPublicKey) certificate.getPublicKey()).getModulus().bitLength(), "Key length mismatch");
assertEquals(_keyStore.getEncodedCertificate(), encodedCertificate, "Certificate content mismatch");
}
}

private AutoGeneratedSelfSignedKeyStore<?> createAutoGeneratedSelfSignedKeyStore()
{
final Map<String, Object> attributes = Map.of(AutoGeneratedSelfSignedKeyStore.NAME, NAME,
AutoGeneratedSelfSignedKeyStore.TYPE, "AutoGeneratedSelfSigned");
return (AutoGeneratedSelfSignedKeyStore<?>) FACTORY.create(KeyStore.class, attributes, BROKER);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ public class FileKeyStoreTest extends UnitTestBase
private static final String SECRET_KEY_ALIAS = "secret-key-alias";

@Test
public void testCreateKeyStoreFromFile_Success() throws Exception
void testCreateKeyStoreFromFile_Success() throws Exception
{
final Path keyStoreFile = TLS_RESOURCE.createSelfSignedKeyStore(DN_FOO);
final Map<String, Object> attributes = Map.of(FileKeyStore.NAME, NAME,
Expand All @@ -81,7 +81,7 @@ public void testCreateKeyStoreFromFile_Success() throws Exception
}

@Test
public void testCreateKeyStoreWithAliasFromFile_Success() throws Exception
void testCreateKeyStoreWithAliasFromFile_Success() throws Exception
{
final Path keyStoreFile = TLS_RESOURCE.createSelfSignedKeyStore(DN_FOO);
final Map<String, Object> attributes = Map.of(FileKeyStore.NAME, NAME,
Expand All @@ -97,7 +97,7 @@ public void testCreateKeyStoreWithAliasFromFile_Success() throws Exception
}

@Test
public void testCreateKeyStoreFromFile_WrongPassword() throws Exception
void testCreateKeyStoreFromFile_WrongPassword() throws Exception
{
final Path keyStoreFile = TLS_RESOURCE.createSelfSignedKeyStore(DN_FOO);
final Map<String, Object> attributes = Map.of(FileKeyStore.NAME, NAME,
Expand All @@ -109,7 +109,7 @@ public void testCreateKeyStoreFromFile_WrongPassword() throws Exception
}

@Test
public void testCreateKeyStoreFromFile_UnknownAlias() throws Exception
void testCreateKeyStoreFromFile_UnknownAlias() throws Exception
{
final Path keyStoreFile = TLS_RESOURCE.createSelfSignedKeyStore(DN_FOO);
final String unknownAlias = TLS_RESOURCE.getPrivateKeyAlias() + "_";
Expand All @@ -124,7 +124,7 @@ public void testCreateKeyStoreFromFile_UnknownAlias() throws Exception
}

@Test
public void testCreateKeyStoreFromFile_NonKeyAlias() throws Exception
void testCreateKeyStoreFromFile_NonKeyAlias() throws Exception
{
final Path keyStoreFile = TLS_RESOURCE.createSelfSignedTrustStore(DN_FOO);
final Map<String, Object> attributes = Map.of(FileKeyStore.NAME, NAME,
Expand All @@ -138,7 +138,7 @@ public void testCreateKeyStoreFromFile_NonKeyAlias() throws Exception
}

@Test
public void testCreateKeyStoreFromDataUrl_Success() throws Exception
void testCreateKeyStoreFromDataUrl_Success() throws Exception
{
final String keyStoreAsDataUrl = TLS_RESOURCE.createSelfSignedKeyStoreAsDataUrl(DN_FOO);
final Map<String, Object> attributes = Map.of(FileKeyStore.NAME, NAME,
Expand All @@ -153,7 +153,7 @@ public void testCreateKeyStoreFromDataUrl_Success() throws Exception
}

@Test
public void testCreateKeyStoreWithAliasFromDataUrl_Success() throws Exception
void testCreateKeyStoreWithAliasFromDataUrl_Success() throws Exception
{
final String keyStoreAsDataUrl = TLS_RESOURCE.createSelfSignedKeyStoreAsDataUrl(DN_FOO);
final Map<String, Object> attributes = Map.of(FileKeyStore.NAME, NAME,
Expand All @@ -169,7 +169,7 @@ public void testCreateKeyStoreWithAliasFromDataUrl_Success() throws Exception
}

@Test
public void testCreateKeyStoreFromDataUrl_WrongPassword() throws Exception
void testCreateKeyStoreFromDataUrl_WrongPassword() throws Exception
{
final String keyStoreAsDataUrl = TLS_RESOURCE.createSelfSignedKeyStoreAsDataUrl(DN_FOO);
final Map<String, Object> attributes = Map.of(FileKeyStore.NAME, NAME,
Expand All @@ -181,7 +181,7 @@ public void testCreateKeyStoreFromDataUrl_WrongPassword() throws Exception
}

@Test
public void testCreateKeyStoreFromDataUrl_BadKeystoreBytes()
void testCreateKeyStoreFromDataUrl_BadKeystoreBytes()
{
final String keyStoreAsDataUrl = DataUrlUtils.getDataUrlForBytes("notatruststore".getBytes());
final Map<String, Object> attributes = Map.of(FileKeyStore.NAME, NAME,
Expand All @@ -193,7 +193,7 @@ public void testCreateKeyStoreFromDataUrl_BadKeystoreBytes()
}

@Test
public void testCreateKeyStoreFromDataUrl_UnknownAlias() throws Exception
void testCreateKeyStoreFromDataUrl_UnknownAlias() throws Exception
{
final String keyStoreAsDataUrl = TLS_RESOURCE.createSelfSignedKeyStoreAsDataUrl(DN_FOO);
final String unknownAlias = TLS_RESOURCE.getPrivateKeyAlias() + "_";
Expand All @@ -208,7 +208,7 @@ public void testCreateKeyStoreFromDataUrl_UnknownAlias() throws Exception
}

@Test
public void testEmptyKeystoreRejected() throws Exception
void testEmptyKeystoreRejected() throws Exception
{
final Path keyStoreFile = TLS_RESOURCE.createKeyStore();
final Map<String, Object> attributes = Map.of(FileKeyStore.NAME, NAME,
Expand All @@ -220,7 +220,7 @@ public void testEmptyKeystoreRejected() throws Exception
}

@Test
public void testKeystoreWithNoPrivateKeyRejected() throws Exception
void testKeystoreWithNoPrivateKeyRejected() throws Exception
{
final Path keyStoreFile = TLS_RESOURCE.createSelfSignedTrustStore(DN_FOO);
final Map<String, Object> attributes = Map.of(FileKeyStore.NAME, getTestName(),
Expand All @@ -233,7 +233,7 @@ public void testKeystoreWithNoPrivateKeyRejected() throws Exception
}

@Test
public void testSymmetricKeysIgnored() throws Exception
void testSymmetricKeysIgnored() throws Exception
{
final String keyStoreType = "jceks"; // or jks
final Path keyStoreFile = createSelfSignedKeyStoreWithSecretKeyAndCertificate(keyStoreType, DN_FOO);
Expand All @@ -246,7 +246,7 @@ public void testSymmetricKeysIgnored() throws Exception
}

@Test
public void testUpdateKeyStore_Success() throws Exception
void testUpdateKeyStore_Success() throws Exception
{
final Path keyStoreFile = TLS_RESOURCE.createSelfSignedKeyStore(DN_FOO);
final Map<String, Object> attributes = Map.of(FileKeyStore.NAME, NAME,
Expand Down Expand Up @@ -276,7 +276,7 @@ public void testUpdateKeyStore_Success() throws Exception
}

@Test
public void testReloadKeystore() throws Exception
void testReloadKeystore() throws Exception
{
final Path keyStorePath = TLS_RESOURCE.createSelfSignedKeyStoreWithCertificate(DN_FOO);
final Path keyStorePath2 = TLS_RESOURCE.createSelfSignedKeyStoreWithCertificate(DN_BAR);
Expand All @@ -295,6 +295,23 @@ public void testReloadKeystore() throws Exception
assertEquals(DN_BAR, certificate2.getIssuerName());
}

@Test
void privateKeyEntryCertificate() throws Exception
{
final Path keyStoreFile = TLS_RESOURCE.createSelfSignedKeyStoreWithCertificate(DN_FOO);
final Map<String, Object> attributes = Map.of(FileKeyStore.NAME, getTestName(),
FileKeyStore.PASSWORD, TLS_RESOURCE.getSecret(),
FileKeyStore.STORE_URL, keyStoreFile.toFile().getAbsolutePath(),
FileKeyStore.KEY_STORE_TYPE, TLS_RESOURCE.getKeyStoreType());
final FileKeyStore<?> keyStore = createFileKeyStore(attributes);
final List<CertificateDetails> certificateDetails = keyStore.getCertificateDetails();

final int keyCertificates = KeyStoreTestHelper
.getNumberOfCertificates(keyStoreFile, "PKCS12", TLS_RESOURCE.getSecret().toCharArray(), true);
assertEquals(keyCertificates, certificateDetails.size(), "Unexpected number of certificates");
assertEquals("private-key-alias", certificateDetails.get(0).getAlias(), "Unexpected alias name");
}

@SuppressWarnings("unchecked")
private FileKeyStore<?> createFileKeyStore(final Map<String, Object> attributes)
{
Expand Down
Loading

0 comments on commit de2c347

Please sign in to comment.