Skip to content

Commit

Permalink
Merge pull request #1035 from fjuma/ELY-1446
Browse files Browse the repository at this point in the history
[ELY-1446] Add utility methods for converting an unordered array of certificates into an ordered X.509 certificate chain and for creating an X.509 certificate chain from a map of certificates
  • Loading branch information
Jan Kalina committed Nov 20, 2017
2 parents 210bcfa + 37b7bbb commit 3952ee5
Show file tree
Hide file tree
Showing 3 changed files with 291 additions and 0 deletions.
12 changes: 12 additions & 0 deletions src/main/java/org/wildfly/security/_private/ElytronMessages.java
Original file line number Diff line number Diff line change
Expand Up @@ -1937,6 +1937,18 @@ public interface ElytronMessages extends BasicLogger {
@Message(id = 10024, value = "Invalid X.509 certificate extension string value")
IllegalArgumentException invalidCertificateExtensionStringValue();

@Message(id = 10025, value = "Non-X.509 certificate found in certificate array")
IllegalArgumentException nonX509CertificateInCertificateArray();

@Message(id = 10026, value = "Starting public key not found in certificate array")
IllegalArgumentException startingPublicKeyNotFoundInCertificateArray();

@Message(id = 10027, value = "Incomplete certificate array")
IllegalArgumentException incompleteCertificateArray();

@Message(id = 10028, value = "Unable to create X.509 certificate chain from map of certificates")
IllegalArgumentException unableToCreateCertificateChainFromCertificateMap();

/* Audit Exceptions */

// 11000 - Unused in any Final release
Expand Down
120 changes: 120 additions & 0 deletions src/main/java/org/wildfly/security/x500/X500.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,19 @@

package org.wildfly.security.x500;

import static org.wildfly.security._private.ElytronMessages.log;

import java.security.Principal;
import java.security.PublicKey;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;

import org.wildfly.common.Assert;

/**
* Useful X500 constants and utilities.
Expand Down Expand Up @@ -137,4 +148,113 @@ public static X509Certificate[] asX509CertificateArray(Object[] certificates) th
return Arrays.copyOf(certificates, certificates.length, X509Certificate[].class);
}
}

/**
* Convert an unordered array of certificates into an ordered X.509 certificate chain.
*
* @param firstPublicKey the public key that should be in the first certificate in the ordered X.509 certificate
* chain (may not be {@code null})
* @param certificates the unordered array of certificates (may not be {@code null})
* @return the ordered X.509 certificate chain, as an array
* @throws IllegalArgumentException if the given unordered array of certificates cannot be converted into an ordered X.509 certificate chain
*/
public static X509Certificate[] asOrderedX509CertificateChain(PublicKey firstPublicKey, Certificate[] certificates) throws IllegalArgumentException {
Assert.checkNotNullParam("firstPublicKey", firstPublicKey);
Assert.checkNotNullParam("certificates", certificates);
X509Certificate[] x509Certificates;
try {
x509Certificates = asX509CertificateArray(certificates);
} catch (ArrayStoreException e) {
throw log.nonX509CertificateInCertificateArray();
}
boolean foundFirstCertificate = false;
for (int i = 0; i < x509Certificates.length; i++) {
if (x509Certificates[i].getPublicKey().equals(firstPublicKey)) {
foundFirstCertificate = true;
swapCertificates(x509Certificates, 0, i);
break;
}
}
if (! foundFirstCertificate) {
throw log.startingPublicKeyNotFoundInCertificateArray();
}
X509Certificate currentCertificate = x509Certificates[0];
for (int i = 1; i < x509Certificates.length - 1; i++) {
boolean issuerCertificateFound = false;
for (int j = i; j < x509Certificates.length; j++) {
if (issuedBy(currentCertificate, x509Certificates[j])) {
swapCertificates(x509Certificates, i, j);
issuerCertificateFound = true;
currentCertificate = x509Certificates[i];
break;
}
}
if (! issuerCertificateFound) {
throw log.incompleteCertificateArray();
}
}
return x509Certificates;
}

/**
* Create an X.509 certificate chain given the first certificate that should be in the chain and a map of certificates.
*
* @param firstCertificate the certificate that should be first in the newly created X.509 certificate chain
* @param certificatesMap a map of distinguished names to certificates to use to create the X.509 certificate chain
* @return the newly created X.509 certificate chain, as an array
* @throws IllegalArgumentException if the X.509 certificate chain could not be created
*/
public static X509Certificate[] createX509CertificateChain(final X509Certificate firstCertificate,
final HashMap<Principal, HashSet<X509Certificate>> certificatesMap) throws IllegalArgumentException {
Assert.checkNotNullParam("firstCertificate", firstCertificate);
Assert.checkNotNullParam("certificatesMap", certificatesMap);
final ArrayList<X509Certificate> certificateChain = new ArrayList<>();
if (createX509CertificateChain(firstCertificate, certificateChain, certificatesMap)) {
Collections.reverse(certificateChain);
return certificateChain.toArray(new X509Certificate[certificateChain.size()]);
}
throw log.unableToCreateCertificateChainFromCertificateMap();
}

private static void swapCertificates(Certificate[] certificates, int i, int j) {
Certificate tempCertificate = certificates[i];
certificates[i] = certificates[j];
certificates[j] = tempCertificate;
}

private static boolean issuedBy(final X509Certificate certificate, X509Certificate issuer) {
if (issuer.getSubjectDN().equals(certificate.getIssuerDN())) {
try {
certificate.verify(issuer.getPublicKey());
return true;
} catch (Exception e) {
return false;
}
}
return false;
}

private static boolean createX509CertificateChain(final X509Certificate firstCertificate, final ArrayList<X509Certificate> certificateChain,
final HashMap<Principal, HashSet<X509Certificate>> certificatesMap) {
if (issuedBy(firstCertificate, firstCertificate)) {
// self-signed
certificateChain.add(firstCertificate);
return true;
}
final HashSet<X509Certificate> issuerCertificates = certificatesMap.get(firstCertificate.getIssuerDN());
if (issuerCertificates == null || issuerCertificates.isEmpty()) {
return false;
}
for (X509Certificate issuerCertificate : issuerCertificates) {
if (issuedBy(firstCertificate, issuerCertificate)) {
// recurse
if (createX509CertificateChain(issuerCertificate, certificateChain, certificatesMap)) {
certificateChain.add(firstCertificate);
return true;
}
}
}
return false;
}

}
159 changes: 159 additions & 0 deletions src/test/java/org/wildfly/security/x500/X500Test.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
/*
* JBoss, Home of Professional Open Source.
* Copyright 2017 Red Hat, Inc., and individual contributors
* as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.wildfly.security.x500;

import static org.junit.Assert.*;

import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.Principal;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;

import javax.security.auth.x500.X500Principal;

import org.junit.Test;
import org.wildfly.security.asn1.ASN1Encodable;
import org.wildfly.security.x500.cert.X509CertificateBuilder;

/**
* Tests for X500 utilities.
*
* @author <a href="mailto:fjuma@redhat.com">Farah Juma</a>
*/
public class X500Test {

private static X509Certificate[] populateCertificateChain() throws Exception {
KeyPairGenerator keyPairGenerator;
try {
keyPairGenerator = KeyPairGenerator.getInstance("RSA");
} catch (NoSuchAlgorithmException e) {
throw new Error(e);
}
final KeyPair[] keyPairs = new KeyPair[5];
for (int i = 0; i < keyPairs.length; i++) {
keyPairs[i] = keyPairGenerator.generateKeyPair();
}
final X509Certificate[] orderedCertificates = new X509Certificate[5];
for (int i = 0; i < orderedCertificates.length; i++) {
X509CertificateBuilder builder = new X509CertificateBuilder();
X500PrincipalBuilder principalBuilder = new X500PrincipalBuilder();
principalBuilder.addItem(X500AttributeTypeAndValue.create(X500.OID_AT_COMMON_NAME,
ASN1Encodable.ofUtf8String("bob" + i)));
X500Principal dn = principalBuilder.build();
builder.setSubjectDn(dn);
if (i == orderedCertificates.length - 1) {
// self-signed
builder.setIssuerDn(dn);
builder.setSigningKey(keyPairs[i].getPrivate());
} else {
principalBuilder = new X500PrincipalBuilder();
principalBuilder.addItem(X500AttributeTypeAndValue.create(X500.OID_AT_COMMON_NAME,
ASN1Encodable.ofUtf8String("bob" + (i + 1))));
X500Principal issuerDn = principalBuilder.build();
builder.setIssuerDn(issuerDn);
builder.setSigningKey(keyPairs[i + 1].getPrivate());
}
builder.setSignatureAlgorithmName("SHA256withRSA");
builder.setPublicKey(keyPairs[i].getPublic());
orderedCertificates[i] = builder.build();
}
return orderedCertificates;
}

@Test
public void testAsOrderedX509CertificateChain() throws Exception {
final X509Certificate[] orderedCertificates = populateCertificateChain();

X509Certificate[] unorderedCertificates = new X509Certificate[1];
unorderedCertificates[0] = orderedCertificates[4];
assertArrayEquals(new X509Certificate[] { orderedCertificates[4] }, X500.asOrderedX509CertificateChain(orderedCertificates[4].getPublicKey(), unorderedCertificates));

unorderedCertificates = new X509Certificate[5];
unorderedCertificates[0] = orderedCertificates[3];
unorderedCertificates[1] = orderedCertificates[0];
unorderedCertificates[2] = orderedCertificates[4];
unorderedCertificates[3] = orderedCertificates[1];
unorderedCertificates[4] = orderedCertificates[2];
assertArrayEquals(orderedCertificates, X500.asOrderedX509CertificateChain(orderedCertificates[0].getPublicKey(), unorderedCertificates));
}

@Test
public void testAsOrderedX509CertificateChainInvalidValues() throws Exception {
final X509Certificate[] orderedCertificates = populateCertificateChain();
try {
// without starting public key
X500.asOrderedX509CertificateChain(orderedCertificates[0].getPublicKey(), new X509Certificate[] { orderedCertificates[3], orderedCertificates[1], orderedCertificates[2] });
fail("Expected IllegalArgumentException not thrown");
} catch (IllegalArgumentException expected) {
}

try {
// incomplete array
X500.asOrderedX509CertificateChain(orderedCertificates[0].getPublicKey(), new X509Certificate[] { orderedCertificates[4], orderedCertificates[0], orderedCertificates[3], orderedCertificates[1] });
fail("Expected IllegalArgumentException not thrown");
} catch (IllegalArgumentException expected) {
}
}

@Test
public void testCreateX509CertificateChain() throws Exception {
final X509Certificate[] orderedCertificates = populateCertificateChain();
HashMap<Principal, HashSet<X509Certificate>> certificatesMap = new HashMap<>();

certificatesMap = new HashMap<>();
certificatesMap.put(orderedCertificates[4].getSubjectDN(), new HashSet<> (Arrays.asList(orderedCertificates[4])));
assertArrayEquals(new X509Certificate[] { orderedCertificates[4] }, X500.createX509CertificateChain(orderedCertificates[4], certificatesMap));

certificatesMap = new HashMap<>();
for (int i = 0; i < orderedCertificates.length; i++) {
certificatesMap.put(orderedCertificates[i].getSubjectDN(), new HashSet<> (Arrays.asList(orderedCertificates[i])));
}
assertArrayEquals(orderedCertificates, X500.createX509CertificateChain(orderedCertificates[0], certificatesMap));
}

@Test
public void testCreateX509CertificateChainInvalidValues() throws Exception {
final X509Certificate[] orderedCertificates = populateCertificateChain();
HashMap<Principal, HashSet<X509Certificate>> certificatesMap = new HashMap<>();

// incomplete map
for (int i = 0; i < orderedCertificates.length; i+=2) {
certificatesMap.put(orderedCertificates[i].getSubjectDN(), new HashSet<> (Arrays.asList(orderedCertificates[i])));
}
try {
X500.createX509CertificateChain(orderedCertificates[0], certificatesMap);
fail("Expected IllegalArgumentException not thrown");
} catch (IllegalArgumentException expected) {
}

// invalid starting certificate
for (int i = 2; i < orderedCertificates.length; i++) {
certificatesMap.put(orderedCertificates[i].getSubjectDN(), new HashSet<> (Arrays.asList(orderedCertificates[i])));
}
try {
X500.createX509CertificateChain(orderedCertificates[0], certificatesMap);
fail("Expected IllegalArgumentException not thrown");
} catch (IllegalArgumentException expected) {
}
}
}

0 comments on commit 3952ee5

Please sign in to comment.