NIFI-3331 TLS Toolkit - add the possibility to define SAN in issued certificates.

Added unit tests for SAN inclusion in CertificateUtils#generateIssuedCertificate() and TlsHelper#generateCertificationRequest().
Fixed typos.

This closes #1491.

Signed-off-by: Andy LoPresto <alopresto@apache.org>
This commit is contained in:
Andy LoPresto 2017-02-09 14:47:39 +01:00
parent 71e2061b5d
commit 6fc30900b9
No known key found for this signature in database
GPG Key ID: 3C6EF65B2F7DEF69
11 changed files with 345 additions and 139 deletions

View File

@ -17,7 +17,12 @@
package org.apache.nifi.security.util; package org.apache.nifi.security.util;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.bouncycastle.asn1.ASN1Encodable;
import org.bouncycastle.asn1.ASN1ObjectIdentifier; import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.ASN1Set;
import org.bouncycastle.asn1.DERSequence;
import org.bouncycastle.asn1.pkcs.Attribute;
import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
import org.bouncycastle.asn1.x500.AttributeTypeAndValue; import org.bouncycastle.asn1.x500.AttributeTypeAndValue;
import org.bouncycastle.asn1.x500.RDN; import org.bouncycastle.asn1.x500.RDN;
import org.bouncycastle.asn1.x500.X500Name; import org.bouncycastle.asn1.x500.X500Name;
@ -25,6 +30,7 @@ import org.bouncycastle.asn1.x500.style.BCStyle;
import org.bouncycastle.asn1.x509.BasicConstraints; import org.bouncycastle.asn1.x509.BasicConstraints;
import org.bouncycastle.asn1.x509.ExtendedKeyUsage; import org.bouncycastle.asn1.x509.ExtendedKeyUsage;
import org.bouncycastle.asn1.x509.Extension; import org.bouncycastle.asn1.x509.Extension;
import org.bouncycastle.asn1.x509.Extensions;
import org.bouncycastle.asn1.x509.KeyPurposeId; import org.bouncycastle.asn1.x509.KeyPurposeId;
import org.bouncycastle.asn1.x509.KeyUsage; import org.bouncycastle.asn1.x509.KeyUsage;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
@ -37,6 +43,7 @@ import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.operator.ContentSigner; import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.OperatorCreationException; import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequest;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -124,6 +131,7 @@ public final class CertificateUtils {
this.description = description; this.description = description;
} }
@Override
public String toString() { public String toString() {
return "Client Auth: " + this.description + " (" + this.value + ")"; return "Client Auth: " + this.description + " (" + this.value + ")";
} }
@ -541,6 +549,24 @@ public final class CertificateUtils {
*/ */
public static X509Certificate generateIssuedCertificate(String dn, PublicKey publicKey, X509Certificate issuer, KeyPair issuerKeyPair, String signingAlgorithm, int days) public static X509Certificate generateIssuedCertificate(String dn, PublicKey publicKey, X509Certificate issuer, KeyPair issuerKeyPair, String signingAlgorithm, int days)
throws CertificateException { throws CertificateException {
return generateIssuedCertificate(dn, publicKey, null, issuer, issuerKeyPair, signingAlgorithm, days);
}
/**
* Generates an issued {@link X509Certificate} from the given issuer certificate and {@link KeyPair}
*
* @param dn the distinguished name to use
* @param publicKey the public key to issue the certificate to
* @param extensions extensions extracted from the CSR
* @param issuer the issuer's certificate
* @param issuerKeyPair the issuer's keypair
* @param signingAlgorithm the signing algorithm to use
* @param days the number of days it should be valid for
* @return an issued {@link X509Certificate} from the given issuer certificate and {@link KeyPair}
* @throws CertificateException if there is an error issuing the certificate
*/
public static X509Certificate generateIssuedCertificate(String dn, PublicKey publicKey, Extensions extensions, X509Certificate issuer, KeyPair issuerKeyPair, String signingAlgorithm, int days)
throws CertificateException {
try { try {
ContentSigner sigGen = new JcaContentSignerBuilder(signingAlgorithm).setProvider(BouncyCastleProvider.PROVIDER_NAME).build(issuerKeyPair.getPrivate()); ContentSigner sigGen = new JcaContentSignerBuilder(signingAlgorithm).setProvider(BouncyCastleProvider.PROVIDER_NAME).build(issuerKeyPair.getPrivate());
SubjectPublicKeyInfo subPubKeyInfo = SubjectPublicKeyInfo.getInstance(publicKey.getEncoded()); SubjectPublicKeyInfo subPubKeyInfo = SubjectPublicKeyInfo.getInstance(publicKey.getEncoded());
@ -567,6 +593,11 @@ public final class CertificateUtils {
// (2) extendedKeyUsage extension // (2) extendedKeyUsage extension
certBuilder.addExtension(Extension.extendedKeyUsage, false, new ExtendedKeyUsage(new KeyPurposeId[]{KeyPurposeId.id_kp_clientAuth, KeyPurposeId.id_kp_serverAuth})); certBuilder.addExtension(Extension.extendedKeyUsage, false, new ExtendedKeyUsage(new KeyPurposeId[]{KeyPurposeId.id_kp_clientAuth, KeyPurposeId.id_kp_serverAuth}));
// (3) subjectAlternativeName
if(extensions != null && extensions.getExtension(Extension.subjectAlternativeName) != null) {
certBuilder.addExtension(Extension.subjectAlternativeName, false, extensions.getExtensionParsedValue(Extension.subjectAlternativeName));
}
X509CertificateHolder certificateHolder = certBuilder.build(sigGen); X509CertificateHolder certificateHolder = certBuilder.build(sigGen);
return new JcaX509CertificateConverter().setProvider(BouncyCastleProvider.PROVIDER_NAME).getCertificate(certificateHolder); return new JcaX509CertificateConverter().setProvider(BouncyCastleProvider.PROVIDER_NAME).getCertificate(certificateHolder);
} catch (CertIOException | NoSuchAlgorithmException | OperatorCreationException e) { } catch (CertIOException | NoSuchAlgorithmException | OperatorCreationException e) {
@ -613,6 +644,25 @@ public final class CertificateUtils {
} }
} }
/**
* Extract extensions from CSR object
*/
public static Extensions getExtensionsFromCSR(JcaPKCS10CertificationRequest csr) {
Attribute[] attributess = csr.getAttributes(PKCSObjectIdentifiers.pkcs_9_at_extensionRequest);
for (Attribute attribute : attributess) {
ASN1Set attValue = attribute.getAttrValues();
if (attValue != null) {
ASN1Encodable extension = attValue.getObjectAt(0);
if (extension instanceof Extensions) {
return (Extensions) extension;
} else if (extension instanceof DERSequence) {
return Extensions.getInstance(extension);
}
}
}
return null;
}
private CertificateUtils() { private CertificateUtils() {
} }
} }

View File

@ -16,6 +16,11 @@
*/ */
package org.apache.nifi.security.util package org.apache.nifi.security.util
import org.bouncycastle.asn1.x509.Extension
import org.bouncycastle.asn1.x509.Extensions
import org.bouncycastle.asn1.x509.ExtensionsGenerator
import org.bouncycastle.asn1.x509.GeneralName
import org.bouncycastle.asn1.x509.GeneralNames
import org.bouncycastle.operator.OperatorCreationException import org.bouncycastle.operator.OperatorCreationException
import org.junit.After import org.junit.After
import org.junit.Before import org.junit.Before
@ -51,17 +56,18 @@ import static org.junit.Assert.assertTrue
@RunWith(JUnit4.class) @RunWith(JUnit4.class)
class CertificateUtilsTest extends GroovyTestCase { class CertificateUtilsTest extends GroovyTestCase {
private static final Logger logger = LoggerFactory.getLogger(CertificateUtilsTest.class); private static final Logger logger = LoggerFactory.getLogger(CertificateUtilsTest.class)
private static final int KEY_SIZE = 2048; private static final int KEY_SIZE = 2048
private static final long YESTERDAY = System.currentTimeMillis() - 24 * 60 * 60 * 1000; private static final int DAYS_IN_YEAR = 365
private static final long ONE_YEAR_FROM_NOW = System.currentTimeMillis() + 365 * 24 * 60 * 60 * 1000; private static final long YESTERDAY = System.currentTimeMillis() - 24 * 60 * 60 * 1000
private static final String SIGNATURE_ALGORITHM = "SHA256withRSA"; private static final long ONE_YEAR_FROM_NOW = System.currentTimeMillis() + 365 * 24 * 60 * 60 * 1000
private static final String PROVIDER = "BC"; private static final String SIGNATURE_ALGORITHM = "SHA256withRSA"
private static final String PROVIDER = "BC"
private static final String SUBJECT_DN = "CN=NiFi Test Server,OU=Security,O=Apache,ST=CA,C=US"; private static final String SUBJECT_DN = "CN=NiFi Test Server,OU=Security,O=Apache,ST=CA,C=US"
private static final String ISSUER_DN = "CN=NiFi Test CA,OU=Security,O=Apache,ST=CA,C=US"; private static final String ISSUER_DN = "CN=NiFi Test CA,OU=Security,O=Apache,ST=CA,C=US"
@BeforeClass @BeforeClass
static void setUpOnce() { static void setUpOnce() {
@ -88,9 +94,9 @@ class CertificateUtilsTest extends GroovyTestCase {
* @throws java.security.NoSuchAlgorithmException if the RSA algorithm is not available * @throws java.security.NoSuchAlgorithmException if the RSA algorithm is not available
*/ */
private static KeyPair generateKeyPair() throws NoSuchAlgorithmException { private static KeyPair generateKeyPair() throws NoSuchAlgorithmException {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA")
keyPairGenerator.initialize(KEY_SIZE); keyPairGenerator.initialize(KEY_SIZE)
return keyPairGenerator.generateKeyPair(); return keyPairGenerator.generateKeyPair()
} }
/** /**
@ -108,8 +114,8 @@ class CertificateUtilsTest extends GroovyTestCase {
*/ */
private private
static X509Certificate generateCertificate(String dn) throws IOException, NoSuchAlgorithmException, CertificateException, NoSuchProviderException, SignatureException, InvalidKeyException, OperatorCreationException { static X509Certificate generateCertificate(String dn) throws IOException, NoSuchAlgorithmException, CertificateException, NoSuchProviderException, SignatureException, InvalidKeyException, OperatorCreationException {
KeyPair keyPair = generateKeyPair(); KeyPair keyPair = generateKeyPair()
return CertificateUtils.generateSelfSignedX509Certificate(keyPair, dn, SIGNATURE_ALGORITHM, 365); return CertificateUtils.generateSelfSignedX509Certificate(keyPair, dn, SIGNATURE_ALGORITHM, DAYS_IN_YEAR)
} }
/** /**
@ -129,15 +135,15 @@ class CertificateUtilsTest extends GroovyTestCase {
*/ */
private private
static X509Certificate generateIssuedCertificate(String dn, X509Certificate issuer, KeyPair issuerKey) throws IOException, NoSuchAlgorithmException, CertificateException, NoSuchProviderException, SignatureException, InvalidKeyException, OperatorCreationException { static X509Certificate generateIssuedCertificate(String dn, X509Certificate issuer, KeyPair issuerKey) throws IOException, NoSuchAlgorithmException, CertificateException, NoSuchProviderException, SignatureException, InvalidKeyException, OperatorCreationException {
KeyPair keyPair = generateKeyPair(); KeyPair keyPair = generateKeyPair()
return CertificateUtils.generateIssuedCertificate(dn, keyPair.getPublic(), issuer, issuerKey, SIGNATURE_ALGORITHM, 365); return CertificateUtils.generateIssuedCertificate(dn, keyPair.getPublic(), issuer, issuerKey, SIGNATURE_ALGORITHM, DAYS_IN_YEAR)
} }
private static X509Certificate[] generateCertificateChain(String dn = SUBJECT_DN, String issuerDn = ISSUER_DN) { private static X509Certificate[] generateCertificateChain(String dn = SUBJECT_DN, String issuerDn = ISSUER_DN) {
final KeyPair issuerKeyPair = generateKeyPair(); final KeyPair issuerKeyPair = generateKeyPair()
final X509Certificate issuerCertificate = CertificateUtils.generateSelfSignedX509Certificate(issuerKeyPair, issuerDn, SIGNATURE_ALGORITHM, 365); final X509Certificate issuerCertificate = CertificateUtils.generateSelfSignedX509Certificate(issuerKeyPair, issuerDn, SIGNATURE_ALGORITHM, DAYS_IN_YEAR)
final X509Certificate certificate = generateIssuedCertificate(dn, issuerCertificate, issuerKeyPair); final X509Certificate certificate = generateIssuedCertificate(dn, issuerCertificate, issuerKeyPair)
[certificate, issuerCertificate] as X509Certificate[] [certificate, issuerCertificate] as X509Certificate[]
} }
@ -150,7 +156,7 @@ class CertificateUtilsTest extends GroovyTestCase {
} }
private static Date inFuture(int days) { private static Date inFuture(int days) {
return new Date(System.currentTimeMillis() + TimeUnit.DAYS.toMillis(days)); return new Date(System.currentTimeMillis() + TimeUnit.DAYS.toMillis(days))
} }
@Test @Test
@ -429,76 +435,72 @@ class CertificateUtilsTest extends GroovyTestCase {
assert !dn1MatchesEmpty assert !dn1MatchesEmpty
} }
@Test @Test
public void testShouldGenerateSelfSignedCert() throws Exception { public void testShouldGenerateSelfSignedCert() throws Exception {
String dn = "CN=testDN,O=testOrg"; String dn = "CN=testDN,O=testOrg"
int days = 365; int days = 365
X509Certificate x509Certificate = CertificateUtils.generateSelfSignedX509Certificate(generateKeyPair(), dn, SIGNATURE_ALGORITHM, days); X509Certificate x509Certificate = CertificateUtils.generateSelfSignedX509Certificate(generateKeyPair(), dn, SIGNATURE_ALGORITHM, days)
Date notAfter = x509Certificate.getNotAfter(); Date notAfter = x509Certificate.getNotAfter()
assertTrue(notAfter.after(inFuture(days - 1))); assertTrue(notAfter.after(inFuture(days - 1)))
assertTrue(notAfter.before(inFuture(days + 1))); assertTrue(notAfter.before(inFuture(days + 1)))
Date notBefore = x509Certificate.getNotBefore(); Date notBefore = x509Certificate.getNotBefore()
assertTrue(notBefore.after(inFuture(-1))); assertTrue(notBefore.after(inFuture(-1)))
assertTrue(notBefore.before(inFuture(1))); assertTrue(notBefore.before(inFuture(1)))
assertEquals(dn, x509Certificate.getIssuerX500Principal().getName()); assertEquals(dn, x509Certificate.getIssuerX500Principal().getName())
assertEquals(SIGNATURE_ALGORITHM.toUpperCase(), x509Certificate.getSigAlgName().toUpperCase()); assertEquals(SIGNATURE_ALGORITHM.toUpperCase(), x509Certificate.getSigAlgName().toUpperCase())
assertEquals("RSA", x509Certificate.getPublicKey().getAlgorithm()); assertEquals("RSA", x509Certificate.getPublicKey().getAlgorithm())
x509Certificate.checkValidity(); x509Certificate.checkValidity()
} }
@Test @Test
public void testIssueCert() throws Exception { public void testIssueCert() throws Exception {
int days = 365; int days = 365;
KeyPair issuerKeyPair = generateKeyPair(); KeyPair issuerKeyPair = generateKeyPair()
X509Certificate issuer = CertificateUtils.generateSelfSignedX509Certificate(issuerKeyPair, "CN=testCa,O=testOrg", SIGNATURE_ALGORITHM, days); X509Certificate issuer = CertificateUtils.generateSelfSignedX509Certificate(issuerKeyPair, "CN=testCa,O=testOrg", SIGNATURE_ALGORITHM, days)
String dn = "CN=testIssued, O=testOrg"; String dn = "CN=testIssued, O=testOrg"
KeyPair keyPair = generateKeyPair(); KeyPair keyPair = generateKeyPair()
X509Certificate x509Certificate = CertificateUtils.generateIssuedCertificate(dn, keyPair.getPublic(), issuer, issuerKeyPair, SIGNATURE_ALGORITHM, days); X509Certificate x509Certificate = CertificateUtils.generateIssuedCertificate(dn, keyPair.getPublic(), issuer, issuerKeyPair, SIGNATURE_ALGORITHM, days)
assertEquals(dn, x509Certificate.getSubjectX500Principal().toString()); assertEquals(dn, x509Certificate.getSubjectX500Principal().toString())
assertEquals(issuer.getSubjectX500Principal().toString(), x509Certificate.getIssuerX500Principal().toString()); assertEquals(issuer.getSubjectX500Principal().toString(), x509Certificate.getIssuerX500Principal().toString())
assertEquals(keyPair.getPublic(), x509Certificate.getPublicKey()); assertEquals(keyPair.getPublic(), x509Certificate.getPublicKey())
Date notAfter = x509Certificate.getNotAfter(); Date notAfter = x509Certificate.getNotAfter()
assertTrue(notAfter.after(inFuture(days - 1))); assertTrue(notAfter.after(inFuture(days - 1)))
assertTrue(notAfter.before(inFuture(days + 1))); assertTrue(notAfter.before(inFuture(days + 1)))
Date notBefore = x509Certificate.getNotBefore(); Date notBefore = x509Certificate.getNotBefore()
assertTrue(notBefore.after(inFuture(-1))); assertTrue(notBefore.after(inFuture(-1)))
assertTrue(notBefore.before(inFuture(1))); assertTrue(notBefore.before(inFuture(1)))
assertEquals(SIGNATURE_ALGORITHM.toUpperCase(), x509Certificate.getSigAlgName().toUpperCase()); assertEquals(SIGNATURE_ALGORITHM.toUpperCase(), x509Certificate.getSigAlgName().toUpperCase())
assertEquals("RSA", x509Certificate.getPublicKey().getAlgorithm()); assertEquals("RSA", x509Certificate.getPublicKey().getAlgorithm())
x509Certificate.verify(issuerKeyPair.getPublic()); x509Certificate.verify(issuerKeyPair.getPublic())
} }
@Test @Test
public void reorderShouldPutElementsInCorrectOrder() { public void reorderShouldPutElementsInCorrectOrder() {
String cn = "CN=testcn"; String cn = "CN=testcn"
String l = "L=testl"; String l = "L=testl"
String st = "ST=testst"; String st = "ST=testst"
String o = "O=testo"; String o = "O=testo"
String ou = "OU=testou"; String ou = "OU=testou"
String c = "C=testc"; String c = "C=testc"
String street = "STREET=teststreet"; String street = "STREET=teststreet"
String dc = "DC=testdc"; String dc = "DC=testdc"
String uid = "UID=testuid"; String uid = "UID=testuid"
String surname = "SURNAME=testsurname"; String surname = "SURNAME=testsurname"
String initials = "INITIALS=testinitials"; String initials = "INITIALS=testinitials"
String givenName = "GIVENNAME=testgivenname"; String givenName = "GIVENNAME=testgivenname"
assertEquals("$cn,$l,$st,$o,$ou,$c,$street,$dc,$uid,$surname,$givenName,$initials".toString(), assertEquals("$cn,$l,$st,$o,$ou,$c,$street,$dc,$uid,$surname,$givenName,$initials".toString(),
CertificateUtils.reorderDn("$surname,$st,$o,$initials,$givenName,$uid,$street,$c,$cn,$ou,$l,$dc")); CertificateUtils.reorderDn("$surname,$st,$o,$initials,$givenName,$uid,$street,$c,$cn,$ou,$l,$dc"))
} }
@Test @Test
@ -548,4 +550,40 @@ class CertificateUtilsTest extends GroovyTestCase {
executorService.shutdown() executorService.shutdown()
} }
} }
@Test
void testShouldGenerateIssuedCertificateWithSans() {
// Arrange
final String SUBJECT_DN = "CN=localhost"
final List<String> SANS = ["127.0.0.1", "nifi.nifi.apache.org"]
logger.info("Creating a certificate with subject: ${SUBJECT_DN} and SAN: ${SANS}")
final KeyPair subjectKeyPair = generateKeyPair()
final KeyPair issuerKeyPair = generateKeyPair()
final X509Certificate issuerCertificate = CertificateUtils.generateSelfSignedX509Certificate(issuerKeyPair, ISSUER_DN, SIGNATURE_ALGORITHM, DAYS_IN_YEAR)
// Form the SANS into GeneralName instances and populate the container with the array
def gns = SANS.collect { String san ->
new GeneralName(GeneralName.dNSName, san)
}
def generalNames = new GeneralNames(gns as GeneralName[])
logger.info("Created GeneralNames object: ${generalNames.names*.toString()}")
// Form the Extensions object
ExtensionsGenerator extensionsGenerator = new ExtensionsGenerator()
extensionsGenerator.addExtension(Extension.subjectAlternativeName, false, generalNames)
Extensions extensions = extensionsGenerator.generate()
logger.info("Generated extensions object: ${extensions.oids()*.toString()}")
// Act
X509Certificate certificate = CertificateUtils.generateIssuedCertificate(SUBJECT_DN, subjectKeyPair.public, extensions, issuerCertificate, issuerKeyPair, SIGNATURE_ALGORITHM, DAYS_IN_YEAR)
logger.info("Issued certificate with subject: ${certificate.getSubjectDN().name} and SAN: ${certificate.getSubjectAlternativeNames().join(",")}")
// Assert
assert certificate instanceof X509Certificate
assert certificate.getSubjectDN().name == SUBJECT_DN
assert certificate.getSubjectAlternativeNames().size() == SANS.size()
assert certificate.getSubjectAlternativeNames()*.last().containsAll(SANS)
}
} }

