mirror of https://github.com/apache/nifi.git
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:
parent
71e2061b5d
commit
6fc30900b9
|
@ -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() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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));
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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});
|
||||||
|
|
|
@ -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())));
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 -> {
|
||||||
|
|
|
@ -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());
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue