/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.nifi.web.security.x509.ocsp; import org.bouncycastle.asn1.x500.X500Name; import org.bouncycastle.asn1.x509.ExtendedKeyUsage; import org.bouncycastle.asn1.x509.KeyPurposeId; import org.bouncycastle.asn1.x509.KeyUsage; import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; import org.bouncycastle.asn1.x509.X509Extension; import org.bouncycastle.cert.X509CertificateHolder; import org.bouncycastle.cert.X509v3CertificateBuilder; import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.operator.ContentSigner; import org.bouncycastle.operator.OperatorCreationException; import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Ignore; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import java.math.BigInteger; import java.security.InvalidKeyException; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.security.PrivateKey; import java.security.PublicKey; import java.security.Security; import java.security.SignatureException; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.Date; import java.util.Vector; public class OcspCertificateValidatorTest { private static final Logger logger = LoggerFactory.getLogger(OcspCertificateValidatorTest.class); private static final int KEY_SIZE = 2048; private static final long YESTERDAY = System.currentTimeMillis() - 24 * 60 * 60 * 1000; private static final long ONE_YEAR_FROM_NOW = System.currentTimeMillis() + 365 * 24 * 60 * 60 * 1000; private static final String SIGNATURE_ALGORITHM = "SHA256withRSA"; private static final String PROVIDER = "BC"; private static final String ISSUER_DN = "CN=NiFi Test CA,OU=Security,O=Apache,ST=CA,C=US"; private static X509Certificate ISSUER_CERTIFICATE; @BeforeClass public static void setUpOnce() throws Exception { Security.addProvider(new BouncyCastleProvider()); // ISSUER_CERTIFICATE = generateCertificate(ISSUER_DN); } @Before public void setUp() throws Exception { } @After public void tearDown() throws Exception { } /** * Generates a public/private RSA keypair using the default key size. * * @return the keypair * @throws NoSuchAlgorithmException if the RSA algorithm is not available */ private static KeyPair generateKeyPair() throws NoSuchAlgorithmException { KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); keyPairGenerator.initialize(KEY_SIZE); return keyPairGenerator.generateKeyPair(); } /** * Generates a signed certificate using an on-demand keypair. * * @param dn the DN * @return the certificate * @throws IOException if an exception occurs * @throws NoSuchAlgorithmException if an exception occurs * @throws CertificateException if an exception occurs * @throws NoSuchProviderException if an exception occurs * @throws SignatureException if an exception occurs * @throws InvalidKeyException if an exception occurs * @throws OperatorCreationException if an exception occurs */ private static X509Certificate generateCertificate(String dn) throws IOException, NoSuchAlgorithmException, CertificateException, NoSuchProviderException, SignatureException, InvalidKeyException, OperatorCreationException { KeyPair keyPair = generateKeyPair(); return generateCertificate(dn, keyPair); } /** * Generates a signed certificate with a specific keypair. * * @param dn the DN * @param keyPair the public key will be included in the certificate and the the private key is used to sign the certificate * @return the certificate * @throws IOException if an exception occurs * @throws NoSuchAlgorithmException if an exception occurs * @throws CertificateException if an exception occurs * @throws NoSuchProviderException if an exception occurs * @throws SignatureException if an exception occurs * @throws InvalidKeyException if an exception occurs * @throws OperatorCreationException if an exception occurs */ private static X509Certificate generateCertificate(String dn, KeyPair keyPair) throws IOException, NoSuchAlgorithmException, CertificateException, NoSuchProviderException, SignatureException, InvalidKeyException, OperatorCreationException { PrivateKey privateKey = keyPair.getPrivate(); ContentSigner sigGen = new JcaContentSignerBuilder(SIGNATURE_ALGORITHM).setProvider(PROVIDER).build(privateKey); SubjectPublicKeyInfo subPubKeyInfo = SubjectPublicKeyInfo.getInstance(keyPair.getPublic().getEncoded()); Date startDate = new Date(YESTERDAY); Date endDate = new Date(ONE_YEAR_FROM_NOW); X509v3CertificateBuilder certBuilder = new X509v3CertificateBuilder( new X500Name(dn), BigInteger.valueOf(System.currentTimeMillis()), startDate, endDate, new X500Name(dn), subPubKeyInfo); // Set certificate extensions // (1) digitalSignature extension certBuilder.addExtension(X509Extension.keyUsage, true, new KeyUsage(KeyUsage.digitalSignature | KeyUsage.keyEncipherment | KeyUsage.dataEncipherment | KeyUsage.keyAgreement)); // (2) extendedKeyUsage extension Vector<KeyPurposeId> ekUsages = new Vector<>(); ekUsages.add(KeyPurposeId.id_kp_clientAuth); ekUsages.add(KeyPurposeId.id_kp_serverAuth); certBuilder.addExtension(X509Extension.extendedKeyUsage, false, new ExtendedKeyUsage(ekUsages)); // Sign the certificate X509CertificateHolder certificateHolder = certBuilder.build(sigGen); return new JcaX509CertificateConverter().setProvider(PROVIDER) .getCertificate(certificateHolder); } /** * Generates a certificate signed by the issuer key. * * @param dn the subject DN * @param issuerDn the issuer DN * @param issuerKey the issuer private key * @return the certificate * @throws IOException if an exception occurs * @throws NoSuchAlgorithmException if an exception occurs * @throws CertificateException if an exception occurs * @throws NoSuchProviderException if an exception occurs * @throws SignatureException if an exception occurs * @throws InvalidKeyException if an exception occurs * @throws OperatorCreationException if an exception occurs */ private static X509Certificate generateIssuedCertificate(String dn, String issuerDn, PrivateKey issuerKey) throws IOException, NoSuchAlgorithmException, CertificateException, NoSuchProviderException, SignatureException, InvalidKeyException, OperatorCreationException { KeyPair keyPair = generateKeyPair(); return generateIssuedCertificate(dn, keyPair.getPublic(), issuerDn, issuerKey); } /** * Generates a certificate with a specific public key signed by the issuer key. * * @param dn the subject DN * @param publicKey the subject public key * @param issuerDn the issuer DN * @param issuerKey the issuer private key * @return the certificate * @throws IOException if an exception occurs * @throws NoSuchAlgorithmException if an exception occurs * @throws CertificateException if an exception occurs * @throws NoSuchProviderException if an exception occurs * @throws SignatureException if an exception occurs * @throws InvalidKeyException if an exception occurs * @throws OperatorCreationException if an exception occurs */ private static X509Certificate generateIssuedCertificate(String dn, PublicKey publicKey, String issuerDn, PrivateKey issuerKey) throws IOException, NoSuchAlgorithmException, CertificateException, NoSuchProviderException, SignatureException, InvalidKeyException, OperatorCreationException { ContentSigner sigGen = new JcaContentSignerBuilder(SIGNATURE_ALGORITHM).setProvider(PROVIDER).build(issuerKey); SubjectPublicKeyInfo subPubKeyInfo = SubjectPublicKeyInfo.getInstance(publicKey.getEncoded()); Date startDate = new Date(YESTERDAY); Date endDate = new Date(ONE_YEAR_FROM_NOW); X509v3CertificateBuilder v3CertGen = new X509v3CertificateBuilder( new X500Name(issuerDn), BigInteger.valueOf(System.currentTimeMillis()), startDate, endDate, new X500Name(dn), subPubKeyInfo); X509CertificateHolder certificateHolder = v3CertGen.build(sigGen); return new JcaX509CertificateConverter().setProvider(PROVIDER) .getCertificate(certificateHolder); } @Test public void testShouldGenerateCertificate() throws Exception { // Arrange final String testDn = "CN=This is a test"; // Act X509Certificate certificate = generateCertificate(testDn); logger.info("Generated certificate: \n{}", certificate); // Assert assert certificate.getSubjectDN().getName().equals(testDn); assert certificate.getIssuerDN().getName().equals(testDn); certificate.verify(certificate.getPublicKey()); } @Test public void testShouldGenerateCertificateFromKeyPair() throws Exception { // Arrange final String testDn = "CN=This is a test"; final KeyPair keyPair = generateKeyPair(); // Act X509Certificate certificate = generateCertificate(testDn, keyPair); logger.info("Generated certificate: \n{}", certificate); // Assert assert certificate.getPublicKey().equals(keyPair.getPublic()); assert certificate.getSubjectDN().getName().equals(testDn); assert certificate.getIssuerDN().getName().equals(testDn); certificate.verify(certificate.getPublicKey()); } @Test public void testShouldGenerateIssuedCertificate() throws Exception { // Arrange final String testDn = "CN=This is a signed test"; final String issuerDn = "CN=Issuer CA"; final KeyPair issuerKeyPair = generateKeyPair(); final PrivateKey issuerPrivateKey = issuerKeyPair.getPrivate(); final X509Certificate issuerCertificate = generateCertificate(issuerDn, issuerKeyPair); logger.info("Generated issuer certificate: \n{}", issuerCertificate); // Act X509Certificate certificate = generateIssuedCertificate(testDn, issuerDn, issuerPrivateKey); logger.info("Generated signed certificate: \n{}", certificate); // Assert assert issuerCertificate.getPublicKey().equals(issuerKeyPair.getPublic()); assert certificate.getSubjectX500Principal().getName().equals(testDn); assert certificate.getIssuerX500Principal().getName().equals(issuerDn); certificate.verify(issuerCertificate.getPublicKey()); try { certificate.verify(certificate.getPublicKey()); Assert.fail("Should have thrown exception"); } catch (Exception e) { assert e instanceof SignatureException; assert e.getMessage().contains("certificate does not verify with supplied key"); } } @Ignore("To be implemented with Groovy test") @Test public void testShouldValidateCertificate() throws Exception { } @Ignore("To be implemented with Groovy test") @Test public void testShouldNotValidateEmptyCertificate() throws Exception { } @Ignore("To be implemented with Groovy test") @Test public void testShouldNotValidateInvalidCertificate() throws Exception { } @Ignore("To be implemented with Groovy test") @Test public void testValidateShouldHandleUnsignedResponse() throws Exception { } @Ignore("To be implemented with Groovy test") @Test public void testValidateShouldHandleResponseWithIncorrectNonce() throws Exception { } }