View File

@ -41,6 +41,7 @@ public class TlsConfig {
private String signingAlgorithm = DEFAULT_SIGNING_ALGORITHM; private String signingAlgorithm = DEFAULT_SIGNING_ALGORITHM;
private String dn; private String dn;
private String domainAlternativeNames;
private String keyStore; private String keyStore;
private String keyStoreType = DEFAULT_KEY_STORE_TYPE; private String keyStoreType = DEFAULT_KEY_STORE_TYPE;
private String keyStorePassword; private String keyStorePassword;
@ -206,4 +207,12 @@ public class TlsConfig {
dn = calcDefaultDn(caHostname); dn = calcDefaultDn(caHostname);
} }
} }
public String getDomainAlternativeNames() {
return domainAlternativeNames;
}
public void setDomainAlternativeNames(String domainAlternativeNames) {
this.domainAlternativeNames = domainAlternativeNames;
}
} }

View File

@ -18,6 +18,12 @@
package org.apache.nifi.toolkit.tls.service.client; package org.apache.nifi.toolkit.tls.service.client;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetAddress;
import java.net.UnknownHostException;
import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.CommandLine;
import org.apache.nifi.toolkit.tls.commandLine.CommandLineParseException; import org.apache.nifi.toolkit.tls.commandLine.CommandLineParseException;
import org.apache.nifi.toolkit.tls.commandLine.ExitCode; import org.apache.nifi.toolkit.tls.commandLine.ExitCode;
@ -28,25 +34,20 @@ import org.apache.nifi.util.StringUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetAddress;
import java.net.UnknownHostException;
/** /**
* Command line parser for a TlsClientConfig object and a main entry point to invoke the parser and run the CA client * Command line parser for a TlsClientConfig object and a main entry point to invoke the parser and run the CA client
*/ */
public class TlsCertificateAuthorityClientCommandLine extends BaseCertificateAuthorityCommandLine { public class TlsCertificateAuthorityClientCommandLine extends BaseCertificateAuthorityCommandLine {
public static final String DESCRIPTION = "Generates a private key and gets it signed by the certificate authority."; public static final String DESCRIPTION = "Generates a private key and gets it signed by the certificate authority.";
public static final String CERTIFICATE_DIRECTORY = "certificateDirectory"; public static final String CERTIFICATE_DIRECTORY = "certificateDirectory";
public static final String SUBJECT_ALTERNATIVE_NAMES = "subjectAlternativeNames";
public static final String DEFAULT_CERTIFICATE_DIRECTORY = "."; public static final String DEFAULT_CERTIFICATE_DIRECTORY = ".";
private final Logger logger = LoggerFactory.getLogger(TlsCertificateAuthorityClientCommandLine.class); private final Logger logger = LoggerFactory.getLogger(TlsCertificateAuthorityClientCommandLine.class);
private final InputStreamFactory inputStreamFactory; private final InputStreamFactory inputStreamFactory;
private String certificateDirectory; private String certificateDirectory;
private String domainAlternativeNames;
public TlsCertificateAuthorityClientCommandLine() { public TlsCertificateAuthorityClientCommandLine() {
this(FileInputStream::new); this(FileInputStream::new);
@ -56,6 +57,7 @@ public class TlsCertificateAuthorityClientCommandLine extends BaseCertificateAut
super(DESCRIPTION); super(DESCRIPTION);
this.inputStreamFactory = inputStreamFactory; this.inputStreamFactory = inputStreamFactory;
addOptionWithArg("C", CERTIFICATE_DIRECTORY, "The file to write the CA certificate to", DEFAULT_CERTIFICATE_DIRECTORY); addOptionWithArg("C", CERTIFICATE_DIRECTORY, "The file to write the CA certificate to", DEFAULT_CERTIFICATE_DIRECTORY);
addOptionWithArg("S", SUBJECT_ALTERNATIVE_NAMES, "Comma-separated list of domains to use as Subject Alternative Names in the certificate");
} }
public static void main(String[] args) throws Exception { public static void main(String[] args) throws Exception {
@ -110,6 +112,7 @@ public class TlsCertificateAuthorityClientCommandLine extends BaseCertificateAut
protected CommandLine doParse(String[] args) throws CommandLineParseException { protected CommandLine doParse(String[] args) throws CommandLineParseException {
CommandLine commandLine = super.doParse(args); CommandLine commandLine = super.doParse(args);
certificateDirectory = commandLine.getOptionValue(CERTIFICATE_DIRECTORY, DEFAULT_CERTIFICATE_DIRECTORY); certificateDirectory = commandLine.getOptionValue(CERTIFICATE_DIRECTORY, DEFAULT_CERTIFICATE_DIRECTORY);
domainAlternativeNames = commandLine.getOptionValue(SUBJECT_ALTERNATIVE_NAMES);
return commandLine; return commandLine;
} }
@ -117,6 +120,10 @@ public class TlsCertificateAuthorityClientCommandLine extends BaseCertificateAut
return certificateDirectory; return certificateDirectory;
} }
public String getDomainAlternativeNames() {
return domainAlternativeNames;
}
public TlsClientConfig createClientConfig() throws IOException { public TlsClientConfig createClientConfig() throws IOException {
String configJsonIn = getConfigJsonIn(); String configJsonIn = getConfigJsonIn();
if (!StringUtils.isEmpty(configJsonIn)) { if (!StringUtils.isEmpty(configJsonIn)) {
@ -129,6 +136,7 @@ public class TlsCertificateAuthorityClientCommandLine extends BaseCertificateAut
TlsClientConfig tlsClientConfig = new TlsClientConfig(); TlsClientConfig tlsClientConfig = new TlsClientConfig();
tlsClientConfig.setCaHostname(getCertificateAuthorityHostname()); tlsClientConfig.setCaHostname(getCertificateAuthorityHostname());
tlsClientConfig.setDn(getDn()); tlsClientConfig.setDn(getDn());
tlsClientConfig.setDomainAlternativeNames(getDomainAlternativeNames());
tlsClientConfig.setToken(getToken()); tlsClientConfig.setToken(getToken());
tlsClientConfig.setPort(getPort()); tlsClientConfig.setPort(getPort());
tlsClientConfig.setKeyStore(KEYSTORE + getKeyStoreType().toLowerCase()); tlsClientConfig.setKeyStore(KEYSTORE + getKeyStoreType().toLowerCase());
@ -140,4 +148,5 @@ public class TlsCertificateAuthorityClientCommandLine extends BaseCertificateAut
return tlsClientConfig; return tlsClientConfig;
} }
} }
} }

