Skip to content

Commit

Permalink
Added support for PKCS11 keystore on PKI Block Creation (hyperledger#…
Browse files Browse the repository at this point in the history
…2865)

* Added support for PKCS11 keystore on PKI Block Creation

Signed-off-by: Lucas Saldanha <[email protected]>
  • Loading branch information
lucassaldanha authored and jflo committed Oct 13, 2021
1 parent baac20c commit d5ec791
Show file tree
Hide file tree
Showing 176 changed files with 411 additions and 106 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

### Additions and Improvements
- Add CLI autocomplete scripts. [#2854](https://github.com/hyperledger/besu/pull/2854)
- Added support for PKCS11 keystore on PKI Block Creation. [#2865](https://github.com/hyperledger/besu/pull/2865)

### Bug Fixes

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,20 +34,17 @@
import org.hyperledger.besu.pki.config.PkiKeyStoreConfiguration;
import org.hyperledger.besu.pki.keystore.KeyStoreWrapper;
import org.hyperledger.besu.tests.acceptance.dsl.node.configuration.genesis.GenesisConfigurationProvider;
import org.hyperledger.besu.tests.acceptance.dsl.node.configuration.pki.PKCS11Utils;

import java.io.File;
import java.io.IOException;
import java.net.URISyntaxException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;

import com.google.common.base.Charsets;

public class BesuNodeConfigurationBuilder {

private String name;
Expand Down Expand Up @@ -273,14 +270,14 @@ private static Path toPath(final String path) throws Exception {
public BesuNodeConfigurationBuilder p2pTLSEnabled(final String name, final String type) {
final TLSConfiguration.Builder builder = TLSConfiguration.Builder.tlsConfiguration();
try {
final String nsspin = "/p2p-tls/%s/nsspin.txt";
final String truststore = "/p2p-tls/%s/truststore.jks";
final String crl = "/p2p-tls/%s/crl.pem";
final String nsspin = "/pki-certs/%s/nsspin.txt";
final String truststore = "/pki-certs/%s/truststore.jks";
final String crl = "/pki-certs/%s/crl.pem";
switch (type) {
case KeyStoreWrapper.KEYSTORE_TYPE_JKS:
builder
.withKeyStoreType(type)
.withKeyStorePath(toPath(String.format("/p2p-tls/%s/keystore.jks", name)))
.withKeyStorePath(toPath(String.format("/pki-certs/%s/keystore.jks", name)))
.withKeyStorePasswordSupplier(
new FileBasedPasswordProvider(toPath(String.format(nsspin, name))))
.withKeyStorePasswordPath(toPath(String.format(nsspin, name)))
Expand All @@ -294,7 +291,7 @@ public BesuNodeConfigurationBuilder p2pTLSEnabled(final String name, final Strin
case KeyStoreWrapper.KEYSTORE_TYPE_PKCS12:
builder
.withKeyStoreType(type)
.withKeyStorePath(toPath(String.format("/p2p-tls/%s/keys.p12", name)))
.withKeyStorePath(toPath(String.format("/pki-certs/%s/keys.p12", name)))
.withKeyStorePasswordSupplier(
new FileBasedPasswordProvider(toPath(String.format(nsspin, name))))
.withKeyStorePasswordPath(toPath(String.format(nsspin, name)))
Expand All @@ -309,7 +306,8 @@ public BesuNodeConfigurationBuilder p2pTLSEnabled(final String name, final Strin
builder
.withKeyStoreType(type)
.withKeyStorePath(
initNSSConfigFile(toPath(String.format("/p2p-tls/%s/nss.cfg", name))))
PKCS11Utils.initNSSConfigFile(
toPath(String.format("/pki-certs/%s/nss.cfg", name))))
.withKeyStorePasswordSupplier(
new FileBasedPasswordProvider(toPath(String.format(nsspin, name))))
.withKeyStorePasswordPath(toPath(String.format(nsspin, name)))
Expand All @@ -323,34 +321,6 @@ public BesuNodeConfigurationBuilder p2pTLSEnabled(final String name, final Strin
return this;
}

private Path initNSSConfigFile(final Path srcFilePath) {
Path ret = null;
try {
final String content = Files.readString(srcFilePath);
final String updated =
content.replaceAll(
"(nssSecmodDirectory\\W*)(\\.\\/.*)",
"$1".concat(srcFilePath.toAbsolutePath().toString().replace("nss.cfg", "nssdb")));
final Path targetFilePath = createTemporaryFile("nsscfg");
Files.write(targetFilePath, updated.getBytes(Charsets.UTF_8));
ret = targetFilePath;
} catch (IOException e) {
throw new RuntimeException("Error populating nss config file", e);
}
return ret;
}

private Path createTemporaryFile(final String suffix) {
final File tempFile;
try {
tempFile = File.createTempFile("temp", suffix);
tempFile.deleteOnExit();
} catch (IOException e) {
throw new RuntimeException("Error creating temporary file", e);
}
return tempFile.toPath();
}

public BesuNodeConfigurationBuilder pkiBlockCreationEnabled(
final PkiKeyStoreConfiguration pkiKeyStoreConfiguration) {
this.pkiKeyStoreConfiguration = Optional.of(pkiKeyStoreConfiguration);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -397,7 +397,19 @@ public BesuNode createQbftNode(final String name) throws IOException {
.build());
}

public BesuNode createPkiQbftNode(final String name) throws IOException {
public BesuNode createPkiQbftJKSNode(final String name) throws IOException {
return createPkiQbftNode(KeyStoreWrapper.KEYSTORE_TYPE_JKS, name);
}

public BesuNode createPkiQbftPKCS11Node(final String name) throws IOException {
return createPkiQbftNode(KeyStoreWrapper.KEYSTORE_TYPE_PKCS11, name);
}

public BesuNode createPkiQbftPKCS12Node(final String name) throws IOException {
return createPkiQbftNode(KeyStoreWrapper.KEYSTORE_TYPE_PKCS12, name);
}

public BesuNode createPkiQbftNode(final String type, final String name) throws IOException {
return create(
new BesuNodeConfigurationBuilder()
.name(name)
Expand All @@ -406,7 +418,7 @@ public BesuNode createPkiQbftNode(final String name) throws IOException {
.webSocketConfiguration(node.createWebSocketEnabledConfig())
.devMode(false)
.genesisConfigProvider(genesis::createQbftGenesisConfig)
.pkiBlockCreationEnabled(pkiKeystoreConfigurationFactory.createPkiConfig())
.pkiBlockCreationEnabled(pkiKeystoreConfigurationFactory.createPkiConfig(type, name))
.build());
}

Expand Down Expand Up @@ -522,8 +534,23 @@ public BesuNode createQbftNodeWithValidators(final String name, final String...
.build());
}

public BesuNode createPkiQbftNodeWithValidators(final String name, final String... validators)
public BesuNode createPkiQbftJKSNodeWithValidators(final String name, final String... validators)
throws IOException {
return createPkiQbftNodeWithValidators(KeyStoreWrapper.KEYSTORE_TYPE_JKS, name, validators);
}

public BesuNode createPkiQbftPKCS11NodeWithValidators(
final String name, final String... validators) throws IOException {
return createPkiQbftNodeWithValidators(KeyStoreWrapper.KEYSTORE_TYPE_PKCS11, name, validators);
}

public BesuNode createPkiQbftPKCS12NodeWithValidators(
final String name, final String... validators) throws IOException {
return createPkiQbftNodeWithValidators(KeyStoreWrapper.KEYSTORE_TYPE_PKCS12, name, validators);
}

public BesuNode createPkiQbftNodeWithValidators(
final String type, final String name, final String... validators) throws IOException {

return create(
new BesuNodeConfigurationBuilder()
Expand All @@ -532,7 +559,7 @@ public BesuNode createPkiQbftNodeWithValidators(final String name, final String.
.jsonRpcConfiguration(node.createJsonRpcWithQbftEnabledConfig(false))
.webSocketConfiguration(node.createWebSocketEnabledConfig())
.devMode(false)
.pkiBlockCreationEnabled(pkiKeystoreConfigurationFactory.createPkiConfig())
.pkiBlockCreationEnabled(pkiKeystoreConfigurationFactory.createPkiConfig(type, name))
.genesisConfigProvider(
nodes ->
node.createGenesisConfigForValidators(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
* Copyright Hyperledger Besu Contributors.
*
* 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.
*
* SPDX-License-Identifier: Apache-2.0
*/

package org.hyperledger.besu.tests.acceptance.dsl.node.configuration.pki;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;

import com.google.common.base.Charsets;

public class PKCS11Utils {

public static Path initNSSConfigFile(final Path srcFilePath) {
Path ret = null;
try {
final String content = Files.readString(srcFilePath);
final String updated =
content.replaceAll(
"(nssSecmodDirectory\\W*)(\\.\\/.*)",
"$1".concat(srcFilePath.toAbsolutePath().toString().replace("nss.cfg", "nssdb")));
final Path targetFilePath = createTemporaryFile("nsscfg");
Files.write(targetFilePath, updated.getBytes(Charsets.UTF_8));
ret = targetFilePath;
} catch (IOException e) {
throw new RuntimeException("Error populating nss config file", e);
}
return ret;
}

private static Path createTemporaryFile(final String suffix) {
final File tempFile;
try {
tempFile = File.createTempFile("temp", suffix);
tempFile.deleteOnExit();
} catch (IOException e) {
throw new RuntimeException("Error creating temporary file", e);
}
return tempFile.toPath();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import static org.hyperledger.besu.pki.util.TestCertificateUtils.issueCertificate;

import org.hyperledger.besu.pki.config.PkiKeyStoreConfiguration;
import org.hyperledger.besu.pki.keystore.KeyStoreWrapper;

import java.io.FileOutputStream;
import java.io.IOException;
Expand All @@ -35,44 +36,87 @@
import java.security.cert.X509Certificate;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Objects;
import java.util.UUID;

public class PkiKeystoreConfigurationFactory {

public static final String KEYSTORE_DEFAULT_TYPE = "PKCS12";
/*
PKCS11 config files
*/
final String NSSCONFIG_PATH_STRING = "/pki-certs/%s/nss.cfg";
final String NSSPIN_PATH_STRING = "/pki-certs/%s/nsspin.txt";
final String TRUSTSTORE_PATH_STRING = "/pki-certs/%s/truststore.jks";
final String CRL_PATH_STRING = "/pki-certs/%s/crl.pem";

/*
Software keystore config
*/
public static final String KEYSTORE_DEFAULT_PASSWORD = "password";
public static final String KEYSTORE_DEFAULT_CERT_ALIAS = "validator";

private KeyPair caKeyPair;
private X509Certificate caCertificate;
private Path trustStoreFile;
private Path passwordFile;

public PkiKeyStoreConfiguration createPkiConfig() {
public PkiKeyStoreConfiguration createPkiConfig(final String type, final String name) {
if (KeyStoreWrapper.KEYSTORE_TYPE_PKCS11.equals(type)) {
return createPKCS11PkiConfig(name);
} else {
return createSoftwareKeyStorePkiConfig(type, name);
}
}

private PkiKeyStoreConfiguration createPKCS11PkiConfig(final String name) {
final PkiKeyStoreConfiguration.Builder pkiKeyStoreConfigBuilder =
new PkiKeyStoreConfiguration.Builder();

try {
pkiKeyStoreConfigBuilder
.withKeyStoreType(KeyStoreWrapper.KEYSTORE_TYPE_PKCS11)
.withKeyStorePath(
PKCS11Utils.initNSSConfigFile(
readResourceAsPath(String.format(NSSCONFIG_PATH_STRING, name))))
.withKeyStorePasswordPath(readResourceAsPath(String.format(NSSPIN_PATH_STRING, name)))
.withTrustStoreType(KeyStoreWrapper.KEYSTORE_TYPE_JKS)
.withTrustStorePath(readResourceAsPath(String.format(TRUSTSTORE_PATH_STRING, name)))
.withTrustStorePasswordPath(readResourceAsPath(String.format(NSSPIN_PATH_STRING, name)))
.withCrlFilePath(readResourceAsPath(String.format(CRL_PATH_STRING, name)))
.withCertificateAlias(name);

} catch (Exception e) {
throw new RuntimeException(e);
}

return pkiKeyStoreConfigBuilder.build();
}

private PkiKeyStoreConfiguration createSoftwareKeyStorePkiConfig(
final String type, final String name) {
PkiKeyStoreConfiguration.Builder pkiKeyStoreConfigBuilder =
new PkiKeyStoreConfiguration.Builder();

pkiKeyStoreConfigBuilder.withTrustStoreType(KEYSTORE_DEFAULT_TYPE);
pkiKeyStoreConfigBuilder.withTrustStorePath(createTrustStore());
pkiKeyStoreConfigBuilder.withTrustStoreType(type);
pkiKeyStoreConfigBuilder.withTrustStorePath(createTrustStore(type));
pkiKeyStoreConfigBuilder.withTrustStorePasswordPath(passwordFile);

pkiKeyStoreConfigBuilder.withKeyStoreType(KEYSTORE_DEFAULT_TYPE);
pkiKeyStoreConfigBuilder.withKeyStorePath(createKeyStore());
pkiKeyStoreConfigBuilder.withKeyStoreType(type);
pkiKeyStoreConfigBuilder.withKeyStorePath(createKeyStore(type, name));
pkiKeyStoreConfigBuilder.withKeyStorePasswordPath(passwordFile);

pkiKeyStoreConfigBuilder.withCertificateAlias(KEYSTORE_DEFAULT_CERT_ALIAS);
pkiKeyStoreConfigBuilder.withCertificateAlias(name);

return pkiKeyStoreConfigBuilder.build();
}

private Path createTrustStore() {
private Path createTrustStore(final String type) {
// Only create the truststore if this is the first time this method is being called
if (caKeyPair == null) {
try {
caKeyPair = createKeyPair();
caCertificate = createSelfSignedCertificate("ca", notBefore(), notAfter(), caKeyPair);

final KeyStore truststore = KeyStore.getInstance(KEYSTORE_DEFAULT_TYPE);
final KeyStore truststore = KeyStore.getInstance(type);
truststore.load(null, null);
truststore.setCertificateEntry("ca", caCertificate);

Expand All @@ -87,20 +131,20 @@ private Path createTrustStore() {
return trustStoreFile;
}

private Path createKeyStore() {
private Path createKeyStore(final String type, final String alias) {
if (caKeyPair == null) {
createTrustStore();
createTrustStore(type);
}

final KeyPair kp = createKeyPair();
final X509Certificate certificate =
issueCertificate(caCertificate, caKeyPair, "validator", notBefore(), notAfter(), kp, false);

try {
final KeyStore keyStore = KeyStore.getInstance(KEYSTORE_DEFAULT_TYPE);
final KeyStore keyStore = KeyStore.getInstance(type);
keyStore.load(null, null);
keyStore.setKeyEntry(
"validator",
alias,
kp.getPrivate(),
KEYSTORE_DEFAULT_PASSWORD.toCharArray(),
new Certificate[] {certificate, caCertificate});
Expand Down Expand Up @@ -144,4 +188,8 @@ private Instant notBefore() {
private Instant notAfter() {
return Instant.now().plus(10, ChronoUnit.DAYS);
}

private Path readResourceAsPath(final String path) throws Exception {
return Path.of(Objects.requireNonNull(this.getClass().getResource(path)).toURI());
}
}
Loading

0 comments on commit d5ec791

Please sign in to comment.