CMS creation/validation logic (#2340)

* CMS creation/validation logic

Signed-off-by: Lucas Saldanha <lucascrsaldanha@gmail.com>
pull/2463/head
Lucas Saldanha 3 years ago committed by GitHub
parent ec4db5b7a7
commit e92c9bc697
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      pki/build.gradle
  2. 107
      pki/src/main/java/org/hyperledger/besu/pki/cms/CmsCreator.java
  3. 181
      pki/src/main/java/org/hyperledger/besu/pki/cms/CmsValidator.java
  4. 43
      pki/src/main/java/org/hyperledger/besu/pki/crl/CRLUtil.java
  5. 11
      pki/src/main/java/org/hyperledger/besu/pki/keystore/SoftwareKeyStoreWrapper.java
  6. 338
      pki/src/test/java/org/hyperledger/besu/pki/cms/CmsCreationAndValidationTest.java
  7. 198
      pki/src/test/java/org/hyperledger/besu/pki/util/TestCertificateUtils.java

@ -36,4 +36,5 @@ dependencies {
testImplementation 'junit:junit' testImplementation 'junit:junit'
testImplementation 'org.assertj:assertj-core' testImplementation 'org.assertj:assertj-core'
testImplementation 'org.mockito:mockito-core' testImplementation 'org.mockito:mockito-core'
testImplementation 'org.apache.logging.log4j:log4j-core'
} }

@ -0,0 +1,107 @@
/*
* 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.cms;
import org.hyperledger.besu.pki.keystore.KeyStoreWrapper;
import java.security.PrivateKey;
import java.security.Security;
import java.security.cert.X509Certificate;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.tuweni.bytes.Bytes;
import org.bouncycastle.cert.jcajce.JcaCertStore;
import org.bouncycastle.cms.CMSProcessableByteArray;
import org.bouncycastle.cms.CMSSignedData;
import org.bouncycastle.cms.CMSSignedDataGenerator;
import org.bouncycastle.cms.CMSTypedData;
import org.bouncycastle.cms.jcajce.JcaSignerInfoGeneratorBuilder;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.DigestCalculatorProvider;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder;
import org.bouncycastle.util.Store;
public class CmsCreator {
static {
if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) {
Security.addProvider(new BouncyCastleProvider());
}
}
private static final Logger LOGGER = LogManager.getLogger();
private final String certificateAlias;
private final KeyStoreWrapper keyStore;
public CmsCreator(final KeyStoreWrapper keyStore, final String certificateAlias) {
this.keyStore = keyStore;
this.certificateAlias = certificateAlias;
}
/**
* Creates a CMS message with the content parameter, signed with the certificate with alias
* defined in the {@code CmsCreator} constructor. The certificate chain is also included so the
* recipient has the information needed to build a trusted certificate path when validating this
* message.
*
* @param contentToSign the content that will be signed and added to the message
* @return the CMS message bytes
*/
@SuppressWarnings("rawtypes")
public Bytes create(final Bytes contentToSign) {
try {
// Certificates that will be sent
final List<X509Certificate> x509Certificates =
Stream.of(keyStore.getCertificateChain(certificateAlias))
.map(X509Certificate.class::cast)
.collect(Collectors.toList());
final Store certs = new JcaCertStore(x509Certificates);
// Private key of the certificate that will sign the message
final PrivateKey privateKey = keyStore.getPrivateKey(certificateAlias);
final ContentSigner contentSigner =
new JcaContentSignerBuilder("SHA256withRSA").build(privateKey);
final CMSSignedDataGenerator cmsGenerator = new CMSSignedDataGenerator();
// Aditional intermediate certificates for path building
cmsGenerator.addCertificates(certs);
final DigestCalculatorProvider digestCalculatorProvider =
new JcaDigestCalculatorProviderBuilder().setProvider("BC").build();
// The first certificate in the list (leaf certificate is the signer)
cmsGenerator.addSignerInfoGenerator(
new JcaSignerInfoGeneratorBuilder(digestCalculatorProvider)
.build(contentSigner, x509Certificates.get(0)));
// Add signed content
final CMSTypedData cmsData = new CMSProcessableByteArray(contentToSign.toArray());
final CMSSignedData cmsSignedData = cmsGenerator.generate(cmsData, false);
return Bytes.wrap(cmsSignedData.getEncoded());
} catch (final Exception e) {
LOGGER.error("Error creating CMS data", e);
throw new RuntimeException("Error creating CMS data", e);
}
}
}

