Adding utility methods for generating x509 certificates for testing purposes.
Adding jacoco test coverage report plugin. Adding and improving tests for X509SvidValidator and CertificateUtils. Signed-off-by: Max Lambrecht <maxlambrecht@gmail.com>
This commit is contained in:
parent
ef4dbf86c5
commit
ef2cdafab9
23
build.gradle
23
build.gradle
|
|
@ -3,6 +3,7 @@ subprojects {
|
||||||
version '0.6.0'
|
version '0.6.0'
|
||||||
|
|
||||||
apply plugin: 'java-library'
|
apply plugin: 'java-library'
|
||||||
|
apply plugin: 'jacoco'
|
||||||
|
|
||||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||||
targetCompatibility = JavaVersion.VERSION_1_8
|
targetCompatibility = JavaVersion.VERSION_1_8
|
||||||
|
|
@ -13,6 +14,28 @@ subprojects {
|
||||||
|
|
||||||
test {
|
test {
|
||||||
useJUnitPlatform()
|
useJUnitPlatform()
|
||||||
|
finalizedBy jacocoTestReport
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
jacocoTestReport {
|
||||||
|
dependsOn test // tests are required to run before generating the report
|
||||||
|
reports {
|
||||||
|
xml.enabled false
|
||||||
|
csv.enabled false
|
||||||
|
html.destination file("${buildDir}/jacocoHtml")
|
||||||
|
}
|
||||||
|
|
||||||
|
afterEvaluate {
|
||||||
|
classDirectories.setFrom(files(classDirectories.files.collect {
|
||||||
|
fileTree(dir: it, exclude: ['**/internal/**', '**/exception/**'])
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
jacoco {
|
||||||
|
toolVersion = "0.8.5"
|
||||||
|
reportsDir = file("$buildDir/customJacocoReportDir")
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
|
|
||||||
|
|
@ -56,5 +56,8 @@ dependencies {
|
||||||
|
|
||||||
// library for processing JWT tokens and JOSE JWK bundles
|
// library for processing JWT tokens and JOSE JWK bundles
|
||||||
implementation group: 'com.nimbusds', name: 'nimbus-jose-jwt', version: '5.7'
|
implementation group: 'com.nimbusds', name: 'nimbus-jose-jwt', version: '5.7'
|
||||||
|
|
||||||
|
// using bouncy castle for generating x509 certs for testing purposes
|
||||||
|
testImplementation group: 'org.bouncycastle', name: 'bcpkix-jdk15on', version: '1.65'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ import java.util.List;
|
||||||
import java.util.concurrent.ThreadLocalRandom;
|
import java.util.concurrent.ThreadLocalRandom;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import static java.util.Collections.EMPTY_LIST;
|
||||||
import static org.apache.commons.lang3.StringUtils.startsWith;
|
import static org.apache.commons.lang3.StringUtils.startsWith;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -209,6 +210,9 @@ public class CertificateUtils {
|
||||||
}
|
}
|
||||||
|
|
||||||
private static List<String> getSpiffeIds(X509Certificate certificate) throws CertificateParsingException {
|
private static List<String> getSpiffeIds(X509Certificate certificate) throws CertificateParsingException {
|
||||||
|
if (certificate.getSubjectAlternativeNames() == null) {
|
||||||
|
return EMPTY_LIST;
|
||||||
|
}
|
||||||
return certificate.getSubjectAlternativeNames()
|
return certificate.getSubjectAlternativeNames()
|
||||||
.stream()
|
.stream()
|
||||||
.map(san -> (String) san.get(SAN_VALUE_INDEX))
|
.map(san -> (String) san.get(SAN_VALUE_INDEX))
|
||||||
|
|
|
||||||
|
|
@ -3,19 +3,26 @@ package spiffe.internal;
|
||||||
import lombok.val;
|
import lombok.val;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import spiffe.spiffeid.SpiffeId;
|
import spiffe.spiffeid.SpiffeId;
|
||||||
|
import spiffe.spiffeid.TrustDomain;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.net.URISyntaxException;
|
import java.net.URISyntaxException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
|
import java.security.InvalidKeyException;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.security.PrivateKey;
|
||||||
import java.security.cert.CertPathValidatorException;
|
import java.security.cert.CertPathValidatorException;
|
||||||
import java.security.cert.CertificateException;
|
import java.security.cert.CertificateException;
|
||||||
import java.security.cert.X509Certificate;
|
import java.security.cert.X509Certificate;
|
||||||
|
import java.security.spec.InvalidKeySpecException;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
import static org.junit.jupiter.api.Assertions.fail;
|
import static spiffe.utils.X509CertificateTestUtils.createCertificate;
|
||||||
|
import static spiffe.utils.X509CertificateTestUtils.createRootCA;
|
||||||
|
|
||||||
public class CertificateUtilsTest {
|
public class CertificateUtilsTest {
|
||||||
|
|
||||||
|
|
@ -24,7 +31,7 @@ public class CertificateUtilsTest {
|
||||||
val path = Paths.get(toUri("testdata/internal/cert.pem"));
|
val path = Paths.get(toUri("testdata/internal/cert.pem"));
|
||||||
val certBytes = Files.readAllBytes(path);
|
val certBytes = Files.readAllBytes(path);
|
||||||
|
|
||||||
List<X509Certificate> x509CertificateList = null;
|
List<X509Certificate> x509CertificateList;
|
||||||
SpiffeId spiffeId = null;
|
SpiffeId spiffeId = null;
|
||||||
try {
|
try {
|
||||||
x509CertificateList = CertificateUtils.generateCertificates(certBytes);
|
x509CertificateList = CertificateUtils.generateCertificates(certBytes);
|
||||||
|
|
@ -55,7 +62,59 @@ public class CertificateUtilsTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testGeneratePrivateKey() throws URISyntaxException, IOException {
|
||||||
|
val keyPath = Paths.get(toUri("testdata/internal/privateKeyRsa.pem"));
|
||||||
|
val keyBytes = Files.readAllBytes(keyPath);
|
||||||
|
|
||||||
|
try {
|
||||||
|
PrivateKey privateKey = CertificateUtils.generatePrivateKey(keyBytes);
|
||||||
|
assertNotNull(privateKey);
|
||||||
|
assertEquals("RSA", privateKey.getAlgorithm());
|
||||||
|
} catch (InvalidKeySpecException | InvalidKeyException | NoSuchAlgorithmException e) {
|
||||||
|
fail("Should have generated key", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testGetSpiffeId() throws Exception {
|
||||||
|
val rootCa = createRootCA("C = US, O = SPIFFE", "spiffe://domain.test" );
|
||||||
|
val leaf = createCertificate("C = US, O = SPIRE", "C = US, O = SPIRE", "spiffe://domain.test/workload", rootCa, false);
|
||||||
|
SpiffeId spiffeId = CertificateUtils.getSpiffeId(leaf.getCertificate());
|
||||||
|
assertEquals(SpiffeId.parse("spiffe://domain.test/workload"), spiffeId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testGetSpiffeId_certNotContainSpiffeId_throwsCertificateException() throws Exception {
|
||||||
|
val rootCa = createRootCA("C = US, O = SPIFFE", "spiffe://domain.test" );
|
||||||
|
val leaf = createCertificate("C = US, O = SPIRE", "C = US, O = SPIRE", "", rootCa, false);
|
||||||
|
try {
|
||||||
|
CertificateUtils.getSpiffeId(leaf.getCertificate());
|
||||||
|
fail("exception is expected");
|
||||||
|
} catch (CertificateException e) {
|
||||||
|
assertEquals("Certificate does not contain SPIFFE ID in the URI SAN", e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testGetTrustDomain() throws Exception {
|
||||||
|
val rootCa = createRootCA("C = US, O = SPIFFE", "spiffe://domain.test" );
|
||||||
|
val intermediate = createCertificate("C = US, O = SPIRE", "C = US, O = SPIRE", "spiffe://domain.test/host", rootCa, true);
|
||||||
|
val leaf = createCertificate("C = US, O = SPIRE", "C = US, O = SPIRE", "spiffe://domain.test/workload", intermediate, false);
|
||||||
|
|
||||||
|
val chain = Arrays.asList(leaf.getCertificate(), intermediate.getCertificate());
|
||||||
|
|
||||||
|
try {
|
||||||
|
TrustDomain trustDomain = CertificateUtils.getTrustDomain(chain);
|
||||||
|
assertNotNull(trustDomain);
|
||||||
|
assertEquals(TrustDomain.of("domain.test"), trustDomain);
|
||||||
|
} catch (CertificateException e) {
|
||||||
|
fail(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private URI toUri(String path) throws URISyntaxException {
|
private URI toUri(String path) throws URISyntaxException {
|
||||||
return getClass().getClassLoader().getResource(path).toURI();
|
return getClass().getClassLoader().getResource(path).toURI();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,88 +3,79 @@ package spiffe.svid.x509svid;
|
||||||
import lombok.val;
|
import lombok.val;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.mockito.Mock;
|
|
||||||
import org.mockito.MockitoAnnotations;
|
|
||||||
import spiffe.bundle.x509bundle.X509Bundle;
|
import spiffe.bundle.x509bundle.X509Bundle;
|
||||||
import spiffe.bundle.x509bundle.X509BundleSource;
|
|
||||||
import spiffe.exception.BundleNotFoundException;
|
import spiffe.exception.BundleNotFoundException;
|
||||||
import spiffe.internal.CertificateUtils;
|
|
||||||
import spiffe.spiffeid.SpiffeId;
|
import spiffe.spiffeid.SpiffeId;
|
||||||
import spiffe.spiffeid.TrustDomain;
|
import spiffe.spiffeid.TrustDomain;
|
||||||
|
import spiffe.utils.X509CertificateTestUtils.CertAndKeyPair;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.URI;
|
|
||||||
import java.net.URISyntaxException;
|
import java.net.URISyntaxException;
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Paths;
|
|
||||||
import java.security.cert.CertificateException;
|
import java.security.cert.CertificateException;
|
||||||
|
import java.security.cert.X509Certificate;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import static java.util.Collections.EMPTY_LIST;
|
import static java.util.Collections.EMPTY_LIST;
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.fail;
|
import static org.junit.jupiter.api.Assertions.fail;
|
||||||
import static org.mockito.Mockito.when;
|
import static spiffe.utils.X509CertificateTestUtils.createCertificate;
|
||||||
|
import static spiffe.utils.X509CertificateTestUtils.createRootCA;
|
||||||
|
|
||||||
public class X509SvidValidatorTest {
|
public class X509SvidValidatorTest {
|
||||||
|
|
||||||
@Mock
|
List<X509Certificate> chain;
|
||||||
X509BundleSource bundleSourceMock;
|
CertAndKeyPair rootCa;
|
||||||
|
CertAndKeyPair otherRootCa;
|
||||||
|
CertAndKeyPair leaf;
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
void setup() {
|
void setUp() throws Exception {
|
||||||
MockitoAnnotations.initMocks(this);
|
rootCa = createRootCA("C = US, O = SPIFFE", "spiffe://example.org" );
|
||||||
|
val intermediate1 = createCertificate("C = US, O = SPIRE", "C = US, O = SPIFFE", "spiffe://example.org/host", rootCa, true);
|
||||||
|
val intermediate2 = createCertificate("C = US, O = SPIRE", "C = US, O = SPIRE", "spiffe://example.org/host2", intermediate1, true);
|
||||||
|
leaf = createCertificate("C = US, O = SPIRE", "C = US, O = SPIRE", "spiffe://example.org/test", intermediate2, false);
|
||||||
|
chain = Arrays.asList(leaf.getCertificate(), intermediate2.getCertificate(), intermediate1.getCertificate());
|
||||||
|
otherRootCa = createRootCA("C = US, O = SPIFFE", "spiffe://example.org" );
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void verifyChain_certificateExpired_throwsCertificateException() throws IOException, CertificateException, BundleNotFoundException, URISyntaxException {
|
void testVerifyChain_chainCanBeVerifiedWithAuthorityInBundle() throws Exception {
|
||||||
val certPath = Paths.get(toUri("testdata/x509svid/cert.pem"));
|
HashSet<X509Certificate> x509Authorities = new HashSet<>();
|
||||||
val certBytes = Files.readAllBytes(certPath);
|
x509Authorities.add(rootCa.getCertificate());
|
||||||
val chain = CertificateUtils.generateCertificates(certBytes);
|
x509Authorities.add(otherRootCa.getCertificate());
|
||||||
|
|
||||||
val bundlePath = Paths.get(toUri("testdata/x509svid/bundle.pem"));
|
val x509Bundle = new X509Bundle(TrustDomain.of("example.org"), x509Authorities);
|
||||||
X509Bundle x509Bundle=
|
X509SvidValidator.verifyChain(chain, x509Bundle);
|
||||||
X509Bundle.load(
|
}
|
||||||
TrustDomain.of("example.org"),
|
|
||||||
bundlePath
|
|
||||||
);
|
|
||||||
|
|
||||||
when(bundleSourceMock
|
@Test
|
||||||
.getX509BundleForTrustDomain(
|
void testVerifyChain_chainCannotBeVerifiedWithAuthorityInBundle_throwsCertificateException() throws Exception {
|
||||||
TrustDomain.of("example.org")))
|
HashSet<X509Certificate> x509Authorities = new HashSet<>();
|
||||||
.thenReturn(x509Bundle);
|
x509Authorities.add(otherRootCa.getCertificate());
|
||||||
|
|
||||||
|
val x509Bundle = new X509Bundle(TrustDomain.of("example.org"), x509Authorities);
|
||||||
try {
|
try {
|
||||||
X509SvidValidator.verifyChain(chain, bundleSourceMock);
|
X509SvidValidator.verifyChain(chain, x509Bundle);
|
||||||
fail("Verify chain should have thrown validation exception");
|
fail("exception is expected");
|
||||||
} catch (CertificateException e) {
|
} catch (CertificateException e) {
|
||||||
assertEquals("Cert chain cannot be verified", e.getMessage());
|
assertEquals("Cert chain cannot be verified", e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void verifyChain_noBundleForTrustDomain_throwsBundleNotFoundException() throws IOException, CertificateException, BundleNotFoundException, URISyntaxException {
|
void verifyChain_noBundleForTrustDomain_throwsBundleNotFoundException() throws Exception {
|
||||||
val certPath = Paths.get(toUri("testdata/x509svid/cert.pem"));
|
HashSet<X509Certificate> x509Authorities = new HashSet<>();
|
||||||
val certBytes = Files.readAllBytes(certPath);
|
x509Authorities.add(otherRootCa.getCertificate());
|
||||||
val chain = CertificateUtils.generateCertificates(certBytes);
|
|
||||||
|
|
||||||
val bundlePath = Paths.get(toUri("testdata/x509svid/bundle.pem"));
|
val x509Bundle = new X509Bundle(TrustDomain.of("other.org"), x509Authorities);
|
||||||
X509Bundle x509Bundle=
|
|
||||||
X509Bundle.load(
|
|
||||||
TrustDomain.of("example.org"),
|
|
||||||
bundlePath
|
|
||||||
);
|
|
||||||
|
|
||||||
when(bundleSourceMock
|
|
||||||
.getX509BundleForTrustDomain(
|
|
||||||
TrustDomain.of("example.org")))
|
|
||||||
.thenThrow(new BundleNotFoundException("No bundle found"));
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
X509SvidValidator.verifyChain(chain, bundleSourceMock);
|
X509SvidValidator.verifyChain(chain, x509Bundle);
|
||||||
fail("Verify chain should have thrown validation exception");
|
fail("Verify chain should have thrown validation exception");
|
||||||
} catch (BundleNotFoundException e) {
|
} catch (BundleNotFoundException e) {
|
||||||
assertEquals("No bundle found", e.getMessage());
|
assertEquals("No X509 bundle found for trust domain example.org", e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -93,13 +84,9 @@ public class X509SvidValidatorTest {
|
||||||
val spiffeId1 = SpiffeId.parse("spiffe://example.org/test");
|
val spiffeId1 = SpiffeId.parse("spiffe://example.org/test");
|
||||||
val spiffeId2 = SpiffeId.parse("spiffe://example.org/test2");
|
val spiffeId2 = SpiffeId.parse("spiffe://example.org/test2");
|
||||||
|
|
||||||
val certPath = Paths.get(toUri("testdata/x509svid/cert.pem"));
|
|
||||||
val certBytes = Files.readAllBytes(certPath);
|
|
||||||
val x509Certificate = CertificateUtils.generateCertificates(certBytes);
|
|
||||||
|
|
||||||
val spiffeIdList = Arrays.asList(spiffeId1, spiffeId2);
|
val spiffeIdList = Arrays.asList(spiffeId1, spiffeId2);
|
||||||
|
|
||||||
X509SvidValidator.verifySpiffeId(x509Certificate.get(0), () -> spiffeIdList);
|
X509SvidValidator.verifySpiffeId(leaf.getCertificate(), () -> spiffeIdList);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
@ -108,12 +95,8 @@ public class X509SvidValidatorTest {
|
||||||
val spiffeId2 = SpiffeId.parse("spiffe://example.org/other2");
|
val spiffeId2 = SpiffeId.parse("spiffe://example.org/other2");
|
||||||
List<SpiffeId> spiffeIdList = Arrays.asList(spiffeId1, spiffeId2);
|
List<SpiffeId> spiffeIdList = Arrays.asList(spiffeId1, spiffeId2);
|
||||||
|
|
||||||
val certPath = Paths.get(toUri("testdata/x509svid/cert.pem"));
|
|
||||||
val certBytes = Files.readAllBytes(certPath);
|
|
||||||
val x509Certificate = CertificateUtils.generateCertificates(certBytes);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
X509SvidValidator.verifySpiffeId(x509Certificate.get(0), () -> spiffeIdList);
|
X509SvidValidator.verifySpiffeId(leaf.getCertificate(), () -> spiffeIdList);
|
||||||
fail("Should have thrown CertificateException");
|
fail("Should have thrown CertificateException");
|
||||||
} catch (CertificateException e) {
|
} catch (CertificateException e) {
|
||||||
assertEquals("SPIFFE ID spiffe://example.org/test in X.509 certificate is not accepted", e.getMessage());
|
assertEquals("SPIFFE ID spiffe://example.org/test in X.509 certificate is not accepted", e.getMessage());
|
||||||
|
|
@ -133,10 +116,7 @@ public class X509SvidValidatorTest {
|
||||||
@Test
|
@Test
|
||||||
void checkSpiffeId_nullAcceptedSpiffeIdsSuppplier_throwsNullPointerException() throws CertificateException, URISyntaxException, IOException {
|
void checkSpiffeId_nullAcceptedSpiffeIdsSuppplier_throwsNullPointerException() throws CertificateException, URISyntaxException, IOException {
|
||||||
try {
|
try {
|
||||||
val certPath = Paths.get(toUri("testdata/x509svid/cert.pem"));
|
X509SvidValidator.verifySpiffeId(leaf.getCertificate(), null);
|
||||||
val certBytes = Files.readAllBytes(certPath);
|
|
||||||
val x509Certificate = CertificateUtils.generateCertificates(certBytes);
|
|
||||||
X509SvidValidator.verifySpiffeId(x509Certificate.get(0), null);
|
|
||||||
fail("should have thrown an exception");
|
fail("should have thrown an exception");
|
||||||
} catch (NullPointerException e) {
|
} catch (NullPointerException e) {
|
||||||
assertEquals("acceptedSpiffedIdsSupplier is marked non-null but is null", e.getMessage());
|
assertEquals("acceptedSpiffedIdsSupplier is marked non-null but is null", e.getMessage());
|
||||||
|
|
@ -146,7 +126,7 @@ public class X509SvidValidatorTest {
|
||||||
@Test
|
@Test
|
||||||
void verifyChain_nullChain_throwsNullPointerException() throws CertificateException, BundleNotFoundException {
|
void verifyChain_nullChain_throwsNullPointerException() throws CertificateException, BundleNotFoundException {
|
||||||
try {
|
try {
|
||||||
X509SvidValidator.verifyChain(null, bundleSourceMock);
|
X509SvidValidator.verifyChain(null, new X509Bundle(TrustDomain.of("example.org")));
|
||||||
fail("should have thrown an exception");
|
fail("should have thrown an exception");
|
||||||
} catch (NullPointerException e) {
|
} catch (NullPointerException e) {
|
||||||
assertEquals("chain is marked non-null but is null", e.getMessage());
|
assertEquals("chain is marked non-null but is null", e.getMessage());
|
||||||
|
|
@ -156,17 +136,10 @@ public class X509SvidValidatorTest {
|
||||||
@Test
|
@Test
|
||||||
void verifyChain_nullBundleSource_throwsNullPointerException() throws CertificateException, BundleNotFoundException, URISyntaxException, IOException {
|
void verifyChain_nullBundleSource_throwsNullPointerException() throws CertificateException, BundleNotFoundException, URISyntaxException, IOException {
|
||||||
try {
|
try {
|
||||||
val certPath = Paths.get(toUri("testdata/x509svid/cert.pem"));
|
|
||||||
val certBytes = Files.readAllBytes(certPath);
|
|
||||||
val chain = CertificateUtils.generateCertificates(certBytes);
|
|
||||||
X509SvidValidator.verifyChain(chain, null);
|
X509SvidValidator.verifyChain(chain, null);
|
||||||
fail("should have thrown an exception");
|
fail("should have thrown an exception");
|
||||||
} catch (NullPointerException e) {
|
} catch (NullPointerException e) {
|
||||||
assertEquals("x509BundleSource is marked non-null but is null", e.getMessage());
|
assertEquals("x509BundleSource is marked non-null but is null", e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private URI toUri(String path) throws URISyntaxException {
|
|
||||||
return getClass().getClassLoader().getResource(path).toURI();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,125 @@
|
||||||
|
package spiffe.utils;
|
||||||
|
|
||||||
|
import lombok.Value;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.bouncycastle.asn1.ASN1EncodableVector;
|
||||||
|
import org.bouncycastle.asn1.DERSequence;
|
||||||
|
import org.bouncycastle.asn1.x500.X500Name;
|
||||||
|
import org.bouncycastle.asn1.x509.*;
|
||||||
|
import org.bouncycastle.cert.CertIOException;
|
||||||
|
import org.bouncycastle.cert.X509CertificateHolder;
|
||||||
|
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
|
||||||
|
import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
|
||||||
|
import org.bouncycastle.operator.ContentSigner;
|
||||||
|
import org.bouncycastle.operator.OperatorCreationException;
|
||||||
|
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
|
||||||
|
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.security.KeyPair;
|
||||||
|
import java.security.KeyPairGenerator;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.security.PrivateKey;
|
||||||
|
import java.security.cert.CertificateException;
|
||||||
|
import java.security.cert.X509Certificate;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.time.temporal.ChronoUnit;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
public class X509CertificateTestUtils {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a self-signed Root CA certificate
|
||||||
|
*/
|
||||||
|
public static CertAndKeyPair createRootCA(String subject, String spiffeId) throws Exception {
|
||||||
|
KeyPair certKeyPair = generateKeyPair();
|
||||||
|
JcaX509v3CertificateBuilder builder = getCertificateBuilder(certKeyPair, subject, subject);
|
||||||
|
addCAExtensions(builder, certKeyPair, spiffeId);
|
||||||
|
|
||||||
|
// self signed
|
||||||
|
X509Certificate cert = getSignedX509Certificate(certKeyPair.getPrivate(), builder);
|
||||||
|
return new CertAndKeyPair(cert, certKeyPair);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a certificate signed with the private key of the issuer. The generated cert can be an intermediate CA or
|
||||||
|
* a leaf certificate.
|
||||||
|
*/
|
||||||
|
public static CertAndKeyPair createCertificate(String subject, String issuerSubject, String spiffeId, CertAndKeyPair issuer, boolean isCa) throws Exception {
|
||||||
|
KeyPair certKeyPair = generateKeyPair();
|
||||||
|
PrivateKey issuerKey = issuer.keyPair.getPrivate();
|
||||||
|
JcaX509v3CertificateBuilder builder = getCertificateBuilder(certKeyPair, subject, issuerSubject);
|
||||||
|
addCertExtensions(builder, spiffeId, isCa);
|
||||||
|
X509Certificate cert = getSignedX509Certificate(issuerKey, builder);
|
||||||
|
return new CertAndKeyPair(cert, certKeyPair);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Value
|
||||||
|
public final static class CertAndKeyPair {
|
||||||
|
private final KeyPair keyPair;
|
||||||
|
private final X509Certificate certificate;
|
||||||
|
|
||||||
|
public CertAndKeyPair(X509Certificate certificate, KeyPair keyPair) {
|
||||||
|
this.keyPair = keyPair;
|
||||||
|
this.certificate = certificate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static KeyPair generateKeyPair() throws NoSuchAlgorithmException {
|
||||||
|
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
|
||||||
|
return keyGen.generateKeyPair();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void addCertExtensions(JcaX509v3CertificateBuilder builder, String spiffeId, boolean isCa) throws CertIOException {
|
||||||
|
builder.addExtension(Extension.basicConstraints, true, new BasicConstraints(isCa));
|
||||||
|
|
||||||
|
if (isCa) {
|
||||||
|
KeyUsage usage = new KeyUsage(KeyUsage.keyCertSign | KeyUsage.digitalSignature | KeyUsage.cRLSign);
|
||||||
|
builder.addExtension(Extension.keyUsage, true, usage);
|
||||||
|
} else {
|
||||||
|
KeyUsage usage = new KeyUsage(KeyUsage.digitalSignature | KeyUsage.keyEncipherment | KeyUsage.keyAgreement);
|
||||||
|
builder.addExtension(Extension.keyUsage, true, usage);
|
||||||
|
|
||||||
|
ASN1EncodableVector purposes = new ASN1EncodableVector();
|
||||||
|
purposes.add(KeyPurposeId.id_kp_serverAuth);
|
||||||
|
purposes.add(KeyPurposeId.id_kp_clientAuth);
|
||||||
|
builder.addExtension(Extension.extendedKeyUsage, false, new DERSequence(purposes));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (StringUtils.isNotBlank(spiffeId)) {
|
||||||
|
builder.addExtension(Extension.subjectAlternativeName, false,
|
||||||
|
new GeneralNames(new GeneralName(GeneralName.uniformResourceIdentifier, spiffeId )));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void addCAExtensions(JcaX509v3CertificateBuilder builder, KeyPair certKeyPair, String spiffeId) throws CertIOException {
|
||||||
|
builder.addExtension(Extension.basicConstraints, true, new BasicConstraints(true));
|
||||||
|
KeyUsage usage = new KeyUsage(KeyUsage.keyCertSign | KeyUsage.digitalSignature | KeyUsage.cRLSign);
|
||||||
|
builder.addExtension(Extension.keyUsage, true, usage);
|
||||||
|
|
||||||
|
builder.addExtension(Extension.subjectAlternativeName, true,
|
||||||
|
new GeneralNames(new GeneralName(GeneralName.uniformResourceIdentifier, spiffeId)));
|
||||||
|
|
||||||
|
builder.addExtension(Extension.subjectKeyIdentifier, false, new SubjectKeyIdentifier(certKeyPair.getPublic().getEncoded()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static JcaX509v3CertificateBuilder getCertificateBuilder(KeyPair certKeyPair, String subject, String issuerSubject) {
|
||||||
|
BigInteger serialNumber = BigInteger.valueOf(System.currentTimeMillis());
|
||||||
|
Instant validFrom = Instant.now().minus(5, ChronoUnit.DAYS);
|
||||||
|
Instant validUntil = validFrom.plus(30 , ChronoUnit.DAYS);
|
||||||
|
X500Name name = new X500Name(subject);
|
||||||
|
X500Name issuerName = new X500Name(issuerSubject);
|
||||||
|
return new JcaX509v3CertificateBuilder(
|
||||||
|
issuerName,
|
||||||
|
serialNumber,
|
||||||
|
Date.from(validFrom), Date.from(validUntil),
|
||||||
|
name, certKeyPair.getPublic());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static X509Certificate getSignedX509Certificate(PrivateKey issuerKey, JcaX509v3CertificateBuilder builder) throws OperatorCreationException, CertificateException {
|
||||||
|
ContentSigner signer = new JcaContentSignerBuilder("SHA256WithRSA").build(issuerKey);
|
||||||
|
X509CertificateHolder certHolder = builder.build(signer);
|
||||||
|
return new JcaX509CertificateConverter().getCertificate(certHolder);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
-----BEGIN PRIVATE KEY-----
|
||||||
|
MIIB5QIBADANBgkqhkiG9w0BAQEFAASCAc8wggHLAgEAAmEA0EdYWa7LZ/ZA2sKk
|
||||||
|
JsREA2TkXDIq01JIRsVetPFRqRW5GEmkwwk0qThebp/ofZt8oLkMwp06BLaQC3tH
|
||||||
|
QGkK31jh5Djd1NHA8CpU4ByObgGeY75dhwFEzI4YwXM+e0n/AgMBAAECYFD4S4qh
|
||||||
|
/4WtIE1refFwP5iqMnT9M9TvmhWZSVZCsqJvRYQBrUH9ZDGdLmkHVZTvSvKKmkoZ
|
||||||
|
VvXDlpmW4Eaed8xXqsLYplMrVo6WkvdtvlvfIwP69PGFmWwKgFBe2aLHsQIxAOoT
|
||||||
|
dwmlr/dNNu2MjyjcvTK0lCn6vexp6k8MaXTEsTvG0kBmVDZGuSXcKzbBoAZLxQIx
|
||||||
|
AOPJU65HnDpcOM+qLH3jahTnbrg4C0BO0mj1OusLcSUnA6bFP2NkZ9LyWfMerbvG
|
||||||
|
8wIxAI7Iyt8mo50+C5iCGj250OtiPdMRsdLJlPUdRCLHbLljAZPpF8t3/q66i929
|
||||||
|
5MiSZQIwE3wXQmMxw/Q7j9f4slQPsPYTDIMOw1N6wCup/I0gApORxmQ9Bd2C3BKL
|
||||||
|
CzbmmZdtAjEA2v1fSN4DPcQW2bgmoE0GoNEMYGfSza7jBGOiKkqm4p2hAjaur174
|
||||||
|
U2t9BPJHk+Xh
|
||||||
|
-----END PRIVATE KEY-----
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
-----BEGIN CERTIFICATE-----
|
|
||||||
MIIBzDCCAVGgAwIBAgIBADAKBggqhkjOPQQDAzAeMQswCQYDVQQGEwJVUzEPMA0G
|
|
||||||
A1UEChMGU1BJRkZFMB4XDTIwMDMxNjE4MDQ1N1oXDTIwMDMyMzE4MDUwN1owHjEL
|
|
||||||
MAkGA1UEBhMCVVMxDzANBgNVBAoTBlNQSUZGRTB2MBAGByqGSM49AgEGBSuBBAAi
|
|
||||||
A2IABOStlk5qbbCuuRqKitTqTzQf/UnlljO2bHAekgz+0na4oJhcsOYhRLbKQVcr
|
|
||||||
U5jf7BpcT7nYHyLMv79Cy+Xa1snzMCGhx1obgt8gAXb4b4GwyAiNzaq7ytX4SAZB
|
|
||||||
CUXp36NjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0O
|
|
||||||
BBYEFCMzt4WGhen9N2N+MwzUkwEtG6YLMB8GA1UdEQQYMBaGFHNwaWZmZTovL2V4
|
|
||||||
YW1wbGUub3JnMAoGCCqGSM49BAMDA2kAMGYCMQDJSIl+8jQek7tHRSfU2vYLQ6sy
|
|
||||||
2t4NJcc5zeSjNxFIajUogl7L8T1QNzrKpXm/ZbQCMQDU54VnA1Awq0Qq2pf4zQ1C
|
|
||||||
KOpj4PguMpUmulRVrzD2KNQz+FUKsHgL/mIZSfN5g1M=
|
|
||||||
-----END CERTIFICATE-----
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
-----BEGIN CERTIFICATE-----
|
|
||||||
MIICADCCAYagAwIBAgIQJqqLnXOR3tjUV43PpGW4YDAKBggqhkjOPQQDAzAeMQsw
|
|
||||||
CQYDVQQGEwJVUzEPMA0GA1UEChMGU1BJRkZFMB4XDTIwMDMxODE5MDc1MloXDTIw
|
|
||||||
MDMyMzE4MDUwN1owHTELMAkGA1UEBhMCVVMxDjAMBgNVBAoTBVNQSVJFMFkwEwYH
|
|
||||||
KoZIzj0CAQYIKoZIzj0DAQcDQgAE4/NA2umeK9j1U5+egPqfzomjnpnLz68jNvUN
|
|
||||||
tdA0Lg6E7/nsmvqoNbVVbaD84Jplfg6/6HWSnoO7K7A+oZ1g+qOBpjCBozAOBgNV
|
|
||||||
HQ8BAf8EBAMCA6gwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMAwGA1Ud
|
|
||||||
EwEB/wQCMAAwHQYDVR0OBBYEFPLf59AkFCva6ehKx8L4i+pjCU1CMB8GA1UdIwQY
|
|
||||||
MBaAFCMzt4WGhen9N2N+MwzUkwEtG6YLMCQGA1UdEQQdMBuGGXNwaWZmZTovL2V4
|
|
||||||
YW1wbGUub3JnL3Rlc3QwCgYIKoZIzj0EAwMDaAAwZQIwRzrN6Rh8X28UYJEuql/1
|
|
||||||
GZEeto7zzj0UtjZFwQy2ODl48nFFRGKUnq8mc4cIMI/kAjEAlixJBUHqb4ty8Ff+
|
|
||||||
d0XHzA5duE1hzFxd2feRppjqiOHKJu7Rh2dzZd3rZhMZWrrd
|
|
||||||
-----END CERTIFICATE-----
|
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
lombok.addLombokGeneratedAnnotation = true
|
||||||
Loading…
Reference in New Issue