33
44package software.aws.toolkits.jetbrains.services.amazonq.lsp
55
6+ import com.github.tomakehurst.wiremock.WireMockServer
67import com.github.tomakehurst.wiremock.common.Slf4jNotifier
7- import com.github.tomakehurst.wiremock.common.ssl.KeyStoreSettings
8+ import com.github.tomakehurst.wiremock.core.WireMockConfiguration
89import com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig
9- import com.github.tomakehurst.wiremock.junit5.WireMockExtension
1010import com.intellij.testFramework.ApplicationExtension
11+ import com.intellij.util.net.ssl.CertificateManager
12+ import org.assertj.core.api.Assertions.assertThat
1113import org.junit.jupiter.api.Test
1214import org.junit.jupiter.api.extension.ExtendWith
13- import org.junit.jupiter.api.extension.RegisterExtension
1415import java.net.URI
1516import java.security.cert.X509Certificate
16-
1717import java.math.BigInteger
1818import java.security.KeyPairGenerator
1919import java.security.KeyStore
20- import java.util.*
2120import org.bouncycastle.asn1.x500.X500Name
22- import org.bouncycastle.asn1.x509.*
23- import org.bouncycastle.cert.X509v3CertificateBuilder
21+ import org.bouncycastle.asn1.x509.BasicConstraints
22+ import org.bouncycastle.asn1.x509.Extension
23+ import org.bouncycastle.asn1.x509.GeneralName
24+ import org.bouncycastle.asn1.x509.GeneralNames
25+ import org.bouncycastle.asn1.x509.KeyUsage
2426import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter
2527import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder
2628import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder
29+ import org.junit.jupiter.api.extension.AfterAllCallback
30+ import org.junit.jupiter.api.extension.ExtensionContext
2731import software.aws.toolkits.core.utils.outputStream
2832import java.nio.file.Files
2933import java.nio.file.Path
3034import java.security.KeyPair
3135import java.security.PrivateKey
3236import java.time.Instant
3337import java.time.temporal.ChronoUnit
38+ import java.util.Date
3439
3540@ExtendWith(ApplicationExtension ::class )
36- class TrustChainUtilTest {
41+ class TrustChainUtilTest : AfterAllCallback {
3742 companion object {
38- @RegisterExtension
39- @JvmStatic
40- val wm1 = WireMockExtension .newInstance()
41- .options(
42- wireMockConfig()
43- .httpDisabled(true )
44- .http2TlsDisabled(true )
45- .keystorePath(Files .createTempFile(" certs" , " jks" ).toAbsolutePath().apply { CertificateGenerator .generateCertificateChain(this ) }.toString())
46- .keystoreType(" jks" )
47- .keystorePassword(" changeit" )
48- .keyManagerPassword(" changeit" )
49- .dynamicHttpsPort()
50- .notifier(Slf4jNotifier (true ))
51- )
52- .build()
43+ private val certs = CertificateGenerator .generateCertificateChain()
44+ }
45+
46+ override fun afterAll (context : ExtensionContext ) {
47+ CertificateManager .getInstance().customTrustManager.apply {
48+ certificates.toList().forEach { removeCertificate(it) }
49+ }
50+
51+ assertThat(CertificateManager .getInstance().customTrustManager.certificates).isEmpty()
52+ }
53+
54+ @Test
55+ fun `returns chain from server if leaf is trust anchor` () {
56+ mockWithOptions(
57+ {
58+ it.keystorePath(
59+ Files .createTempFile(" certs" , " jks" )
60+ .toAbsolutePath()
61+ .apply {
62+ CertificateGenerator .saveToKeyStore(
63+ this ,
64+ certs.values.first(),
65+ certs.keys.take(2 ).toTypedArray(),
66+ )
67+ }
68+ .toString()
69+ )
70+ }
71+ ) {
72+ val trustChain = TrustChainUtil .getTrustChain(URI (" https://localhost:${it.httpsPort()} " ))
73+ // leaf, intermediate
74+ assertThat(trustChain)
75+ .isEqualTo(certs.keys.take(2 ).toList())
76+ }
77+ }
78+
79+ @Test
80+ fun `returns entire chain if CA is trusted` () {
81+ CertificateManager .getInstance().customTrustManager.addCertificate(certs.keys.last())
82+
83+ mockWithOptions(
84+ {
85+ it.keystorePath(
86+ Files .createTempFile(" certs" , " jks" )
87+ .toAbsolutePath()
88+ .apply {
89+ CertificateGenerator .saveToKeyStore(
90+ this ,
91+ certs.values.first(),
92+ certs.keys.take(2 ).toTypedArray(),
93+ )
94+ }
95+ .toString()
96+ )
97+ }
98+ ) {
99+ val trustChain = TrustChainUtil .getTrustChain(URI (" https://localhost:${it.httpsPort()} " ))
100+ // leaf, intermediate, root
101+ assertThat(trustChain)
102+ .isEqualTo(certs.keys.toList())
103+ }
53104 }
54105
55106 @Test
56- fun `TrustChainUtil should return a valid trust chain` () {
57- val trustChain = TrustChainUtil .getTrustChain(URI (" https://localhost:${wm1.httpsPort} " ))
58- println (trustChain)
59- assert (trustChain.isNotEmpty())
107+ fun `returns entire chain if CA is trusted but only returns leaf` () {
108+ CertificateManager .getInstance().customTrustManager.addCertificate(certs.keys.last())
109+
110+ mockWithOptions(
111+ {
112+ it.keystorePath(
113+ Files .createTempFile(" certs" , " jks" )
114+ .toAbsolutePath()
115+ .apply {
116+ CertificateGenerator .saveToKeyStore(
117+ this ,
118+ certs.values.first(),
119+ certs.keys.take(1 ).toTypedArray(),
120+ )
121+ }
122+ .toString()
123+ )
124+ }
125+ ) {
126+ val trustChain = TrustChainUtil .getTrustChain(URI (" https://localhost:${it.httpsPort()} " ))
127+ // leaf, intermediate, root
128+ assertThat(trustChain)
129+ .isEqualTo(certs.keys.toList())
130+ }
131+ }
132+
133+ private fun mockWithOptions (options : (WireMockConfiguration ) -> Unit , runnable : (WireMockServer ) -> Unit ) {
134+ val server = WireMockServer (
135+ wireMockConfig()
136+ .httpDisabled(true )
137+ .http2TlsDisabled(true )
138+ .keystoreType(" jks" )
139+ .keystorePassword(" changeit" )
140+ .keyManagerPassword(" changeit" )
141+ .dynamicHttpsPort()
142+ .notifier(Slf4jNotifier (true ))
143+ .apply { options(this ) }
144+ )
145+
146+ try {
147+ server.start()
148+
149+ runnable(server)
150+ } finally {
151+ server.stop()
152+ }
60153 }
61154}
62155
@@ -66,7 +159,7 @@ class CertificateGenerator {
66159 private const val SIGNATURE_ALGORITHM = " SHA256withRSA"
67160 private const val KEY_SIZE = 4096
68161
69- fun generateCertificateChain (keystorePath : Path ) {
162+ fun generateCertificateChain (): Map < X509Certificate , KeyPair > {
70163 // Generate Root CA
71164 val rootKeyPair = generateKeyPair()
72165 val rootCert = generateRootCertificate(rootKeyPair)
@@ -87,12 +180,10 @@ class CertificateGenerator {
87180 intermediateKeyPair.private
88181 )
89182
90- // Store in KeyStore
91- saveToKeyStore(
92- keystorePath,
93- rootKeyPair, rootCert,
94- intermediateKeyPair, intermediateCert,
95- leafKeyPair, leafCert
183+ return linkedMapOf(
184+ leafCert to leafKeyPair,
185+ intermediateCert to intermediateKeyPair,
186+ rootCert to rootKeyPair,
96187 )
97188 }
98189
@@ -213,7 +304,7 @@ class CertificateGenerator {
213304 GeneralName (GeneralName .dNSName, " localhost" ),
214305 GeneralName (GeneralName .iPAddress, " 127.0.0.1" ),
215306 GeneralName (GeneralName .iPAddress, " ::1" )
216- )
307+ )
217308 )
218309
219310 addExtension(
@@ -230,14 +321,10 @@ class CertificateGenerator {
230321 .getCertificate(certBuilder.build(signer))
231322 }
232323
233- private fun saveToKeyStore (
324+ fun saveToKeyStore (
234325 keystorePath : Path ,
235- rootKeyPair : KeyPair ,
236- rootCert : X509Certificate ,
237- intermediateKeyPair : KeyPair ,
238- intermediateCert : X509Certificate ,
239326 leafKeyPair : KeyPair ,
240- leafCert : X509Certificate
327+ trustChain : Array < X509Certificate >
241328 ) {
242329 val password = " changeit" .toCharArray()
243330
@@ -246,28 +333,12 @@ class CertificateGenerator {
246333 load(null , password)
247334 }
248335
249- // Store root CA
250- // keyStore.setKeyEntry(
251- // "root",
252- // rootKeyPair.private,
253- // password,
254- // arrayOf(rootCert)
255- // )
256-
257- // // Store intermediate CA
258- // keyStore.setKeyEntry(
259- // "intermediate",
260- // intermediateKeyPair.private,
261- // password,
262- // arrayOf(intermediateCert, rootCert)
263- // )
264-
265336 // Store leaf certificate
266337 keyStore.setKeyEntry(
267338 " leaf" ,
268339 leafKeyPair.private,
269340 password,
270- arrayOf(leafCert, intermediateCert)
341+ trustChain
271342 )
272343
273344 // Save to file
0 commit comments