@ -0,0 +1,181 @@
/*
* 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.cms;
import org.hyperledger.besu.pki.keystore.KeyStoreWrapper;
import java.security.Security;
import java.security.cert.CertPathBuilder;
import java.security.cert.CertPathBuilderException;
import java.security.cert.CertStore;
import java.security.cert.PKIXBuilderParameters;
import java.security.cert.PKIXRevocationChecker;
import java.security.cert.PKIXRevocationChecker.Option;
import java.security.cert.X509CertSelector;
import java.security.cert.X509Certificate;
import java.util.Collection;
import java.util.EnumSet;
import java.util.Optional;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.tuweni.bytes.Bytes;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.jcajce.JcaCertStoreBuilder;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.cms.CMSException;
import org.bouncycastle.cms.CMSProcessableByteArray;
import org.bouncycastle.cms.CMSSignedData;
import org.bouncycastle.cms.SignerInformation;
import org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoVerifierBuilder;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.util.Store;
public class CmsValidator {
static {
if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) {
Security.addProvider(new BouncyCastleProvider());
}
}
private static final Logger LOGGER = LogManager.getLogger();
private final KeyStoreWrapper truststore;
private final Optional<CertStore> crlCertStore;
public CmsValidator(final KeyStoreWrapper truststore, final CertStore crlCertStore) {
this.truststore = truststore;
this.crlCertStore = Optional.ofNullable(crlCertStore);
}
/**
* Verifies that a CMS message signed content matched the expected content, and that the message
* signer is from a certificate that is trusted (has permission to propose a block)
*
* @param cms the CMS message bytes
* @param expectedContent the expected signed content in the CMS message
* @return true, if the signed content matched the expected content and the signer's certificate
* is trusted, otherwise returns false.
*/
public boolean validate(final Bytes cms, final Bytes expectedContent) {
try {
LOGGER.trace("Validating CMS message");
final CMSSignedData cmsSignedData =
new CMSSignedData(new CMSProcessableByteArray(expectedContent.toArray()), cms.toArray());
final X509Certificate signerCertificate = getSignerCertificate(cmsSignedData);
// Validate msg signature and content
if (!isSignatureValid(signerCertificate, cmsSignedData)) {
return false;
}
// Validate certificate trust
if (!isCertificateTrusted(signerCertificate, cmsSignedData)) {
return false;
}
return true;
} catch (final Exception e) {
LOGGER.error("Error validating CMS data", e);
throw new RuntimeException("Error validating CMS data", e);
}
}
@SuppressWarnings("unchecked")
private X509Certificate getSignerCertificate(final CMSSignedData cmsSignedData) {
try {
final Store<X509CertificateHolder> certificateStore = cmsSignedData.getCertificates();
// We don't expect more than one signer on the CMS data
if (cmsSignedData.getSignerInfos().size() != 1) {
throw new RuntimeException("Only one signer is expected on the CMS message");
}
final SignerInformation signerInformation =
cmsSignedData.getSignerInfos().getSigners().stream().findFirst().get();
// Find signer's certificate from CMS data
final Collection<X509CertificateHolder> signerCertificates =
certificateStore.getMatches(signerInformation.getSID());
final X509CertificateHolder certificateHolder = signerCertificates.stream().findFirst().get();
return new JcaX509CertificateConverter().getCertificate(certificateHolder);
} catch (final Exception e) {
LOGGER.error("Error retrieving signer certificate from CMS data", e);
throw new RuntimeException("Error retrieving signer certificate from CMS data", e);
}
}
private boolean isSignatureValid(
final X509Certificate signerCertificate, final CMSSignedData cmsSignedData) {
LOGGER.trace("Validating CMS signature");
try {
return cmsSignedData.verifySignatures(
sid -> new JcaSimpleSignerInfoVerifierBuilder().build(signerCertificate));
} catch (final CMSException e) {
return false;
}
}
private boolean isCertificateTrusted(
final X509Certificate signerCertificate, final CMSSignedData cmsSignedData) {
LOGGER.trace("Starting CMS certificate validation");
try {
final CertPathBuilder cpb = CertPathBuilder.getInstance("PKIX");
// Define CMS signer certificate as the starting point of the path (leaf certificate)
final X509CertSelector targetConstraints = new X509CertSelector();
targetConstraints.setCertificate(signerCertificate);
// Set parameters for the certificate path building algorithm
final PKIXBuilderParameters params =
new PKIXBuilderParameters(truststore.getKeyStore(), targetConstraints);
// Adding CertStore with CRLs (if present, otherwise disabling revocation check)
crlCertStore.ifPresentOrElse(
CRLs -> {
params.addCertStore(CRLs);
PKIXRevocationChecker rc = (PKIXRevocationChecker) cpb.getRevocationChecker();
rc.setOptions(EnumSet.of(Option.PREFER_CRLS));
params.addCertPathChecker(rc);
},
() -> {
LOGGER.warn("No CRL CertStore provided. CRL validation will be disabled.");
params.setRevocationEnabled(false);
});
// Read certificates sent on the CMS message and adding it to the path building algorithm
final CertStore cmsCertificates =
new JcaCertStoreBuilder().addCertificates(cmsSignedData.getCertificates()).build();
params.addCertStore(cmsCertificates);
// Validate certificate path
try {
cpb.build(params);
return true;
} catch (final CertPathBuilderException cpbe) {
LOGGER.warn("Untrusted certificate chain", cpbe);
LOGGER.trace("Reason for failed validation", cpbe.getCause());
return false;
}
} catch (final Exception e) {
LOGGER.error("Error validating certificate chain");
throw new RuntimeException("Error validating certificate chain", e);
}
}
}

