Skip to content

Commit 57ecb8b

Browse files
committed
wip
1 parent ed5f0c8 commit 57ecb8b

File tree

2 files changed

+281
-1
lines changed
  • plugins/amazonq/shared/jetbrains-community

2 files changed

+281
-1
lines changed

plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/TrustChainUtil.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,8 @@ object TrustChainUtil {
9494
val trustChain = try {
9595
resolveTrustChain(certificates.toList(), ks)
9696
} catch (e: Exception) {
97-
LOG.warn(e) { "Passed Apache PKIX verification but could not build trust anchor via CertPathBuilder" }
97+
// Java PKIX is happy with leaf cert in certification path, but Node.JS will not respect in NODE_CA_CERTS
98+
LOG.warn(e) { "Passed Apache PKIX verification but could not build trust anchor via CertPathBuilder? maybe user accepted leaf cert but not intermediate" }
9899
emptyList()
99100
}
100101

Lines changed: 279 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,279 @@
1+
// Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package software.aws.toolkits.jetbrains.services.amazonq.lsp
5+
6+
import com.github.tomakehurst.wiremock.common.Slf4jNotifier
7+
import com.github.tomakehurst.wiremock.common.ssl.KeyStoreSettings
8+
import com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig
9+
import com.github.tomakehurst.wiremock.junit5.WireMockExtension
10+
import com.intellij.testFramework.ApplicationExtension
11+
import org.junit.jupiter.api.Test
12+
import org.junit.jupiter.api.extension.ExtendWith
13+
import org.junit.jupiter.api.extension.RegisterExtension
14+
import java.net.URI
15+
import java.security.cert.X509Certificate
16+
17+
import java.math.BigInteger
18+
import java.security.KeyPairGenerator
19+
import java.security.KeyStore
20+
import java.util.*
21+
import org.bouncycastle.asn1.x500.X500Name
22+
import org.bouncycastle.asn1.x509.*
23+
import org.bouncycastle.cert.X509v3CertificateBuilder
24+
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter
25+
import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder
26+
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder
27+
import software.aws.toolkits.core.utils.outputStream
28+
import java.nio.file.Files
29+
import java.nio.file.Path
30+
import java.security.KeyPair
31+
import java.security.PrivateKey
32+
import java.time.Instant
33+
import java.time.temporal.ChronoUnit
34+
35+
@ExtendWith(ApplicationExtension::class)
36+
class TrustChainUtilTest {
37+
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()
53+
}
54+
55+
@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())
60+
}
61+
}
62+
63+
class CertificateGenerator {
64+
companion object {
65+
private const val KEY_ALGORITHM = "RSA"
66+
private const val SIGNATURE_ALGORITHM = "SHA256withRSA"
67+
private const val KEY_SIZE = 4096
68+
69+
fun generateCertificateChain(keystorePath: Path) {
70+
// Generate Root CA
71+
val rootKeyPair = generateKeyPair()
72+
val rootCert = generateRootCertificate(rootKeyPair)
73+
74+
// Generate Intermediate CA
75+
val intermediateKeyPair = generateKeyPair()
76+
val intermediateCert = generateIntermediateCertificate(
77+
intermediateKeyPair,
78+
rootCert,
79+
rootKeyPair.private
80+
)
81+
82+
// Generate Leaf Certificate
83+
val leafKeyPair = generateKeyPair()
84+
val leafCert = generateLeafCertificate(
85+
leafKeyPair,
86+
intermediateCert,
87+
intermediateKeyPair.private
88+
)
89+
90+
// Store in KeyStore
91+
saveToKeyStore(
92+
keystorePath,
93+
rootKeyPair, rootCert,
94+
intermediateKeyPair, intermediateCert,
95+
leafKeyPair, leafCert
96+
)
97+
}
98+
99+
private fun generateKeyPair(): KeyPair =
100+
KeyPairGenerator.getInstance(KEY_ALGORITHM).apply {
101+
initialize(KEY_SIZE)
102+
}.generateKeyPair()
103+
104+
private fun generateRootCertificate(keyPair: KeyPair): X509Certificate {
105+
val name = X500Name("CN=Root CA,O=My Organization,C=US")
106+
107+
val now = Instant.now()
108+
val startDate = Date.from(now)
109+
val endDate = Date.from(now.plus(3650, ChronoUnit.DAYS)) // 10 years validity
110+
111+
val certBuilder = JcaX509v3CertificateBuilder(
112+
name, // issuer
113+
BigInteger.valueOf(System.currentTimeMillis()),
114+
startDate,
115+
endDate,
116+
name, // subject (same as issuer for root CA)
117+
keyPair.public
118+
).apply {
119+
// Add Extensions
120+
addExtension(
121+
Extension.basicConstraints,
122+
true,
123+
BasicConstraints(true)
124+
)
125+
addExtension(
126+
Extension.keyUsage,
127+
true,
128+
KeyUsage(KeyUsage.keyCertSign or KeyUsage.cRLSign)
129+
)
130+
}
131+
132+
// Sign the certificate
133+
val signer = JcaContentSignerBuilder(SIGNATURE_ALGORITHM)
134+
.build(keyPair.private)
135+
136+
return JcaX509CertificateConverter()
137+
.getCertificate(certBuilder.build(signer))
138+
}
139+
140+
private fun generateIntermediateCertificate(
141+
intermediateKeyPair: KeyPair,
142+
issuerCert: X509Certificate,
143+
issuerPrivateKey: PrivateKey
144+
): X509Certificate {
145+
val subjectName = X500Name("CN=Intermediate CA,O=My Organization,C=US")
146+
147+
val now = Instant.now()
148+
val startDate = Date.from(now)
149+
val endDate = Date.from(now.plus(1825, ChronoUnit.DAYS)) // 5 years validity
150+
151+
val certBuilder = JcaX509v3CertificateBuilder(
152+
issuerCert,
153+
BigInteger.valueOf(System.currentTimeMillis()),
154+
startDate,
155+
endDate,
156+
subjectName,
157+
intermediateKeyPair.public
158+
).apply {
159+
// Add Extensions
160+
addExtension(
161+
Extension.basicConstraints,
162+
true,
163+
BasicConstraints(true)
164+
)
165+
addExtension(
166+
Extension.keyUsage,
167+
true,
168+
KeyUsage(KeyUsage.keyCertSign or KeyUsage.cRLSign)
169+
)
170+
}
171+
172+
val signer = JcaContentSignerBuilder(SIGNATURE_ALGORITHM)
173+
.build(issuerPrivateKey)
174+
175+
return JcaX509CertificateConverter()
176+
.getCertificate(certBuilder.build(signer))
177+
}
178+
179+
private fun generateLeafCertificate(
180+
leafKeyPair: KeyPair,
181+
issuerCert: X509Certificate,
182+
issuerPrivateKey: PrivateKey
183+
): X509Certificate {
184+
val subjectName = X500Name("CN=localhost,O=My Organization,C=US")
185+
186+
val now = Instant.now()
187+
val startDate = Date.from(now)
188+
val endDate = Date.from(now.plus(365, ChronoUnit.DAYS)) // 1 year validity
189+
190+
val certBuilder = JcaX509v3CertificateBuilder(
191+
issuerCert,
192+
BigInteger.valueOf(System.currentTimeMillis()),
193+
startDate,
194+
endDate,
195+
subjectName,
196+
leafKeyPair.public
197+
).apply {
198+
// Add Extensions
199+
addExtension(
200+
Extension.basicConstraints,
201+
true,
202+
BasicConstraints(false)
203+
)
204+
addExtension(
205+
Extension.keyUsage,
206+
true,
207+
KeyUsage(KeyUsage.digitalSignature or KeyUsage.keyEncipherment)
208+
)
209+
210+
// Add Subject Alternative Names (SAN)
211+
val subjectAltNames = GeneralNames(
212+
arrayOf(
213+
GeneralName(GeneralName.dNSName, "localhost"),
214+
GeneralName(GeneralName.iPAddress, "127.0.0.1"),
215+
GeneralName(GeneralName.iPAddress, "::1")
216+
)
217+
)
218+
219+
addExtension(
220+
Extension.subjectAlternativeName,
221+
false,
222+
subjectAltNames
223+
)
224+
}
225+
226+
val signer = JcaContentSignerBuilder(SIGNATURE_ALGORITHM)
227+
.build(issuerPrivateKey)
228+
229+
return JcaX509CertificateConverter()
230+
.getCertificate(certBuilder.build(signer))
231+
}
232+
233+
private fun saveToKeyStore(
234+
keystorePath: Path,
235+
rootKeyPair: KeyPair,
236+
rootCert: X509Certificate,
237+
intermediateKeyPair: KeyPair,
238+
intermediateCert: X509Certificate,
239+
leafKeyPair: KeyPair,
240+
leafCert: X509Certificate
241+
) {
242+
val password = "changeit".toCharArray()
243+
244+
// Create KeyStore
245+
val keyStore = KeyStore.getInstance("JKS").apply {
246+
load(null, password)
247+
}
248+
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+
265+
// Store leaf certificate
266+
keyStore.setKeyEntry(
267+
"leaf",
268+
leafKeyPair.private,
269+
password,
270+
arrayOf(leafCert, intermediateCert)
271+
)
272+
273+
// Save to file
274+
keystorePath.outputStream().use { fos ->
275+
keyStore.store(fos, password)
276+
}
277+
}
278+
}
279+
}

0 commit comments

Comments
 (0)