mirror of https://github.com/hyperledger/besu
Added PKI module (#2298)
* Added PKI keystore Signed-off-by: Lucas Saldanha <lucascrsaldanha@gmail.com>pull/2345/head
parent
68510e500b
commit
17b2d53aa8
@ -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' |
||||||
|
} |
@ -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); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -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); |
||||||
|
} |
||||||
|
} |
@ -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); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -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 |
||||||
|
*/ |
||||||
|
package org.hyperledger.besu.pki.keystore; |
||||||
|
|
||||||
|
import java.security.KeyStore; |
||||||
|
import java.security.PrivateKey; |
||||||
|
import java.security.PublicKey; |
||||||
|
import java.security.cert.Certificate; |
||||||
|
|
||||||
|
public interface KeyStoreWrapper { |
||||||
|
|
||||||
|
String KEYSTORE_TYPE_JKS = "JKS"; |
||||||
|
String KEYSTORE_TYPE_PKCS11 = "PKCS11"; |
||||||
|
String KEYSTORE_TYPE_PKCS12 = "PKCS12"; |
||||||
|
|
||||||
|
KeyStore getKeyStore(); |
||||||
|
|
||||||
|
KeyStore getTrustStore(); |
||||||
|
|
||||||
|
PrivateKey getPrivateKey(String keyAlias); |
||||||
|
|
||||||
|
PublicKey getPublicKey(String keyAlias); |
||||||
|
|
||||||
|
Certificate getCertificate(String certificateAlias); |
||||||
|
|
||||||
|
Certificate[] getCertificateChain(String certificateAlias); |
||||||
|
} |
@ -0,0 +1,215 @@ |
|||||||
|
/* |
||||||
|
* 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.FileInputStream; |
||||||
|
import java.io.InputStream; |
||||||
|
import java.nio.file.Path; |
||||||
|
import java.security.GeneralSecurityException; |
||||||
|
import java.security.Key; |
||||||
|
import java.security.KeyStore; |
||||||
|
import java.security.PrivateKey; |
||||||
|
import java.security.PublicKey; |
||||||
|
import java.security.cert.Certificate; |
||||||
|
import java.util.HashMap; |
||||||
|
import java.util.Map; |
||||||
|
|
||||||
|
import com.google.common.annotations.VisibleForTesting; |
||||||
|
import org.apache.logging.log4j.LogManager; |
||||||
|
import org.apache.logging.log4j.Logger; |
||||||
|
|
||||||
|
public class SoftwareKeyStoreWrapper implements KeyStoreWrapper { |
||||||
|
|
||||||
|
private static final Logger LOG = LogManager.getLogger(); |
||||||
|
|
||||||
|
private final KeyStore keystore; |
||||||
|
private final transient char[] keystorePassword; |
||||||
|
private KeyStore truststore; |
||||||
|
private transient char[] truststorePassword; |
||||||
|
|
||||||
|
private final Map<String, PrivateKey> cachedPrivateKeys = new HashMap<>(); |
||||||
|
private final Map<String, PublicKey> cachedPublicKeys = new HashMap<>(); |
||||||
|
private final Map<String, Certificate> cachedCertificates = new HashMap<>(); |
||||||
|
|
||||||
|
public SoftwareKeyStoreWrapper( |
||||||
|
final String keystoreType, final Path keystoreLocation, final String keystorePassword) { |
||||||
|
this(keystoreType, keystoreLocation, keystorePassword, null, null, null); |
||||||
|
} |
||||||
|
|
||||||
|
public SoftwareKeyStoreWrapper( |
||||||
|
final String keystoreType, |
||||||
|
final Path keystoreLocation, |
||||||
|
final String keystorePassword, |
||||||
|
final String truststoreType, |
||||||
|
final Path truststoreLocation, |
||||||
|
final String truststorePassword) { |
||||||
|
|
||||||
|
if (keystorePassword == null) { |
||||||
|
throw new IllegalArgumentException("Keystore password is null"); |
||||||
|
} |
||||||
|
this.keystorePassword = keystorePassword.toCharArray(); |
||||||
|
try (InputStream stream = new FileInputStream(keystoreLocation.toFile())) { |
||||||
|
keystore = KeyStore.getInstance(keystoreType); |
||||||
|
keystore.load(stream, this.keystorePassword); |
||||||
|
|
||||||
|
} catch (final Exception e) { |
||||||
|
throw new PkiException("Failed to initialize software keystore: " + keystoreLocation, e); |
||||||
|
} |
||||||
|
|
||||||
|
if (truststoreType != null && truststoreLocation != null) { |
||||||
|
this.truststorePassword = |
||||||
|
(truststorePassword != null) ? truststorePassword.toCharArray() : null; |
||||||
|
try (InputStream stream = new FileInputStream(truststoreLocation.toFile())) { |
||||||
|
truststore = KeyStore.getInstance(truststoreType); |
||||||
|
truststore.load(stream, this.truststorePassword); |
||||||
|
|
||||||
|
} catch (final Exception e) { |
||||||
|
throw new PkiException( |
||||||
|
"Failed to initialize software truststore: " + truststoreLocation, e); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@VisibleForTesting |
||||||
|
SoftwareKeyStoreWrapper( |
||||||
|
final KeyStore keystore, |
||||||
|
final String keystorePassword, |
||||||
|
final KeyStore truststore, |
||||||
|
final String truststorePassword) { |
||||||
|
this.keystore = keystore; |
||||||
|
this.keystorePassword = keystorePassword.toCharArray(); |
||||||
|
this.truststore = truststore; |
||||||
|
this.truststorePassword = truststorePassword.toCharArray(); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public PrivateKey getPrivateKey(final String keyAlias) { |
||||||
|
LOG.debug("Retrieving private key for alias: {}", keyAlias); |
||||||
|
return (PrivateKey) getKey(keyAlias, PrivateKey.class, cachedPrivateKeys); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public PublicKey getPublicKey(final String keyAlias) { |
||||||
|
LOG.debug("Retrieving public key for alias: {}", keyAlias); |
||||||
|
return (PublicKey) getKey(keyAlias, PublicKey.class, cachedPublicKeys); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public Certificate getCertificate(final String certificateAlias) { |
||||||
|
try { |
||||||
|
LOG.debug("Retrieving certificate for alias: {}", certificateAlias); |
||||||
|
Certificate certificate = cachedCertificates.get(certificateAlias); |
||||||
|
if (certificate == null) { |
||||||
|
LOG.debug("Certificate alias: {} not cached", certificateAlias); |
||||||
|
|
||||||
|
certificate = keystore.getCertificate(certificateAlias); |
||||||
|
if (certificate == null && truststore != null) { |
||||||
|
certificate = truststore.getCertificate(certificateAlias); |
||||||
|
} |
||||||
|
if (certificate != null) { |
||||||
|
LOG.debug("Certificate alias: {} found in keystore/truststore", certificateAlias); |
||||||
|
cachedCertificates.put(certificateAlias, certificate); |
||||||
|
cachedPublicKeys.put(certificateAlias, certificate.getPublicKey()); |
||||||
|
return certificate; |
||||||
|
} else { |
||||||
|
LOG.warn("Certificate alias: {} not found in keystore/truststore", certificateAlias); |
||||||
|
} |
||||||
|
} |
||||||
|
return certificate; |
||||||
|
|
||||||
|
} 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); |
||||||
|
|
||||||
|
Certificate[] certificateChain = keystore.getCertificateChain(certificateAlias); |
||||||
|
if (certificateChain == null && truststore != null) { |
||||||
|
certificateChain = truststore.getCertificateChain(certificateAlias); |
||||||
|
} |
||||||
|
return certificateChain; |
||||||
|
} catch (final Exception e) { |
||||||
|
throw new PkiException("Failed to certificate chain for alias: " + certificateAlias, e); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private Key getKey( |
||||||
|
final String keyAlias, |
||||||
|
final Class<? extends Key> keyTypeClass, |
||||||
|
final Map<String, ? extends Key> keyCache) { |
||||||
|
Key cachedKey = keyCache.get(keyAlias); |
||||||
|
if (cachedKey == null) { |
||||||
|
LOG.debug("Key alias: {} not cached", keyAlias); |
||||||
|
try { |
||||||
|
cachedKey = loadAndCacheKey(this.keystore, this.keystorePassword, keyAlias, keyTypeClass); |
||||||
|
if (cachedKey == null) { |
||||||
|
cachedKey = |
||||||
|
loadAndCacheKey(this.truststore, this.truststorePassword, keyAlias, keyTypeClass); |
||||||
|
} |
||||||
|
} catch (final Exception e) { |
||||||
|
throw new PkiException("Failed to get key: " + keyAlias, e); |
||||||
|
} |
||||||
|
} |
||||||
|
return cachedKey; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public KeyStore getKeyStore() { |
||||||
|
return keystore; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public KeyStore getTrustStore() { |
||||||
|
return truststore; |
||||||
|
} |
||||||
|
|
||||||
|
private Key loadAndCacheKey( |
||||||
|
final KeyStore keystore, |
||||||
|
final char[] keystorePassword, |
||||||
|
final String keyAlias, |
||||||
|
final Class<? extends Key> keyTypeClass) |
||||||
|
throws GeneralSecurityException { |
||||||
|
if (keystore != null && keystore.containsAlias(keyAlias)) { |
||||||
|
|
||||||
|
final Key key = keystore.getKey(keyAlias, keystorePassword); |
||||||
|
if (key != null) { |
||||||
|
LOG.debug("Key alias: {} found in keystore/truststore", keyAlias); |
||||||
|
if (key instanceof PrivateKey && PrivateKey.class.isAssignableFrom(keyTypeClass)) { |
||||||
|
cachedPrivateKeys.put(keyAlias, (PrivateKey) key); |
||||||
|
return key; |
||||||
|
} else if (key instanceof PublicKey && PublicKey.class.isAssignableFrom(keyTypeClass)) { |
||||||
|
cachedPublicKeys.put(keyAlias, (PublicKey) key); |
||||||
|
return key; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if (PublicKey.class.isAssignableFrom(keyTypeClass)) { |
||||||
|
final Certificate certificate = getCertificate(keyAlias); |
||||||
|
if (certificate != null) { |
||||||
|
return certificate.getPublicKey(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
LOG.warn("Key alias: {} not found in keystore/truststore", keyAlias); |
||||||
|
return null; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,81 @@ |
|||||||
|
/* |
||||||
|
* 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 static org.assertj.core.api.Assertions.assertThat; |
||||||
|
import static org.mockito.Mockito.when; |
||||||
|
|
||||||
|
import java.security.KeyStore; |
||||||
|
import java.security.PrivateKey; |
||||||
|
import java.security.PublicKey; |
||||||
|
import java.security.cert.Certificate; |
||||||
|
|
||||||
|
import org.junit.Before; |
||||||
|
import org.junit.Test; |
||||||
|
import org.junit.runner.RunWith; |
||||||
|
import org.mockito.Mock; |
||||||
|
import org.mockito.junit.MockitoJUnitRunner; |
||||||
|
|
||||||
|
@RunWith(MockitoJUnitRunner.class) |
||||||
|
public class HardwareKeyStoreWrapperTest { |
||||||
|
|
||||||
|
private static final String KEY_ALIAS = "keyalias"; |
||||||
|
private static final String CERTIFICATE_ALIAS = "certalias"; |
||||||
|
private static final char[] PASSWORD = "password".toCharArray(); |
||||||
|
|
||||||
|
@Mock private KeyStore keyStore; |
||||||
|
@Mock private PrivateKey privateKey; |
||||||
|
@Mock private PublicKey publicKey; |
||||||
|
@Mock private Certificate certificate; |
||||||
|
|
||||||
|
private HardwareKeyStoreWrapper keyStoreWrapper; |
||||||
|
|
||||||
|
@Before |
||||||
|
public void before() { |
||||||
|
keyStoreWrapper = new HardwareKeyStoreWrapper(keyStore, new String(PASSWORD)); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void getPrivateKey() throws Exception { |
||||||
|
when(keyStore.getKey(KEY_ALIAS, PASSWORD)).thenReturn(privateKey); |
||||||
|
|
||||||
|
assertThat(keyStoreWrapper.getPrivateKey(KEY_ALIAS)).isNotNull(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void getPublicKey() throws Exception { |
||||||
|
// Get public key from certificate
|
||||||
|
when(keyStore.getCertificate(KEY_ALIAS)).thenReturn(certificate); |
||||||
|
when(certificate.getPublicKey()).thenReturn(publicKey); |
||||||
|
|
||||||
|
assertThat(keyStoreWrapper.getPublicKey(KEY_ALIAS)).isNotNull(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void getCertificate() throws Exception { |
||||||
|
when(keyStore.getCertificate(CERTIFICATE_ALIAS)).thenReturn(certificate); |
||||||
|
|
||||||
|
assertThat(keyStoreWrapper.getCertificate(CERTIFICATE_ALIAS)).isNotNull(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void getCertificateChain() throws Exception { |
||||||
|
when(keyStore.getCertificateChain(CERTIFICATE_ALIAS)) |
||||||
|
.thenReturn(new Certificate[] {certificate}); |
||||||
|
|
||||||
|
assertThat(keyStoreWrapper.getCertificateChain(CERTIFICATE_ALIAS)).hasSize(1); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,196 @@ |
|||||||
|
/* |
||||||
|
* 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 static org.assertj.core.api.Assertions.assertThat; |
||||||
|
import static org.hyperledger.besu.pki.keystore.KeyStoreWrapper.KEYSTORE_TYPE_PKCS12; |
||||||
|
import static org.mockito.ArgumentMatchers.eq; |
||||||
|
import static org.mockito.Mockito.times; |
||||||
|
import static org.mockito.Mockito.verify; |
||||||
|
import static org.mockito.Mockito.when; |
||||||
|
|
||||||
|
import java.nio.file.Path; |
||||||
|
import java.security.KeyStore; |
||||||
|
import java.security.PrivateKey; |
||||||
|
import java.security.PublicKey; |
||||||
|
import java.security.cert.Certificate; |
||||||
|
|
||||||
|
import org.junit.Before; |
||||||
|
import org.junit.Test; |
||||||
|
import org.junit.runner.RunWith; |
||||||
|
import org.mockito.Mock; |
||||||
|
import org.mockito.junit.MockitoJUnitRunner; |
||||||
|
|
||||||
|
@RunWith(MockitoJUnitRunner.class) |
||||||
|
public class SoftwareKeyStoreWrapperTest { |
||||||
|
|
||||||
|
private static final String KEY_ALIAS = "keyalias"; |
||||||
|
private static final String CERTIFICATE_ALIAS = "certalias"; |
||||||
|
private static final char[] PASSWORD = "password".toCharArray(); |
||||||
|
|
||||||
|
private SoftwareKeyStoreWrapper keyStoreWrapper; |
||||||
|
|
||||||
|
@Mock private KeyStore keyStore; |
||||||
|
@Mock private KeyStore trustStore; |
||||||
|
@Mock private PrivateKey privateKey; |
||||||
|
@Mock private PublicKey publicKey; |
||||||
|
@Mock private Certificate certificate; |
||||||
|
|
||||||
|
@Before |
||||||
|
public void before() { |
||||||
|
keyStoreWrapper = new SoftwareKeyStoreWrapper(keyStore, new String(PASSWORD), null, ""); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void getPrivateKey() throws Exception { |
||||||
|
when(keyStore.containsAlias(KEY_ALIAS)).thenReturn(true); |
||||||
|
when(keyStore.getKey(KEY_ALIAS, PASSWORD)).thenReturn(privateKey); |
||||||
|
|
||||||
|
assertThat(keyStoreWrapper.getPrivateKey(KEY_ALIAS)).isNotNull(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void getPrivateKeyCaching() throws Exception { |
||||||
|
when(keyStore.containsAlias(KEY_ALIAS)).thenReturn(true); |
||||||
|
when(keyStore.getKey(KEY_ALIAS, PASSWORD)).thenReturn(privateKey); |
||||||
|
|
||||||
|
keyStoreWrapper.getPrivateKey(KEY_ALIAS); |
||||||
|
keyStoreWrapper.getPrivateKey(KEY_ALIAS); |
||||||
|
|
||||||
|
verify(keyStore, times(1)).getKey(eq(KEY_ALIAS), eq(PASSWORD)); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void getPrivateKeyFallbackToTrustStore() throws Exception { |
||||||
|
keyStoreWrapper = |
||||||
|
new SoftwareKeyStoreWrapper( |
||||||
|
keyStore, new String(PASSWORD), trustStore, new String(PASSWORD)); |
||||||
|
|
||||||
|
when(keyStore.containsAlias(KEY_ALIAS)).thenReturn(false); |
||||||
|
when(trustStore.containsAlias(KEY_ALIAS)).thenReturn(true); |
||||||
|
when(trustStore.getKey(KEY_ALIAS, PASSWORD)).thenReturn(privateKey); |
||||||
|
|
||||||
|
assertThat(keyStoreWrapper.getPrivateKey(KEY_ALIAS)).isNotNull(); |
||||||
|
|
||||||
|
verify(trustStore).getKey(eq(KEY_ALIAS), eq(PASSWORD)); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void getPublicKey() throws Exception { |
||||||
|
when(keyStore.containsAlias(KEY_ALIAS)).thenReturn(true); |
||||||
|
when(keyStore.getKey(KEY_ALIAS, PASSWORD)).thenReturn(publicKey); |
||||||
|
|
||||||
|
assertThat(keyStoreWrapper.getPublicKey(KEY_ALIAS)).isNotNull(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void getPublicKeyCaching() throws Exception { |
||||||
|
when(keyStore.containsAlias(KEY_ALIAS)).thenReturn(true); |
||||||
|
when(keyStore.getKey(KEY_ALIAS, PASSWORD)).thenReturn(publicKey); |
||||||
|
|
||||||
|
keyStoreWrapper.getPublicKey(KEY_ALIAS); |
||||||
|
keyStoreWrapper.getPublicKey(KEY_ALIAS); |
||||||
|
|
||||||
|
verify(keyStore, times(1)).getKey(eq(KEY_ALIAS), eq(PASSWORD)); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void getPublicKeyFallbackToTrustStore() throws Exception { |
||||||
|
keyStoreWrapper = |
||||||
|
new SoftwareKeyStoreWrapper( |
||||||
|
keyStore, new String(PASSWORD), trustStore, new String(PASSWORD)); |
||||||
|
|
||||||
|
when(keyStore.containsAlias(KEY_ALIAS)).thenReturn(false); |
||||||
|
when(trustStore.containsAlias(KEY_ALIAS)).thenReturn(true); |
||||||
|
when(trustStore.getKey(KEY_ALIAS, PASSWORD)).thenReturn(publicKey); |
||||||
|
|
||||||
|
assertThat(keyStoreWrapper.getPublicKey(KEY_ALIAS)).isNotNull(); |
||||||
|
|
||||||
|
verify(trustStore).getKey(eq(KEY_ALIAS), eq(PASSWORD)); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void getCertificate() throws Exception { |
||||||
|
when(keyStore.getCertificate(CERTIFICATE_ALIAS)).thenReturn(certificate); |
||||||
|
|
||||||
|
assertThat(keyStoreWrapper.getCertificate(CERTIFICATE_ALIAS)).isNotNull(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void getCertificateCaching() throws Exception { |
||||||
|
when(keyStore.getCertificate(CERTIFICATE_ALIAS)).thenReturn(certificate); |
||||||
|
|
||||||
|
keyStoreWrapper.getCertificate(CERTIFICATE_ALIAS); |
||||||
|
keyStoreWrapper.getCertificate(CERTIFICATE_ALIAS); |
||||||
|
|
||||||
|
verify(keyStore, times(1)).getCertificate(eq(CERTIFICATE_ALIAS)); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void getCertificateFallbackToTrustStore() throws Exception { |
||||||
|
keyStoreWrapper = |
||||||
|
new SoftwareKeyStoreWrapper( |
||||||
|
keyStore, new String(PASSWORD), trustStore, new String(PASSWORD)); |
||||||
|
|
||||||
|
when(keyStore.getCertificate(CERTIFICATE_ALIAS)).thenReturn(null); |
||||||
|
when(trustStore.getCertificate(CERTIFICATE_ALIAS)).thenReturn(certificate); |
||||||
|
|
||||||
|
assertThat(keyStoreWrapper.getCertificate(CERTIFICATE_ALIAS)).isNotNull(); |
||||||
|
|
||||||
|
verify(trustStore).getCertificate(eq(CERTIFICATE_ALIAS)); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void getCertificateChain() throws Exception { |
||||||
|
when(keyStore.getCertificateChain(CERTIFICATE_ALIAS)) |
||||||
|
.thenReturn(new Certificate[] {certificate}); |
||||||
|
|
||||||
|
assertThat(keyStoreWrapper.getCertificateChain(CERTIFICATE_ALIAS)).hasSize(1); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void getCertificateChainFallbackToTrustStore() throws Exception { |
||||||
|
keyStoreWrapper = |
||||||
|
new SoftwareKeyStoreWrapper( |
||||||
|
keyStore, new String(PASSWORD), trustStore, new String(PASSWORD)); |
||||||
|
|
||||||
|
when(keyStore.getCertificateChain(CERTIFICATE_ALIAS)).thenReturn(null); |
||||||
|
when(trustStore.getCertificateChain(CERTIFICATE_ALIAS)) |
||||||
|
.thenReturn(new Certificate[] {certificate}); |
||||||
|
|
||||||
|
assertThat(keyStoreWrapper.getCertificateChain(CERTIFICATE_ALIAS)).hasSize(1); |
||||||
|
|
||||||
|
verify(trustStore).getCertificateChain(eq(CERTIFICATE_ALIAS)); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void loadKeyStoreFromFile() { |
||||||
|
SoftwareKeyStoreWrapper loadedKeyStore = |
||||||
|
new SoftwareKeyStoreWrapper( |
||||||
|
KEYSTORE_TYPE_PKCS12, |
||||||
|
Path.of("src/test/resources/keystore/keystore"), |
||||||
|
"validator", |
||||||
|
KEYSTORE_TYPE_PKCS12, |
||||||
|
Path.of("src/test/resources/keystore/keystore"), |
||||||
|
"validator"); |
||||||
|
|
||||||
|
assertThat(loadedKeyStore.getPublicKey("validator")).isNotNull(); |
||||||
|
assertThat(loadedKeyStore.getPrivateKey("validator")).isNotNull(); |
||||||
|
assertThat(loadedKeyStore.getCertificate("validator")).isNotNull(); |
||||||
|
// CA -> INTERCA -> PARTNERACA -> VALIDATOR
|
||||||
|
assertThat(loadedKeyStore.getCertificateChain("validator")).hasSize(4); |
||||||
|
} |
||||||
|
} |
Binary file not shown.
Binary file not shown.
@ -0,0 +1 @@ |
|||||||
|
mock-maker-inline |
Loading…
Reference in new issue