@ -0,0 +1,43 @@
/*
* 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.crl;
import org.hyperledger.besu.pki.PkiException;
import java.io.FileInputStream;
import java.nio.file.Paths;
import java.security.cert.CertStore;
import java.security.cert.CertificateFactory;
import java.security.cert.CollectionCertStoreParameters;
import java.security.cert.X509CRL;
import java.util.List;
import java.util.stream.Collectors;
public class CRLUtil {
public static CertStore loadCRLs(final String path) {
try {
final List<X509CRL> crls =
CertificateFactory.getInstance("X509")
.generateCRLs(new FileInputStream(Paths.get(path).toFile())).stream()
.map(X509CRL.class::cast)
.collect(Collectors.toList());
return CertStore.getInstance("Collection", new CollectionCertStoreParameters(crls));
} catch (Exception e) {
throw new PkiException("Error loading CRL file " + path, e);
}
}
}

@ -90,7 +90,7 @@ public class SoftwareKeyStoreWrapper extends AbstractKeyStoreWrapper {
} }
@VisibleForTesting @VisibleForTesting
SoftwareKeyStoreWrapper( public SoftwareKeyStoreWrapper(
final KeyStore keystore, final KeyStore keystore,
final String keystorePassword, final String keystorePassword,
final KeyStore truststore, final KeyStore truststore,
@ -102,6 +102,15 @@ public class SoftwareKeyStoreWrapper extends AbstractKeyStoreWrapper {
this.truststorePassword = truststorePassword.toCharArray(); this.truststorePassword = truststorePassword.toCharArray();
} }
@VisibleForTesting
public SoftwareKeyStoreWrapper(final KeyStore keystore, final String keystorePassword) {
super(null);
this.keystore = keystore;
this.keystorePassword = keystorePassword.toCharArray();
this.truststore = null;
this.truststorePassword = null;
}
@Override @Override
public PrivateKey getPrivateKey(final String keyAlias) { public PrivateKey getPrivateKey(final String keyAlias) {
LOG.debug("Retrieving private key for alias: {}", keyAlias); LOG.debug("Retrieving private key for alias: {}", keyAlias);

@ -0,0 +1,338 @@
/*
* 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.cms;
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
import static org.hyperledger.besu.pki.util.TestCertificateUtils.createCRL;
import static org.hyperledger.besu.pki.util.TestCertificateUtils.createKeyPair;
import static org.hyperledger.besu.pki.util.TestCertificateUtils.createSelfSignedCertificate;
import static org.hyperledger.besu.pki.util.TestCertificateUtils.issueCertificate;
import org.hyperledger.besu.pki.keystore.KeyStoreWrapper;
import org.hyperledger.besu.pki.keystore.SoftwareKeyStoreWrapper;
import java.security.KeyPair;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.cert.CertStore;
import java.security.cert.Certificate;
import java.security.cert.CollectionCertStoreParameters;
import java.security.cert.X509CRL;
import java.security.cert.X509Certificate;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Collections;
import java.util.Set;
import org.apache.tuweni.bytes.Bytes;
import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder;
import org.bouncycastle.cms.CMSProcessableByteArray;
import org.bouncycastle.cms.CMSSignedData;
import org.bouncycastle.cms.CMSSignedDataGenerator;
import org.bouncycastle.cms.CMSTypedData;
import org.bouncycastle.cms.jcajce.JcaSignerInfoGeneratorBuilder;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.DigestCalculatorProvider;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
public class CmsCreationAndValidationTest {
private static KeyStoreWrapper keystoreWrapper;
private static KeyStoreWrapper truststoreWrapper;
private static CertStore CRLs;
private CmsValidator cmsValidator;
@BeforeClass
public static void beforeAll() throws Exception {
final Instant notBefore = Instant.now().minus(1, ChronoUnit.DAYS);
final Instant notAfter = Instant.now().plus(1, ChronoUnit.DAYS);
/*
Create self-signed certificate
*/
final KeyPair selfsignedKeyPair = createKeyPair();
final X509Certificate selfsignedCertificate =
createSelfSignedCertificate("selfsigned", notBefore, notAfter, selfsignedKeyPair);
/*
Create trusted chain (ca -> interca -> partneraca -> partneravalidator)
*/
final KeyPair caKeyPair = createKeyPair();
final X509Certificate caCertificate =
createSelfSignedCertificate("ca", notBefore, notAfter, caKeyPair);
final KeyPair interCAKeyPair = createKeyPair();
final X509Certificate interCACertificate =
issueCertificate(
caCertificate, caKeyPair, "interca", notBefore, notAfter, interCAKeyPair, true);
final KeyPair partnerACAPair = createKeyPair();
final X509Certificate partnerACACertificate =
issueCertificate(
interCACertificate,
interCAKeyPair,
"partneraca",
notBefore,
notAfter,
partnerACAPair,
true);
final KeyPair parterAValidatorKeyPair = createKeyPair();
final X509Certificate partnerAValidatorCertificate =
issueCertificate(
partnerACACertificate,
partnerACAPair,
"partneravalidator",
notBefore,
notAfter,
parterAValidatorKeyPair,
false);
/*
Create expired certificate
*/
final KeyPair expiredKeyPair = createKeyPair();
final X509Certificate expiredCertificate =
issueCertificate(
caCertificate,
caKeyPair,
"expired",
notBefore,
notBefore.plus(1, ChronoUnit.SECONDS),
expiredKeyPair,
true);
/*
Create revoked and revoked certificates
*/
final KeyPair revokedKeyPair = createKeyPair();
final X509Certificate revokedCertificate =
issueCertificate(
caCertificate, caKeyPair, "revoked", notBefore, notAfter, revokedKeyPair, true);
/*
Create untrusted chain (untrusted_selfsigned -> unstrusted_partner)
*/
final KeyPair untrustedSelfSignedKeyPair = createKeyPair();
final X509Certificate untrustedSelfsignedCertificate =
createSelfSignedCertificate(
"untrusted_selfsigned", notBefore, notAfter, untrustedSelfSignedKeyPair);
final KeyPair untrustedIntermediateKeyPair = createKeyPair();
final X509Certificate untrustedIntermediateCertificate =
issueCertificate(
untrustedSelfsignedCertificate,
untrustedSelfSignedKeyPair,
"unstrusted_partner",
notBefore,
notAfter,
untrustedIntermediateKeyPair,
true);
/*
Create truststore wrapper with 3 trusted certificates: 'ca', 'interca' and 'selfsigned'
*/
final KeyStore truststore = KeyStore.getInstance("PKCS12");
truststore.load(null, null);
truststore.setCertificateEntry("ca", caCertificate);
truststore.setCertificateEntry("interca", interCACertificate);
truststore.setCertificateEntry("selfsigned", selfsignedCertificate);
truststoreWrapper = new SoftwareKeyStoreWrapper(truststore, "");
/*
Create keystore with certificates used in tests
*/
final KeyStore keystore = KeyStore.getInstance("PKCS12");
keystore.load(null, null);
keystore.setKeyEntry(
"trusted_selfsigned",
selfsignedKeyPair.getPrivate(),
"".toCharArray(),
new Certificate[] {selfsignedCertificate});
keystore.setKeyEntry(
"untrusted_selfsigned",
untrustedSelfSignedKeyPair.getPrivate(),
"".toCharArray(),
new Certificate[] {untrustedSelfsignedCertificate});
keystore.setKeyEntry(
"expired",
expiredKeyPair.getPrivate(),
"".toCharArray(),
new Certificate[] {expiredCertificate});
keystore.setKeyEntry(
"revoked",
revokedKeyPair.getPrivate(),
"".toCharArray(),
new Certificate[] {revokedCertificate});
keystore.setKeyEntry(
"trusted",
parterAValidatorKeyPair.getPrivate(),
"".toCharArray(),
new Certificate[] {partnerAValidatorCertificate, partnerACACertificate});
keystore.setKeyEntry(
"untrusted",
untrustedIntermediateKeyPair.getPrivate(),
"".toCharArray(),
new Certificate[] {untrustedIntermediateCertificate, untrustedSelfsignedCertificate});
keystoreWrapper = new SoftwareKeyStoreWrapper(keystore, "");
/*
Create CRLs for all CA certificates (mostly empty, only ca has one revoked certificate)
*/
final X509CRL caCRL = createCRL(caCertificate, caKeyPair, Set.of(revokedCertificate));
final X509CRL intercaCRL =
createCRL(interCACertificate, interCAKeyPair, Collections.emptyList());
final X509CRL partnerACACRL =
createCRL(partnerACACertificate, partnerACAPair, Collections.emptyList());
final X509CRL selfsignedCRL =
createCRL(selfsignedCertificate, selfsignedKeyPair, Collections.emptyList());
CRLs =
CertStore.getInstance(
"Collection",
new CollectionCertStoreParameters(
Set.of(caCRL, intercaCRL, partnerACACRL, selfsignedCRL)));
}
@Before
public void before() {
cmsValidator = new CmsValidator(truststoreWrapper, CRLs);
}
@Test
public void cmsValidationWithTrustedSelfSignedCertificate() {
final CmsCreator cmsCreator = new CmsCreator(keystoreWrapper, "trusted_selfsigned");
final Bytes data = Bytes.random(32);
final Bytes cms = cmsCreator.create(data);
assertThat(cmsValidator.validate(cms, data)).isTrue();
}
@Test
public void cmsValidationWithUntrustedSelfSignedCertificate() {
final CmsCreator cmsCreator = new CmsCreator(keystoreWrapper, "untrusted_selfsigned");
final Bytes data = Bytes.random(32);
final Bytes cms = cmsCreator.create(data);
assertThat(cmsValidator.validate(cms, data)).isFalse();
}
@Test
public void cmsValidationWithTrustedChain() {
final CmsCreator cmsCreator = new CmsCreator(keystoreWrapper, "trusted");
final Bytes data = Bytes.random(32);
final Bytes cms = cmsCreator.create(data);
assertThat(cmsValidator.validate(cms, data)).isTrue();
}
@Test
public void cmsValidationWithUntrustedChain() {
final CmsCreator cmsCreator = new CmsCreator(keystoreWrapper, "untrusted");
final Bytes data = Bytes.random(32);
final Bytes cms = cmsCreator.create(data);
assertThat(cmsValidator.validate(cms, data)).isFalse();
}
@Test
public void cmsValidationWithExpiredCertificate() {
final CmsCreator cmsCreator = new CmsCreator(keystoreWrapper, "expired");
final Bytes data = Bytes.random(32);
final Bytes cms = cmsCreator.create(data);
assertThat(cmsValidator.validate(cms, data)).isFalse();
}
@Test
public void cmsValidationWithRevokedCertificate() {
final CmsCreator cmsCreator = new CmsCreator(keystoreWrapper, "revoked");
final Bytes data = Bytes.random(32);
final Bytes cms = cmsCreator.create(data);
assertThat(cmsValidator.validate(cms, data)).isFalse();
}
@Test
public void cmsValidationWithoutCRLConfigDisablesCRLCheck() {
final CmsCreator cmsCreator = new CmsCreator(keystoreWrapper, "revoked");
final Bytes data = Bytes.random(32);
final Bytes cms = cmsCreator.create(data);
// Overriding validator with instance without CRL CertStore
cmsValidator = new CmsValidator(truststoreWrapper, null);
// Because we don't have a CRL CertStore, revocation is not checked
assertThat(cmsValidator.validate(cms, data)).isTrue();
}
@Test
public void cmsValidationWithWrongSignedData() {
final CmsCreator cmsCreator = new CmsCreator(keystoreWrapper, "trusted");
final Bytes otherData = Bytes.random(32);
final Bytes cms = cmsCreator.create(otherData);
final Bytes expectedData = Bytes.random(32);
assertThat(cmsValidator.validate(cms, expectedData)).isFalse();
}
@Test
public void cmsValidationWithInvalidSignature() throws Exception {
// Create a CMS message signed with a certificate, but create SignerInfo using another
// certificate to trigger the signature verification to fail.
final PrivateKey privateKey = keystoreWrapper.getPrivateKey("trusted");
final X509Certificate signerCertificate =
(X509Certificate) keystoreWrapper.getCertificate("trusted");
final X509Certificate otherCertificate =
(X509Certificate) keystoreWrapper.getCertificate("trusted_selfsigned");
final ContentSigner contentSigner =
new JcaContentSignerBuilder("SHA256withRSA").build(privateKey);
final CMSSignedDataGenerator cmsGenerator = new CMSSignedDataGenerator();
cmsGenerator.addCertificate(new JcaX509CertificateHolder(signerCertificate));
cmsGenerator.addCertificate(new JcaX509CertificateHolder(otherCertificate));
final DigestCalculatorProvider digestCalculatorProvider =
new JcaDigestCalculatorProviderBuilder().setProvider("BC").build();
cmsGenerator.addSignerInfoGenerator(
new JcaSignerInfoGeneratorBuilder(digestCalculatorProvider)
.build(contentSigner, otherCertificate));
final Bytes expectedData = Bytes.random(32);
final CMSTypedData cmsData = new CMSProcessableByteArray(expectedData.toArray());
final CMSSignedData cmsSignedData = cmsGenerator.generate(cmsData, true);
final Bytes cmsBytes = Bytes.wrap(cmsSignedData.getEncoded());
assertThat(cmsValidator.validate(cmsBytes, expectedData)).isFalse();
}
}

