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