-
Notifications
You must be signed in to change notification settings - Fork 273
feat(lsp): respect IDE user proxy settings / forward trust store #5553
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 17 commits
Commits
Show all changes
19 commits
Select commit
Hold shift + click to select a range
82f7313
wip: feature(lsp): respect IDE user proxy settings / forward trust store
rli cf0ba75
Merge branch 'feature/q-lsp' into rli/lsp-proxy
rli b8dcb4d
hook up
rli 0f73bf6
Merge main into feature/q-lsp
aws-toolkit-automation 3328c9f
Merge main into feature/q-lsp
aws-toolkit-automation 8b08e5c
Merge main into feature/q-lsp
aws-toolkit-automation 92e93b4
Merge branch 'feature/q-lsp' into rli/lsp-proxy
rli 548fa25
configurable
rli ed5f0c8
Merge remote-tracking branch 'origin/main' into rli/lsp-proxy
rli 57ecb8b
wip
rli fb46994
Merge remote-tracking branch 'origin/main' into rli/lsp-proxy
rli c243301
wip
rli 37fd987
tst
rli a7e7872
Merge branch 'main' into rli/lsp-proxy
rli 2276592
lint
rli ecf1779
Merge branch 'main' into rli/lsp-proxy
rli 190427f
Merge branch 'main' into rli/lsp-proxy
rli 1644d96
build
rli 9fd2e21
Merge branch 'feature/q-lsp-chat' into rli/lsp-proxy
rli File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
144 changes: 144 additions & 0 deletions
144
...ains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/TrustChainUtil.kt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,144 @@ | ||
| // Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
| // SPDX-License-Identifier: Apache-2.0 | ||
|
|
||
| package software.aws.toolkits.jetbrains.services.amazonq.lsp | ||
|
|
||
| import com.intellij.util.io.DigestUtil | ||
| import com.intellij.util.net.JdkProxyProvider | ||
| import com.intellij.util.net.ssl.CertificateManager | ||
| import org.apache.http.client.methods.RequestBuilder | ||
| import org.apache.http.conn.ssl.DefaultHostnameVerifier | ||
| import org.apache.http.impl.client.HttpClientBuilder | ||
| import org.apache.http.impl.client.SystemDefaultCredentialsProvider | ||
| import org.apache.http.impl.conn.SystemDefaultRoutePlanner | ||
| import org.jetbrains.annotations.TestOnly | ||
| import software.aws.toolkits.core.utils.getLogger | ||
| import software.aws.toolkits.core.utils.warn | ||
| import java.net.URI | ||
| import java.security.KeyStore | ||
| import java.security.cert.CertPathBuilder | ||
| import java.security.cert.CertStore | ||
| import java.security.cert.Certificate | ||
| import java.security.cert.CollectionCertStoreParameters | ||
| import java.security.cert.PKIXBuilderParameters | ||
| import java.security.cert.PKIXCertPathBuilderResult | ||
| import java.security.cert.X509CertSelector | ||
| import java.security.cert.X509Certificate | ||
| import java.util.Base64 | ||
| import kotlin.collections.ifEmpty | ||
|
|
||
| object TrustChainUtil { | ||
|
||
| private val LOG = getLogger<TrustChainUtil>() | ||
|
|
||
| @TestOnly | ||
| fun resolveTrustChain(certs: Collection<X509Certificate>, trustAnchors: Collection<X509Certificate>) = resolveTrustChain( | ||
| certs, | ||
| keystoreFromCertificates(trustAnchors) | ||
| ) | ||
|
|
||
| /** | ||
| * Build and validate the complete certificate chain | ||
| * @param certs The end-entity certificate | ||
| * @param trustAnchors The truststore containing trusted CA certificates | ||
| * @return The complete certificate chain | ||
| */ | ||
| fun resolveTrustChain(certs: Collection<X509Certificate>, trustAnchors: KeyStore): List<X509Certificate> { | ||
|
||
| try { | ||
| // Create the selector for the certificate | ||
| val selector = X509CertSelector() | ||
| selector.certificate = certs.first() | ||
|
|
||
| // Create the parameters for path validation | ||
| val pkixParams = PKIXBuilderParameters(trustAnchors, selector) | ||
|
|
||
| // Disable CRL checking since we just want to build the path | ||
| pkixParams.isRevocationEnabled = false | ||
|
|
||
| // Create a CertStore containing the certificate we want to validate | ||
| val ccsp = CollectionCertStoreParameters(certs) | ||
| val certStore = CertStore.getInstance("Collection", ccsp) | ||
| pkixParams.addCertStore(certStore) | ||
|
|
||
| // Get the certification path | ||
| val builder = CertPathBuilder.getInstance("PKIX") | ||
| val result = builder.build(pkixParams) as PKIXCertPathBuilderResult | ||
| val certPath = result.certPath | ||
| val chain = (certPath.certificates as List<X509Certificate>).toMutableList() | ||
|
|
||
| // Add the trust anchor (root CA) to complete the chain | ||
| val trustAnchorCert = result.trustAnchor.trustedCert | ||
| if (trustAnchorCert != null) { | ||
| chain.add(trustAnchorCert) | ||
| } | ||
|
|
||
| return chain | ||
| } catch (e: Exception) { | ||
| // Java PKIX is happy with leaf cert in certification path, but Node.JS will not respect in NODE_CA_CERTS | ||
| LOG.warn(e) { "Could not build trust anchor via CertPathBuilder? maybe user accepted leaf cert but not intermediate" } | ||
|
|
||
| return emptyList() | ||
| } | ||
| } | ||
|
|
||
| fun getTrustChain(uri: URI): List<X509Certificate> { | ||
|
||
| val proxyProvider = JdkProxyProvider.getInstance() | ||
| var peerCerts: Array<Certificate> = emptyArray() | ||
| val verifierDelegate = DefaultHostnameVerifier() | ||
| val client = HttpClientBuilder.create() | ||
| .setRoutePlanner(SystemDefaultRoutePlanner(proxyProvider.proxySelector)) | ||
| .setDefaultCredentialsProvider(SystemDefaultCredentialsProvider()) | ||
| .setSSLHostnameVerifier { hostname, sslSession -> | ||
| peerCerts = sslSession.peerCertificates | ||
|
|
||
| verifierDelegate.verify(hostname, sslSession) | ||
| } | ||
| // prompt user via modal to accept certificate if needed; otherwise need to prompt separately prior to launching flare | ||
| .setSSLContext(CertificateManager.getInstance().sslContext) | ||
|
|
||
| // client request will fail if user did not accept cert | ||
| client.build().use { it.execute(RequestBuilder.options(uri).build()) } | ||
|
|
||
| val certificates = peerCerts as Array<X509Certificate> | ||
|
|
||
| // java default + custom system | ||
| // excluding leaf cert for case where user has both leaf and issuing CA as trusted roots | ||
| val allAccepted = CertificateManager.getInstance().trustManager.acceptedIssuers.toSet() - certificates.first() | ||
| val ks = keystoreFromCertificates(allAccepted) | ||
|
|
||
| // if this throws then there is a bug because it passed PKIX validation in apache client | ||
| val trustChain = try { | ||
| resolveTrustChain(certificates.toList(), ks) | ||
| } catch (e: Exception) { | ||
| // Java PKIX is happy with leaf cert in certification path, but Node.JS will not respect in NODE_CA_CERTS | ||
| LOG.warn(e) { "Passed Apache PKIX verification but could not build trust anchor via CertPathBuilder? maybe user accepted leaf cert but not root" } | ||
| emptyList() | ||
| } | ||
|
|
||
| // if trust chain is empty, then somehow user only trusts the leaf cert??? | ||
| return trustChain.ifEmpty { | ||
| // so return the served certificate chain from the server and hope that works | ||
| certificates.toList() | ||
| } | ||
| } | ||
|
|
||
| fun certsToPem(certs: List<X509Certificate>): String = | ||
| buildList { | ||
| certs.forEach { | ||
| add("-----BEGIN CERTIFICATE-----") | ||
| add(Base64.getMimeEncoder(64, System.lineSeparator().toByteArray()).encodeToString(it.encoded)) | ||
| add("-----END CERTIFICATE-----") | ||
| } | ||
| }.joinToString(separator = System.lineSeparator()) | ||
|
|
||
| private fun keystoreFromCertificates(certificates: Collection<X509Certificate>): KeyStore { | ||
| val ks = KeyStore.getInstance(KeyStore.getDefaultType()) | ||
| ks.load(null, null) | ||
| certificates.forEachIndexed { index, cert -> | ||
| ks.setCertificateEntry( | ||
| cert.subjectX500Principal.toString() + "-" + DigestUtil.sha256Hex(cert.encoded), | ||
| cert | ||
| ) | ||
| } | ||
| return ks | ||
| } | ||
| } | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: slowness added to which process?