Skip to content

Commit

Permalink
Added PKI module (hyperledger#2298)
Browse files Browse the repository at this point in the history
* Added PKI keystore

Signed-off-by: Lucas Saldanha <lucascrsaldanha@gmail.com>
  • Loading branch information
lucassaldanha authored May 31, 2021
1 parent 68510e5 commit 17b2d53
Show file tree
Hide file tree
Showing 13 changed files with 920 additions and 0 deletions.
1 change: 1 addition & 0 deletions besu/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ jar {

dependencies {
implementation project(':config')
implementation project(':pki')
implementation project(':consensus:clique')
implementation project(':consensus:common')
implementation project(':consensus:ibft')
Expand Down
39 changes: 39 additions & 0 deletions pki/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* Copyright ConsenSys AG.
*
* 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
*/

apply plugin: 'java-library'

jar {
archiveBaseName = 'besu-pki'
manifest {
attributes(
'Specification-Title': archiveBaseName,
'Specification-Version': project.version,
'Implementation-Title': archiveBaseName,
'Implementation-Version': calculateVersion()
)
}
}

dependencies {
implementation 'com.google.guava:guava'
implementation 'org.apache.logging.log4j:log4j-api'
implementation 'org.apache.tuweni:bytes'
implementation 'org.bouncycastle:bcpkix-jdk15on'

testImplementation 'junit:junit'
testImplementation 'org.assertj:assertj-core'
testImplementation 'org.mockito:mockito-core'
}
141 changes: 141 additions & 0 deletions pki/src/main/java/org/hyperledger/besu/pki/PkiConfiguration.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
/*
* Copyright ConsenSys AG.
*
* 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.pki;

import static java.util.Objects.requireNonNull;

import java.nio.file.Path;
import java.util.function.Supplier;

public class PkiConfiguration {

public static String DEFAULT_KEYSTORE_TYPE = "PKCS12";
public static String DEFAULT_CERTIFICATE_ALIAS = "validator";

private final String keyStoreType;
private final Path keyStorePath;
private final Supplier<String> keyStorePasswordSupplier;
private final String certificateAlias;
private final String trustStoreType;
private final Path trustStorePath;
private final Supplier<String> trustStorePasswordSupplier;

public PkiConfiguration(
final String keyStoreType,
final Path keyStorePath,
final Supplier<String> keyStorePasswordSupplier,
final String certificateAlias,
final String trustStoreType,
final Path trustStorePath,
final Supplier<String> trustStorePasswordSupplier) {
this.keyStoreType = keyStoreType;
this.keyStorePath = keyStorePath;
this.keyStorePasswordSupplier = keyStorePasswordSupplier;
this.certificateAlias = certificateAlias;
this.trustStoreType = trustStoreType;
this.trustStorePath = trustStorePath;
this.trustStorePasswordSupplier = trustStorePasswordSupplier;
}

public String getKeyStoreType() {
return keyStoreType;
}

public Path getKeyStorePath() {
return keyStorePath;
}

public String getKeyStorePassword() {
return null == keyStorePasswordSupplier ? null : keyStorePasswordSupplier.get();
}

public String getCertificateAlias() {
return certificateAlias;
}

public String getTrustStoreType() {
return trustStoreType;
}

public Path getTrustStorePath() {
return trustStorePath;
}

public String getTrustStorePassword() {
return trustStorePasswordSupplier.get();
}

public static final class Builder {

private String keyStoreType = DEFAULT_KEYSTORE_TYPE;
private Path keyStorePath;
private Supplier<String> keyStorePasswordSupplier;
private String certificateAlias = DEFAULT_CERTIFICATE_ALIAS;
private String trustStoreType = DEFAULT_KEYSTORE_TYPE;
private Path trustStorePath;
private Supplier<String> trustStorePasswordSupplier;

public Builder() {}

public Builder withKeyStoreType(final String keyStoreType) {
this.keyStoreType = keyStoreType;
return this;
}

public Builder withKeyStorePath(final Path keyStorePath) {
this.keyStorePath = keyStorePath;
return this;
}

public Builder withKeyStorePasswordSupplier(final Supplier<String> keyStorePasswordSupplier) {
this.keyStorePasswordSupplier = keyStorePasswordSupplier;
return this;
}

public Builder withCertificateAlias(final String certificateAlias) {
this.certificateAlias = certificateAlias;
return this;
}

public Builder withTrustStoreType(final String trustStoreType) {
this.trustStoreType = trustStoreType;
return this;
}

public Builder withTrustStorePath(final Path trustStorePath) {
this.trustStorePath = trustStorePath;
return this;
}

public Builder withTrustStorePasswordSupplier(
final Supplier<String> trustStorePasswordSupplier) {
this.trustStorePasswordSupplier = trustStorePasswordSupplier;
return this;
}

public PkiConfiguration build() {
requireNonNull(keyStoreType, "Key Store Type must not be null");
requireNonNull(keyStorePasswordSupplier, "Key Store password supplier must not be null");
return new PkiConfiguration(
keyStoreType,
keyStorePath,
keyStorePasswordSupplier,
certificateAlias,
trustStoreType,
trustStorePath,
trustStorePasswordSupplier);
}
}
}
36 changes: 36 additions & 0 deletions pki/src/main/java/org/hyperledger/besu/pki/PkiException.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* Copyright ConsenSys AG.
*
* 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.pki;

public class PkiException extends RuntimeException {

private static final long serialVersionUID = 1L;

public PkiException() {
super();
}

public PkiException(final String message) {
super(message);
}

public PkiException(final String message, final Throwable t) {
super(message, t);
}

public PkiException(final Throwable t) {
super(t);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
/*
* Copyright ConsenSys AG.
*
* 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.pki.keystore;

import org.hyperledger.besu.pki.PkiException;

import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.nio.file.Path;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.Provider;
import java.security.PublicKey;
import java.security.Security;
import java.security.cert.Certificate;
import java.util.Optional;
import java.util.Properties;
import java.util.stream.Stream;

import com.google.common.annotations.VisibleForTesting;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

/**
* Creates an instance of this class which is backed by a PKCS#11 keystore, such as a software
* (emulated) HSM or a physical/cloud HSM (see <a href=
* "https://docs.oracle.com/en/java/javase/11/security/pkcs11-reference-guide1.html">here</a>
*/
public class HardwareKeyStoreWrapper implements KeyStoreWrapper {

private static final Logger LOG = LogManager.getLogger();

private static final String pkcs11Provider = "SunPKCS11";

private final KeyStore keystore;
private final transient char[] keystorePassword;

private final java.security.Provider provider;

public HardwareKeyStoreWrapper(final String keystorePassword, final Provider provider) {
try {
if (provider == null) {
throw new IllegalArgumentException("Provider is null");
}
this.keystorePassword = keystorePassword.toCharArray();

this.provider = provider;
if (Security.getProvider(provider.getName()) == null) {
Security.addProvider(provider);
}

keystore = KeyStore.getInstance(KeyStoreWrapper.KEYSTORE_TYPE_PKCS11, provider);
keystore.load(null, this.keystorePassword);

} catch (final Exception e) {
throw new PkiException("Failed to initialize HSM keystore", e);
}
}

public HardwareKeyStoreWrapper(final String keystorePassword, final Path config) {
try {
if (keystorePassword == null) {
throw new IllegalArgumentException("Keystore password is null");
}
final Properties properties = new Properties();
final File configFile = config.toFile();
try (InputStream ins = new FileInputStream(configFile)) {
properties.load(ins);
}
final String name = properties.getProperty("name");
this.keystorePassword = keystorePassword.toCharArray();
final Optional<Provider> existingProvider =
Stream.of(Security.getProviders())
.filter(p -> p.getName().equals(String.format("%s-%s", pkcs11Provider, name)))
.findAny();
if (existingProvider.isPresent()) {
provider = existingProvider.get();
} else {
provider = getPkcs11Provider(configFile.getAbsolutePath());
Security.addProvider(provider);
}

keystore = KeyStore.getInstance(KeyStoreWrapper.KEYSTORE_TYPE_PKCS11, provider);
keystore.load(null, this.keystorePassword);

} catch (final Exception e) {
throw new PkiException("Failed to initialize HSM keystore", e);
}
}

@VisibleForTesting
HardwareKeyStoreWrapper(final KeyStore keystore, final String password) {
this.keystore = keystore;
this.keystorePassword = password.toCharArray();
this.provider = null;
}

@Override
public PrivateKey getPrivateKey(final String keyAlias) {
try {
LOG.debug("Retrieving private key for alias: {}", keyAlias);
return (PrivateKey) keystore.getKey(keyAlias, this.keystorePassword);
} catch (final Exception e) {
throw new PkiException("Failed to get key: " + keyAlias, e);
}
}

@Override
public PublicKey getPublicKey(final String keyAlias) {
try {
LOG.debug("Retrieving public key for alias: {}", keyAlias);
final Certificate certificate = keystore.getCertificate(keyAlias);
return (certificate != null) ? certificate.getPublicKey() : null;
} catch (final Exception e) {
throw new PkiException("Failed to get key: " + keyAlias, e);
}
}

@Override
public Certificate getCertificate(final String certificateAlias) {
try {
LOG.debug("Retrieving certificate for alias: {}", certificateAlias);
return keystore.getCertificate(certificateAlias);
} catch (final Exception e) {
throw new PkiException("Failed to get certificate: " + certificateAlias, e);
}
}

@Override
public Certificate[] getCertificateChain(final String certificateAlias) {
try {
LOG.debug("Retrieving certificate chain for alias: {}", certificateAlias);
return keystore.getCertificateChain(certificateAlias);
} catch (final Exception e) {
throw new PkiException("Failed to certificate chain for alias: " + certificateAlias, e);
}
}

@Override
public KeyStore getKeyStore() {
return keystore;
}

@Override
public KeyStore getTrustStore() {
return keystore;
}

private Provider getPkcs11Provider(final String config) {
final Provider provider = Security.getProvider(pkcs11Provider);
if (null == provider) {
throw new IllegalArgumentException("Unable to load PKCS11 provider configuration.");
} else {
return provider.configure(config);
}
}
}
Loading

0 comments on commit 17b2d53

Please sign in to comment.