@ -0,0 +1,198 @@
/*
* 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.util;
import java.io.IOException;
import java.math.BigInteger;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.Security;
import java.security.cert.CRLException;
import java.security.cert.CRLReason;
import java.security.cert.CertificateEncodingException;
import java.security.cert.X509CRL;
import java.security.cert.X509Certificate;
import java.sql.Date;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Collection;
import java.util.Random;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x509.BasicConstraints;
import org.bouncycastle.asn1.x509.Extension;
import org.bouncycastle.cert.X509CRLHolder;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.X509v2CRLBuilder;
import org.bouncycastle.cert.X509v3CertificateBuilder;
import org.bouncycastle.cert.jcajce.JcaX509CRLConverter;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.cert.jcajce.JcaX509ExtensionUtils;
import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
import org.bouncycastle.jce.X509KeyUsage;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
/*
This class provides utility method for creating certificates used on tests.
Based on https://stackoverflow.com/a/18648284/5021783
*/
public class TestCertificateUtils {
static {
if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) {
Security.addProvider(new BouncyCastleProvider());
}
}
public static KeyPair createKeyPair() {
try {
final KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
return kpg.generateKeyPair();
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("Error creating KeyPair", e);
}
}
public static X509Certificate createSelfSignedCertificate(
final String name, final Instant notBefore, final Instant notAfter, final KeyPair keyPair) {
try {
final ContentSigner signer =
new JcaContentSignerBuilder("SHA256WithRSAEncryption").build(keyPair.getPrivate());
final X509v3CertificateBuilder certificateBuilder =
new JcaX509v3CertificateBuilder(
new X500Name("CN=" + name),
new BigInteger(32, new Random()),
Date.from(notBefore),
Date.from(notAfter),
new X500Name("CN=" + name),
keyPair.getPublic())
.addExtension(
Extension.authorityKeyIdentifier,
false,
new JcaX509ExtensionUtils().createAuthorityKeyIdentifier(keyPair.getPublic()))
.addExtension(
Extension.subjectKeyIdentifier,
false,
new JcaX509ExtensionUtils().createSubjectKeyIdentifier(keyPair.getPublic()))
.addExtension(
Extension.basicConstraints,
false,
new BasicConstraints(true)) // true if it is allowed to sign other certs
.addExtension(
Extension.keyUsage,
true,
new X509KeyUsage(X509KeyUsage.keyCertSign | X509KeyUsage.cRLSign));
final X509CertificateHolder certHolder = certificateBuilder.build(signer);
return new JcaX509CertificateConverter()
.setProvider(BouncyCastleProvider.PROVIDER_NAME)
.getCertificate(certHolder);
} catch (final Exception e) {
throw new RuntimeException("Error creating CA certificate", e);
}
}
public static X509Certificate issueCertificate(
final X509Certificate issuer,
final KeyPair issuerKeyPair,
final String subject,
final Instant notBefore,
final Instant notAfter,
final KeyPair keyPair,
final boolean isCa) {
try {
final ContentSigner signer =
new JcaContentSignerBuilder("SHA256WithRSAEncryption").build(issuerKeyPair.getPrivate());
final X509v3CertificateBuilder certificateBuilder =
new JcaX509v3CertificateBuilder(
issuer,
new BigInteger(32, new Random()),
Date.from(notBefore),
Date.from(notAfter),
new X500Name("CN=" + subject),
keyPair.getPublic())
.addExtension(
Extension.authorityKeyIdentifier,
false,
new JcaX509ExtensionUtils()
.createAuthorityKeyIdentifier(issuerKeyPair.getPublic()))
.addExtension(
Extension.basicConstraints,
false,
new BasicConstraints(isCa)) // true if it is allowed to sign other certs
.addExtension(
Extension.keyUsage,
true,
new X509KeyUsage(
X509KeyUsage.digitalSignature
| X509KeyUsage.nonRepudiation
| X509KeyUsage.keyEncipherment
| X509KeyUsage.dataEncipherment
| X509KeyUsage.cRLSign
| X509KeyUsage.keyCertSign));
final X509CertificateHolder certHolder = certificateBuilder.build(signer);
return new JcaX509CertificateConverter()
.setProvider(BouncyCastleProvider.PROVIDER_NAME)
.getCertificate(certHolder);
} catch (final Exception e) {
throw new RuntimeException("Error creating certificate", e);
}
}
public static X509CRL createCRL(
final X509Certificate issuer,
final KeyPair issuerKeyPair,
final Collection<X509Certificate> revokedCertificates) {
try {
final X509CertificateHolder x509CertificateHolder =
new X509CertificateHolder(issuer.getEncoded());
final X509v2CRLBuilder crlBuilder =
new X509v2CRLBuilder(x509CertificateHolder.getSubject(), Date.from(Instant.now()));
revokedCertificates.forEach(
c ->
crlBuilder.addCRLEntry(
c.getSerialNumber(), Date.from(Instant.now()), CRLReason.UNSPECIFIED.ordinal()));
crlBuilder.setNextUpdate(Date.from(Instant.now().plus(1, ChronoUnit.DAYS)));
final ContentSigner signer =
new JcaContentSignerBuilder("SHA256WithRSAEncryption").build(issuerKeyPair.getPrivate());
final X509CRLHolder crlHolder = crlBuilder.build(signer);
return new JcaX509CRLConverter()
.setProvider(BouncyCastleProvider.PROVIDER_NAME)
.getCRL(crlHolder);
} catch (OperatorCreationException
| CRLException
| CertificateEncodingException
| IOException e) {
throw new RuntimeException(e);
}
}
}
Loading…
Cancel
Save