Skip to content

Commit 82f7313

Browse files
committed
wip: feature(lsp): respect IDE user proxy settings / forward trust store
1 parent 5cc7ae1 commit 82f7313

File tree

1 file changed

+119
-0
lines changed
  • plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp

1 file changed

+119
-0
lines changed
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
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.intellij.util.io.DigestUtil
7+
import com.intellij.util.net.JdkProxyProvider
8+
import com.intellij.util.net.ssl.CertificateManager
9+
import org.apache.http.client.methods.RequestBuilder
10+
import org.apache.http.conn.ssl.DefaultHostnameVerifier
11+
import org.apache.http.impl.client.HttpClientBuilder
12+
import org.apache.http.impl.client.SystemDefaultCredentialsProvider
13+
import org.apache.http.impl.conn.SystemDefaultRoutePlanner
14+
import software.aws.toolkits.core.utils.getLogger
15+
import software.aws.toolkits.core.utils.warn
16+
import java.net.URI
17+
import java.security.KeyStore
18+
import java.security.cert.CertPathBuilder
19+
import java.security.cert.CertStore
20+
import java.security.cert.Certificate
21+
import java.security.cert.CollectionCertStoreParameters
22+
import java.security.cert.PKIXBuilderParameters
23+
import java.security.cert.PKIXCertPathBuilderResult
24+
import java.security.cert.X509CertSelector
25+
import java.security.cert.X509Certificate
26+
import kotlin.collections.ifEmpty
27+
28+
object TrustChainUtil {
29+
private val LOG = getLogger<TrustChainUtil>()
30+
31+
/**
32+
* Build and validate the complete certificate chain
33+
* @param certs The end-entity certificate
34+
* @param trustAnchors The truststore containing trusted CA certificates
35+
* @return The complete certificate chain
36+
*/
37+
fun resolveTrustChain(certs: Collection<X509Certificate>, trustAnchors: KeyStore): List<X509Certificate> {
38+
// Create the selector for the certificate
39+
val selector = X509CertSelector()
40+
selector.certificate = certs.first()
41+
42+
// Create the parameters for path validation
43+
val pkixParams = PKIXBuilderParameters(trustAnchors, selector)
44+
45+
// Disable CRL checking since we just want to build the path
46+
pkixParams.isRevocationEnabled = false
47+
48+
// Create a CertStore containing the certificate we want to validate
49+
val ccsp = CollectionCertStoreParameters(certs)
50+
val certStore = CertStore.getInstance("Collection", ccsp)
51+
pkixParams.addCertStore(certStore)
52+
53+
// Get the certification path
54+
val builder = CertPathBuilder.getInstance("PKIX")
55+
val result = builder.build(pkixParams) as PKIXCertPathBuilderResult
56+
val certPath = result.certPath
57+
val chain = (certPath.certificates as List<X509Certificate>).toMutableList()
58+
59+
// Add the trust anchor (root CA) to complete the chain
60+
val trustAnchorCert = result.trustAnchor.trustedCert
61+
if (trustAnchorCert != null) {
62+
chain.add(trustAnchorCert)
63+
}
64+
65+
return chain
66+
}
67+
68+
fun getTrustChain(uri: URI): List<X509Certificate> {
69+
val proxyProvider = JdkProxyProvider.getInstance()
70+
var peerCerts: Array<Certificate> = emptyArray()
71+
val verifierDelegate = DefaultHostnameVerifier()
72+
val client = HttpClientBuilder.create()
73+
.setRoutePlanner(SystemDefaultRoutePlanner(proxyProvider.proxySelector))
74+
.setDefaultCredentialsProvider(SystemDefaultCredentialsProvider())
75+
.setSSLHostnameVerifier { hostname, sslSession ->
76+
peerCerts = sslSession.peerCertificates
77+
78+
verifierDelegate.verify(hostname, sslSession)
79+
}
80+
// prompt user via modal to accept certificate if needed; otherwise need to prompt separately prior to launching flare
81+
.setSSLContext(CertificateManager.getInstance().sslContext)
82+
83+
// client request will fail if user did not accept cert
84+
client.build().execute(RequestBuilder.options(uri).build())
85+
86+
val certificates = peerCerts as Array<X509Certificate>
87+
88+
// java default + custom system
89+
// excluding leaf cert for case where user has both leaf and issuing CA as trusted roots
90+
val allAccepted = CertificateManager.getInstance().trustManager.acceptedIssuers.toSet() - certificates.first()
91+
val ks = keystoreFromCertificates(allAccepted)
92+
93+
// if this throws then there is a bug because it passed PKIX validation in apache client
94+
val trustChain = try {
95+
resolveTrustChain(certificates.toList(), ks)
96+
} catch (e: Exception) {
97+
LOG.warn(e) { "Passed Apache PKIX verification but could not build trust anchor via CertPathBuilder" }
98+
emptyList()
99+
}
100+
101+
// if trust chain is empty, then somehow user only trusts the leaf cert???
102+
return trustChain.ifEmpty {
103+
// so return the served certificate chain from the server and hope that works
104+
certificates.toList()
105+
}
106+
}
107+
108+
private fun keystoreFromCertificates(certificates: Collection<X509Certificate>): KeyStore {
109+
val ks = KeyStore.getInstance(KeyStore.getDefaultType())
110+
ks.load(null, null)
111+
certificates.forEachIndexed { index, cert ->
112+
ks.setCertificateEntry(
113+
cert.getSubjectX500Principal().toString() + "-" + DigestUtil.sha256Hex(cert.encoded),
114+
cert
115+
)
116+
}
117+
return ks
118+
}
119+
}

0 commit comments

Comments
 (0)