View File

@ -18,6 +18,16 @@
package org.apache.nifi.toolkit.tls.service.client; package org.apache.nifi.toolkit.tls.service.client;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
import java.io.StringReader;
import java.nio.charset.StandardCharsets;
import java.security.KeyPair;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Supplier;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
import org.apache.commons.io.input.BoundedInputStream; import org.apache.commons.io.input.BoundedInputStream;
import org.apache.http.HttpHost; import org.apache.http.HttpHost;
@ -38,17 +48,6 @@ import org.eclipse.jetty.server.Response;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.io.StringReader;
import java.nio.charset.StandardCharsets;
import java.security.KeyPair;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Supplier;
public class TlsCertificateSigningRequestPerformer { public class TlsCertificateSigningRequestPerformer {
public static final String RECEIVED_RESPONSE_CODE = "Received response code "; public static final String RECEIVED_RESPONSE_CODE = "Received response code ";
public static final String EXPECTED_ONE_CERTIFICATE = "Expected one certificate"; public static final String EXPECTED_ONE_CERTIFICATE = "Expected one certificate";
@ -59,23 +58,28 @@ public class TlsCertificateSigningRequestPerformer {
private final Supplier<HttpClientBuilder> httpClientBuilderSupplier; private final Supplier<HttpClientBuilder> httpClientBuilderSupplier;
private final String caHostname; private final String caHostname;
private final String dn; private final String dn;
private final String domainAlternativeNames;
private final String token; private final String token;
private final int port; private final int port;
private final ObjectMapper objectMapper; private final ObjectMapper objectMapper;
private final String signingAlgorithm; private final String signingAlgorithm;
public TlsCertificateSigningRequestPerformer(TlsClientConfig tlsClientConfig) throws NoSuchAlgorithmException { public TlsCertificateSigningRequestPerformer(TlsClientConfig tlsClientConfig) throws NoSuchAlgorithmException {
this(HttpClientBuilder::create, tlsClientConfig.getCaHostname(), tlsClientConfig.getDn(), tlsClientConfig.getToken(), tlsClientConfig.getPort(), tlsClientConfig.getSigningAlgorithm()); this(HttpClientBuilder::create, tlsClientConfig.getCaHostname(), tlsClientConfig.getDn(), tlsClientConfig.getDomainAlternativeNames(),
tlsClientConfig.getToken(), tlsClientConfig.getPort(), tlsClientConfig.getSigningAlgorithm());
} }
protected TlsCertificateSigningRequestPerformer(Supplier<HttpClientBuilder> httpClientBuilderSupplier, TlsClientConfig tlsClientConfig) throws NoSuchAlgorithmException { protected TlsCertificateSigningRequestPerformer(Supplier<HttpClientBuilder> httpClientBuilderSupplier, TlsClientConfig tlsClientConfig) throws NoSuchAlgorithmException {
this(httpClientBuilderSupplier, tlsClientConfig.getCaHostname(), tlsClientConfig.getDn(), tlsClientConfig.getToken(), tlsClientConfig.getPort(), tlsClientConfig.getSigningAlgorithm()); this(httpClientBuilderSupplier, tlsClientConfig.getCaHostname(), tlsClientConfig.getDn(), tlsClientConfig.getDomainAlternativeNames(),
tlsClientConfig.getToken(), tlsClientConfig.getPort(), tlsClientConfig.getSigningAlgorithm());
} }
private TlsCertificateSigningRequestPerformer(Supplier<HttpClientBuilder> httpClientBuilderSupplier, String caHostname, String dn, String token, int port, String signingAlgorithm) { private TlsCertificateSigningRequestPerformer(Supplier<HttpClientBuilder> httpClientBuilderSupplier, String caHostname,
String dn, String domainAlternativeNames, String token, int port, String signingAlgorithm) {
this.httpClientBuilderSupplier = httpClientBuilderSupplier; this.httpClientBuilderSupplier = httpClientBuilderSupplier;
this.caHostname = caHostname; this.caHostname = caHostname;
this.dn = CertificateUtils.reorderDn(dn); this.dn = CertificateUtils.reorderDn(dn);
this.domainAlternativeNames = domainAlternativeNames;
this.token = token; this.token = token;
this.port = port; this.port = port;
this.objectMapper = new ObjectMapper(); this.objectMapper = new ObjectMapper();
@ -87,7 +91,7 @@ public class TlsCertificateSigningRequestPerformer {
* *
* @param keyPair the keypair to generate the csr for * @param keyPair the keypair to generate the csr for
* @throws IOException if there is a problem during the process * @throws IOException if there is a problem during the process
* @returnd the resulting certificate chain * @return the resulting certificate chain
*/ */
public X509Certificate[] perform(KeyPair keyPair) throws IOException { public X509Certificate[] perform(KeyPair keyPair) throws IOException {
try { try {
@ -104,7 +108,7 @@ public class TlsCertificateSigningRequestPerformer {
String jsonResponseString; String jsonResponseString;
int responseCode; int responseCode;
try (CloseableHttpClient client = httpClientBuilder.build()) { try (CloseableHttpClient client = httpClientBuilder.build()) {
JcaPKCS10CertificationRequest request = TlsHelper.generateCertificationRequest(dn, keyPair, signingAlgorithm); JcaPKCS10CertificationRequest request = TlsHelper.generateCertificationRequest(dn, domainAlternativeNames, keyPair, signingAlgorithm);
TlsCertificateAuthorityRequest tlsCertificateAuthorityRequest = new TlsCertificateAuthorityRequest(TlsHelper.calculateHMac(token, request.getPublicKey()), TlsCertificateAuthorityRequest tlsCertificateAuthorityRequest = new TlsCertificateAuthorityRequest(TlsHelper.calculateHMac(token, request.getPublicKey()),
TlsHelper.pemEncodeJcaObject(request)); TlsHelper.pemEncodeJcaObject(request));

View File

@ -86,8 +86,8 @@ public class TlsCertificateAuthorityServiceHandler extends AbstractHandler {
if (logger.isInfoEnabled()) { if (logger.isInfoEnabled()) {
logger.info("Received CSR with DN " + dn); logger.info("Received CSR with DN " + dn);
} }
X509Certificate x509Certificate = CertificateUtils.generateIssuedCertificate(dn, X509Certificate x509Certificate = CertificateUtils.generateIssuedCertificate(dn, jcaPKCS10CertificationRequest.getPublicKey(),
jcaPKCS10CertificationRequest.getPublicKey(), caCert, keyPair, signingAlgorithm, days); CertificateUtils.getExtensionsFromCSR(jcaPKCS10CertificationRequest), caCert, keyPair, signingAlgorithm, days);
writeResponse(objectMapper, request, response, new TlsCertificateAuthorityResponse(TlsHelper.calculateHMac(token, caCert.getPublicKey()), writeResponse(objectMapper, request, response, new TlsCertificateAuthorityResponse(TlsHelper.calculateHMac(token, caCert.getPublicKey()),
TlsHelper.pemEncodeJcaObject(x509Certificate)), Response.SC_OK); TlsHelper.pemEncodeJcaObject(x509Certificate)), Response.SC_OK);
return; return;

View File

@ -180,7 +180,7 @@ public class TlsToolkitStandalone {
TlsClientManager tlsClientManager = new TlsClientManager(tlsClientConfig); TlsClientManager tlsClientManager = new TlsClientManager(tlsClientConfig);
KeyPair keyPair = TlsHelper.generateKeyPair(keyPairAlgorithm, keySize); KeyPair keyPair = TlsHelper.generateKeyPair(keyPairAlgorithm, keySize);
tlsClientManager.addPrivateKeyToKeyStore(keyPair, NIFI_KEY, CertificateUtils.generateIssuedCertificate(tlsClientConfig.calcDefaultDn(hostname), tlsClientManager.addPrivateKeyToKeyStore(keyPair, NIFI_KEY, CertificateUtils.generateIssuedCertificate(tlsClientConfig.calcDefaultDn(hostname),
keyPair.getPublic(), certificate, caKeyPair, signingAlgorithm, days), certificate); keyPair.getPublic(), null, certificate, caKeyPair, signingAlgorithm, days), certificate);
tlsClientManager.setCertificateEntry(NIFI_CERT, certificate); tlsClientManager.setCertificateEntry(NIFI_CERT, certificate);
tlsClientManager.addClientConfigurationWriter(new NifiPropertiesTlsClientConfigWriter(niFiPropertiesWriterFactory, new File(hostDir, "nifi.properties"), tlsClientManager.addClientConfigurationWriter(new NifiPropertiesTlsClientConfigWriter(niFiPropertiesWriterFactory, new File(hostDir, "nifi.properties"),
hostname, instanceDefinition.getNumber())); hostname, instanceDefinition.getNumber()));
@ -213,7 +213,7 @@ public class TlsToolkitStandalone {
logger.info("Generating new client certificate " + clientCertFile); logger.info("Generating new client certificate " + clientCertFile);
} }
KeyPair keyPair = TlsHelper.generateKeyPair(keyPairAlgorithm, keySize); KeyPair keyPair = TlsHelper.generateKeyPair(keyPairAlgorithm, keySize);
X509Certificate clientCert = CertificateUtils.generateIssuedCertificate(reorderedDn, keyPair.getPublic(), certificate, caKeyPair, signingAlgorithm, days); X509Certificate clientCert = CertificateUtils.generateIssuedCertificate(reorderedDn, keyPair.getPublic(), null, certificate, caKeyPair, signingAlgorithm, days);
KeyStore keyStore = KeyStoreUtils.getKeyStore(KeystoreType.PKCS12.toString()); KeyStore keyStore = KeyStoreUtils.getKeyStore(KeystoreType.PKCS12.toString());
keyStore.load(null, null); keyStore.load(null, null);
keyStore.setKeyEntry(NIFI_KEY, keyPair.getPrivate(), null, new Certificate[]{clientCert, certificate}); keyStore.setKeyEntry(NIFI_KEY, keyPair.getPrivate(), null, new Certificate[]{clientCert, certificate});

View File

@ -17,7 +17,33 @@
package org.apache.nifi.toolkit.tls.util; package org.apache.nifi.toolkit.tls.util;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.KeyStore;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;
import javax.crypto.Cipher;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.lang3.StringUtils;
import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
import org.bouncycastle.asn1.x500.X500Name; import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x509.Extension;
import org.bouncycastle.asn1.x509.ExtensionsGenerator;
import org.bouncycastle.asn1.x509.GeneralName;
import org.bouncycastle.asn1.x509.GeneralNames;
import org.bouncycastle.cert.X509CertificateHolder; import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.cert.jcajce.JcaX509ExtensionUtils; import org.bouncycastle.cert.jcajce.JcaX509ExtensionUtils;
@ -35,25 +61,6 @@ import org.bouncycastle.util.io.pem.PemWriter;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import javax.crypto.Cipher;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.KeyStore;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
public class TlsHelper { public class TlsHelper {
private static final Logger logger = LoggerFactory.getLogger(TlsHelper.class); private static final Logger logger = LoggerFactory.getLogger(TlsHelper.class);
private static final int DEFAULT_MAX_ALLOWED_KEY_LENGTH = 128; private static final int DEFAULT_MAX_ALLOWED_KEY_LENGTH = 128;
@ -184,8 +191,27 @@ public class TlsHelper {
return createKeyPairGenerator(algorithm, keySize).generateKeyPair(); return createKeyPairGenerator(algorithm, keySize).generateKeyPair();
} }
public static JcaPKCS10CertificationRequest generateCertificationRequest(String requestedDn, KeyPair keyPair, String signingAlgorithm) throws OperatorCreationException { public static JcaPKCS10CertificationRequest generateCertificationRequest(String requestedDn, String domainAlternativeNames,
KeyPair keyPair, String signingAlgorithm) throws OperatorCreationException {
JcaPKCS10CertificationRequestBuilder jcaPKCS10CertificationRequestBuilder = new JcaPKCS10CertificationRequestBuilder(new X500Name(requestedDn), keyPair.getPublic()); JcaPKCS10CertificationRequestBuilder jcaPKCS10CertificationRequestBuilder = new JcaPKCS10CertificationRequestBuilder(new X500Name(requestedDn), keyPair.getPublic());
// add Subject Alternative Name(s)
if(StringUtils.isNotBlank(domainAlternativeNames)) {
try {
List<GeneralName> namesList = new ArrayList<>();
for(String alternativeName : domainAlternativeNames.split(",")) {
namesList.add(new GeneralName(GeneralName.dNSName, alternativeName));
}
GeneralNames subjectAltNames = new GeneralNames(namesList.toArray(new GeneralName [] {}));
ExtensionsGenerator extGen = new ExtensionsGenerator();
extGen.addExtension(Extension.subjectAlternativeName, false, subjectAltNames);
jcaPKCS10CertificationRequestBuilder.addAttribute(PKCSObjectIdentifiers.pkcs_9_at_extensionRequest, extGen.generate());
} catch (IOException e) {
throw new OperatorCreationException("Error while adding " + domainAlternativeNames + " as Subject Alternative Name.", e);
}
}
JcaContentSignerBuilder jcaContentSignerBuilder = new JcaContentSignerBuilder(signingAlgorithm); JcaContentSignerBuilder jcaContentSignerBuilder = new JcaContentSignerBuilder(signingAlgorithm);
return new JcaPKCS10CertificationRequest(jcaPKCS10CertificationRequestBuilder.build(jcaContentSignerBuilder.build(keyPair.getPrivate()))); return new JcaPKCS10CertificationRequest(jcaPKCS10CertificationRequestBuilder.build(jcaContentSignerBuilder.build(keyPair.getPrivate())));
} }

View File

@ -106,7 +106,7 @@ public class TlsCertificateSigningRequestPerformerTest {
when(tlsClientConfig.getPort()).thenReturn(testPort); when(tlsClientConfig.getPort()).thenReturn(testPort);
when(tlsClientConfig.createCertificateSigningRequestPerformer()).thenReturn(tlsCertificateSigningRequestPerformer); when(tlsClientConfig.createCertificateSigningRequestPerformer()).thenReturn(tlsCertificateSigningRequestPerformer);
when(tlsClientConfig.getSigningAlgorithm()).thenReturn(TlsConfig.DEFAULT_SIGNING_ALGORITHM); when(tlsClientConfig.getSigningAlgorithm()).thenReturn(TlsConfig.DEFAULT_SIGNING_ALGORITHM);
JcaPKCS10CertificationRequest jcaPKCS10CertificationRequest = TlsHelper.generateCertificationRequest(tlsClientConfig.getDn(), keyPair, TlsConfig.DEFAULT_SIGNING_ALGORITHM); JcaPKCS10CertificationRequest jcaPKCS10CertificationRequest = TlsHelper.generateCertificationRequest(tlsClientConfig.getDn(), null, keyPair, TlsConfig.DEFAULT_SIGNING_ALGORITHM);
String testCsrPem = TlsHelper.pemEncodeJcaObject(jcaPKCS10CertificationRequest); String testCsrPem = TlsHelper.pemEncodeJcaObject(jcaPKCS10CertificationRequest);
when(httpClientBuilderSupplier.get()).thenReturn(httpClientBuilder); when(httpClientBuilderSupplier.get()).thenReturn(httpClientBuilder);
when(httpClientBuilder.build()).thenAnswer(invocation -> { when(httpClientBuilder.build()).thenAnswer(invocation -> {

View File

@ -122,7 +122,7 @@ public class TlsCertificateAuthorityServiceHandlerTest {
caCert = CertificateUtils.generateSelfSignedX509Certificate(keyPair, "CN=fakeCa", TlsConfig.DEFAULT_SIGNING_ALGORITHM, TlsConfig.DEFAULT_DAYS); caCert = CertificateUtils.generateSelfSignedX509Certificate(keyPair, "CN=fakeCa", TlsConfig.DEFAULT_SIGNING_ALGORITHM, TlsConfig.DEFAULT_DAYS);
requestedDn = new TlsConfig().calcDefaultDn(TlsConfig.DEFAULT_HOSTNAME); requestedDn = new TlsConfig().calcDefaultDn(TlsConfig.DEFAULT_HOSTNAME);
certificateKeyPair = TlsHelper.generateKeyPair(TlsConfig.DEFAULT_KEY_PAIR_ALGORITHM, TlsConfig.DEFAULT_KEY_SIZE); certificateKeyPair = TlsHelper.generateKeyPair(TlsConfig.DEFAULT_KEY_PAIR_ALGORITHM, TlsConfig.DEFAULT_KEY_SIZE);
jcaPKCS10CertificationRequest = TlsHelper.generateCertificationRequest(requestedDn, certificateKeyPair, TlsConfig.DEFAULT_SIGNING_ALGORITHM); jcaPKCS10CertificationRequest = TlsHelper.generateCertificationRequest(requestedDn, null, certificateKeyPair, TlsConfig.DEFAULT_SIGNING_ALGORITHM);
testPemEncodedCsr = TlsHelper.pemEncodeJcaObject(jcaPKCS10CertificationRequest); testPemEncodedCsr = TlsHelper.pemEncodeJcaObject(jcaPKCS10CertificationRequest);
tlsCertificateAuthorityServiceHandler = new TlsCertificateAuthorityServiceHandler(TlsConfig.DEFAULT_SIGNING_ALGORITHM, TlsConfig.DEFAULT_DAYS, testToken, caCert, keyPair, objectMapper); tlsCertificateAuthorityServiceHandler = new TlsCertificateAuthorityServiceHandler(TlsConfig.DEFAULT_SIGNING_ALGORITHM, TlsConfig.DEFAULT_DAYS, testToken, caCert, keyPair, objectMapper);
testHmac = TlsHelper.calculateHMac(testToken, jcaPKCS10CertificationRequest.getPublicKey()); testHmac = TlsHelper.calculateHMac(testToken, jcaPKCS10CertificationRequest.getPublicKey());

View File

@ -17,21 +17,14 @@
package org.apache.nifi.toolkit.tls.util; package org.apache.nifi.toolkit.tls.util;
import org.apache.nifi.security.util.CertificateUtils; import static org.junit.Assert.assertEquals;
import org.bouncycastle.cert.X509CertificateHolder; import static org.junit.Assert.assertTrue;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; import static org.junit.Assert.fail;
import org.bouncycastle.jce.provider.BouncyCastleProvider; import static org.mockito.Matchers.eq;
import org.bouncycastle.openssl.PEMKeyPair; import static org.mockito.Mockito.doThrow;
import org.bouncycastle.openssl.PEMParser; import static org.mockito.Mockito.times;
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter; import static org.mockito.Mockito.verify;
import org.bouncycastle.operator.OperatorCreationException; import static org.mockito.Mockito.when;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.AdditionalMatchers;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.File; import java.io.File;
@ -53,20 +46,43 @@ import java.security.Provider;
import java.security.SignatureException; import java.security.SignatureException;
import java.security.cert.CertificateException; import java.security.cert.CertificateException;
import java.security.cert.X509Certificate; import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date; import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import static org.junit.Assert.assertEquals; import org.apache.commons.lang3.StringUtils;
import static org.junit.Assert.assertTrue; import org.apache.nifi.security.util.CertificateUtils;
import static org.junit.Assert.fail; import org.apache.nifi.toolkit.tls.configuration.TlsConfig;
import static org.mockito.Matchers.eq; import org.bouncycastle.asn1.pkcs.Attribute;
import static org.mockito.Mockito.doThrow; import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
import static org.mockito.Mockito.times; import org.bouncycastle.asn1.x509.Extension;
import static org.mockito.Mockito.verify; import org.bouncycastle.asn1.x509.Extensions;
import static org.mockito.Mockito.when; import org.bouncycastle.asn1.x509.GeneralName;
import org.bouncycastle.asn1.x509.GeneralNames;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.openssl.PEMKeyPair;
import org.bouncycastle.openssl.PEMParser;
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequest;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.AdditionalMatchers;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@RunWith(MockitoJUnitRunner.class) @RunWith(MockitoJUnitRunner.class)
public class TlsHelperTest { public class TlsHelperTest {
public static final Logger logger = LoggerFactory.getLogger(TlsHelperTest.class);
private static final boolean originalUnlimitedCrypto = TlsHelper.isUnlimitedStrengthCryptographyEnabled(); private static final boolean originalUnlimitedCrypto = TlsHelper.isUnlimitedStrengthCryptographyEnabled();
private int days; private int days;
@ -280,4 +296,58 @@ public class TlsHelperTest {
assertEquals(ioException2, e); assertEquals(ioException2, e);
} }
} }
@Test
public void testShouldIncludeSANFromCSR() throws Exception {
// Arrange
final List<String> SAN_ENTRIES = Arrays.asList("127.0.0.1", "nifi.nifi.apache.org");
final String SAN = StringUtils.join(SAN_ENTRIES, ",");
final int SAN_COUNT = SAN_ENTRIES.size();
final String DN = "CN=localhost";
KeyPair keyPair = keyPairGenerator.generateKeyPair();
logger.info("Generating CSR with DN: " + DN);
// Act
JcaPKCS10CertificationRequest csrWithSan = TlsHelper.generateCertificationRequest(DN, SAN, keyPair, TlsConfig.DEFAULT_SIGNING_ALGORITHM);
logger.info("Created CSR with SAN: " + SAN);
String testCsrPem = TlsHelper.pemEncodeJcaObject(csrWithSan);
logger.info("Encoded CSR as PEM: " + testCsrPem);
// Assert
String subjectName = csrWithSan.getSubject().toString();
logger.info("CSR Subject Name: " + subjectName);
assert subjectName.equals(DN);
List<String> extractedSans = extractSanFromCsr(csrWithSan);
assert extractedSans.size() == SAN_COUNT;
List<String> formattedSans = SAN_ENTRIES.stream().map(s -> "DNS: " + s).collect(Collectors.toList());
assert extractedSans.containsAll(formattedSans);
}
private List<String> extractSanFromCsr(JcaPKCS10CertificationRequest csr) {
List<String> sans = new ArrayList<>();
Attribute[] certAttributes = csr.getAttributes();
for (Attribute attribute : certAttributes) {
if (attribute.getAttrType().equals(PKCSObjectIdentifiers.pkcs_9_at_extensionRequest)) {
Extensions extensions = Extensions.getInstance(attribute.getAttrValues().getObjectAt(0));
GeneralNames gns = GeneralNames.fromExtensions(extensions, Extension.subjectAlternativeName);
GeneralName[] names = gns.getNames();
for (GeneralName name : names) {
logger.info("Type: " + name.getTagNo() + " | Name: " + name.getName());
String title = "";
if (name.getTagNo() == GeneralName.dNSName) {
title = "DNS";
} else if (name.getTagNo() == GeneralName.iPAddress) {
title = "IP Address";
// name.toASN1Primitive();
} else if (name.getTagNo() == GeneralName.otherName) {
title = "Other Name";
}
sans.add(title + ": " + name.getName());
}
}
}
return sans;
}
} }