Skip to content

Commit

Permalink
[WFCORE-5279] Add Certificate Authorities (Accounts)
Browse files Browse the repository at this point in the history
  • Loading branch information
jessicarod7 committed Aug 19, 2022
1 parent 22ea044 commit f7b100e
Show file tree
Hide file tree
Showing 36 changed files with 5,609 additions and 338 deletions.
7 changes: 7 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
<version.org.jboss.arquillian.junit>1.6.0.Final</version.org.jboss.arquillian.junit>
<version.org.jboss.galleon>4.2.8.Final</version.org.jboss.galleon>
<version.org.jmockit>1.39</version.org.jmockit>
<version.org.mockserver>5.8.1</version.org.mockserver>


<!-- Plugin versions and their dependency versions -->
Expand Down Expand Up @@ -336,6 +337,12 @@
<version>${version.org.jmockit}</version>
</dependency>
<dependency>
<groupId>org.mock-server</groupId>
<artifactId>mockserver-netty</artifactId>
<version>${version.org.mockserver}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.wildfly.core</groupId>
<artifactId>wildfly-core-test-runner</artifactId>
<version>${version.org.wildfly.core}</version>
Expand Down
6 changes: 6 additions & 0 deletions subsystem/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,11 @@
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
</dependency>
<dependency>
<groupId>org.mock-server</groupId>
<artifactId>mockserver-netty</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jmockit</groupId>
<artifactId>jmockit</artifactId>
Expand All @@ -150,6 +155,7 @@
<include>*.xml</include>
</includes>
<excludes>
<exclude>elytron-tls-expressions.xml</exclude>
</excludes>
<systemId>src/main/resources/schema/elytron-tls-subsystem_1_0.xsd</systemId>
</validationSet>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
/*
* JBoss, Home of Professional Open Source.
* Copyright 2018 Red Hat, Inc., and individual contributors
* as indicated by the @author tags.
*
* Licensed 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.wildfly.extension.elytron.tls.subsystem;

import static org.wildfly.extension.elytron.tls.subsystem._private.ElytronTLSLogger.LOGGER;

import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import java.util.List;
import java.util.function.Supplier;

import org.jboss.as.controller.OperationContext;
import org.jboss.as.controller.OperationFailedException;
import org.jboss.msc.Service;
import org.jboss.msc.service.ServiceRegistry;
import org.jboss.msc.service.StartContext;
import org.jboss.msc.service.StartException;
import org.jboss.msc.service.StopContext;
import org.wildfly.common.function.ExceptionSupplier;
import org.wildfly.security.credential.source.CredentialSource;
import org.wildfly.security.x500.cert.acme.AcmeAccount;
import org.wildfly.security.x500.cert.acme.CertificateAuthority;

/**
* A {@link Service} responsible for a single {@link AcmeAccount} instance.
*
* @author <a href="mailto:[email protected]">Farah Juma</a>
*/
class AcmeAccountService implements Service {

private Supplier<KeyStore> keyStoreSupplier;
private ExceptionSupplier<CredentialSource, Exception> credentialSourceSupplier;
private final String certificateAuthorityName;
private final List<String> contactUrlsList;
private final String alias;
private final String keyStoreName;
private volatile AcmeAccount acmeAccount;

AcmeAccountService(String certificateAuthorityName, List<String> contactUrlsList, String alias, String keyStoreName) {
this.certificateAuthorityName = certificateAuthorityName;
this.contactUrlsList = contactUrlsList;
this.alias = alias;
this.keyStoreName = keyStoreName;
}

@Override
public void start(StartContext startContext) throws StartException {
try {
final ServiceRegistry serviceRegistry = startContext.getController().getServiceContainer();
final ModifiableKeyStoreService keyStoreService = CertificateAuthorityAccountDefinition.getModifiableKeyStoreService(serviceRegistry, keyStoreName);
char[] keyPassword = resolveKeyPassword((KeyStoreService) keyStoreService);
KeyStore keyStore = keyStoreSupplier.get();
CertificateAuthority certificateAuthority;
if (certificateAuthorityName.equalsIgnoreCase(CertificateAuthority.LETS_ENCRYPT.getName())) {
certificateAuthority = CertificateAuthority.LETS_ENCRYPT;
} else {
certificateAuthority = CertificateAuthorityDefinition.getCertificateAuthorityService(serviceRegistry, certificateAuthorityName).getValue();
}

AcmeAccount.Builder acmeAccountBuilder = AcmeAccount.builder()
.setServerUrl(certificateAuthority.getUrl());
if (certificateAuthority.getStagingUrl() != null) {
acmeAccountBuilder.setStagingServerUrl(certificateAuthority.getStagingUrl());
}
if (! contactUrlsList.isEmpty()) {
acmeAccountBuilder = acmeAccountBuilder.setContactUrls(contactUrlsList.toArray(new String[contactUrlsList.size()]));
}
boolean updateAccountKeyStore = false;
if (keyStore.containsAlias(alias)) {
// use existing account key pair
X509Certificate certificate = (X509Certificate) keyStore.getCertificate(alias);
if (certificate == null) {
throw LOGGER.unableToObtainCertificateAuthorityAccountCertificate(alias);
}
PrivateKey privateKey = (PrivateKey) keyStore.getKey(alias, keyPassword);
if (privateKey == null) {
throw LOGGER.unableToObtainCertificateAuthorityAccountPrivateKey(alias);
}
acmeAccountBuilder = acmeAccountBuilder.setKey(certificate, privateKey);
} else {
updateAccountKeyStore = true;
}
acmeAccount = acmeAccountBuilder.build();
if (updateAccountKeyStore) {
saveCertificateAuthorityAccountKey(keyStoreService, keyPassword); // persist the generated key pair
}
} catch (Exception e) {
throw LOGGER.unableToStartService(e);
}
}

@Override
public void stop(StopContext stopContext) {
acmeAccount = null;
}

public AcmeAccount getValue() throws IllegalStateException, IllegalArgumentException {
return acmeAccount;
}

void setKeyStoreSupplier(Supplier<KeyStore> keyStoreSupplier) {
this.keyStoreSupplier = keyStoreSupplier;
}

void setCredentialSourceSupplier(ExceptionSupplier<CredentialSource, Exception> credentialSourceSupplier) {
this.credentialSourceSupplier = credentialSourceSupplier;
}

char[] resolveKeyPassword(KeyStoreService keyStoreService) throws RuntimeException {
try {
return keyStoreService.resolveKeyPassword(credentialSourceSupplier);
} catch (Exception e) {
throw new RuntimeException(e);
}
}

void saveCertificateAuthorityAccountKey(OperationContext operationContext) throws OperationFailedException {
final ModifiableKeyStoreService keyStoreService = CertificateAuthorityAccountDefinition.getModifiableKeyStoreService(operationContext, keyStoreName);
char[] keyPassword = resolveKeyPassword((KeyStoreService) keyStoreService);
saveCertificateAuthorityAccountKey(keyStoreService, keyPassword);
}

private void saveCertificateAuthorityAccountKey(ModifiableKeyStoreService keyStoreService, char[] keyPassword) throws OperationFailedException {
KeyStore modifiableAccountkeyStore = keyStoreService.getModifiableValue();
try {
modifiableAccountkeyStore.setKeyEntry(alias, acmeAccount.getPrivateKey(), keyPassword, new X509Certificate[]{ acmeAccount.getCertificate() });
} catch (KeyStoreException e) {
throw LOGGER.unableToUpdateCertificateAuthorityAccountKeyStore(e, e.getLocalizedMessage());
}
((KeyStoreService) keyStoreService).save();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* Copyright 2022 Red Hat, Inc.
*
* Licensed 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.wildfly.extension.elytron.tls.subsystem;

import static org.wildfly.extension.elytron.tls.subsystem.Constants.CERTIFICATE_AUTHORITIES;
import static org.wildfly.extension.elytron.tls.subsystem.Constants.CERTIFICATE_AUTHORITY;
import static org.wildfly.extension.elytron.tls.subsystem.Constants.CERTIFICATE_AUTHORITY_ACCOUNT;
import static org.wildfly.extension.elytron.tls.subsystem.Constants.CERTIFICATE_AUTHORITY_ACCOUNTS;

import org.jboss.as.controller.PathElement;
import org.jboss.as.controller.PersistentResourceXMLDescription;

class CAParsers {
final PersistentResourceXMLDescription certificateAuthorityParser_1_0 = PersistentResourceXMLDescription.builder(PathElement.pathElement(CERTIFICATE_AUTHORITY))
.setXmlWrapperElement(CERTIFICATE_AUTHORITIES)
.addAttribute(CertificateAuthorityDefinition.URL)
.addAttribute(CertificateAuthorityDefinition.STAGING_URL)
.build();

final PersistentResourceXMLDescription certificateAuthorityAccountParser_1_0 = PersistentResourceXMLDescription.builder(PathElement.pathElement(CERTIFICATE_AUTHORITY_ACCOUNT))
.setXmlWrapperElement(CERTIFICATE_AUTHORITY_ACCOUNTS)
.addAttribute(CertificateAuthorityAccountDefinition.CERTIFICATE_AUTHORITY)
.addAttribute(CertificateAuthorityAccountDefinition.CONTACT_URLS)
.addAttribute(CertificateAuthorityAccountDefinition.KEY_STORE)
.addAttribute(CertificateAuthorityAccountDefinition.ALIAS)
.addAttribute(CertificateAuthorityAccountDefinition.CREDENTIAL_REFERENCE)
.build();
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
* The capabilities are same as the ones provided by the Elytron subsystem
*
* @author <a href="mailto:[email protected]">Martin Mazanek</a>
* @author <a href="mailto:[email protected]">Cameron Rodriguez</a>
*/
class Capabilities {

Expand Down
Loading

0 comments on commit f7b100e

Please sign in to comment.