|
9 | 9 | import org.slf4j.Logger; |
10 | 10 | import org.slf4j.LoggerFactory; |
11 | 11 |
|
| 12 | +import java.io.InputStream; |
| 13 | +import java.security.KeyStore; |
| 14 | +import java.security.cert.CertificateFactory; |
| 15 | +import java.security.cert.X509Certificate; |
| 16 | +import javax.net.ssl.SSLContext; |
| 17 | +import javax.net.ssl.TrustManager; |
| 18 | +import javax.net.ssl.TrustManagerFactory; |
| 19 | +import javax.net.ssl.X509TrustManager; |
| 20 | + |
12 | 21 | /** |
13 | 22 | * Entry point for the QTSurfer MCP server. |
14 | 23 | * |
@@ -65,6 +74,7 @@ public static void main(String[] args) { |
65 | 74 | * killing the JVM. |
66 | 75 | */ |
67 | 76 | static int run(String[] args) { |
| 77 | + injectBundledIntermediates(); |
68 | 78 | if (hasFlag(args, "--help") || hasFlag(args, "-h")) { |
69 | 79 | printUsage(); |
70 | 80 | return 0; |
@@ -118,6 +128,68 @@ static AuthenticatedClient authenticate(String apikey, String baseUrl) { |
118 | 128 | return QTSurfer.auth(apikey, opts); |
119 | 129 | } |
120 | 130 |
|
| 131 | + /** |
| 132 | + * GraalVM native images cannot AIA-chase at runtime. When the server omits an |
| 133 | + * intermediate CA from its TLS chain, the handshake fails with HTTP 0. |
| 134 | + * This method loads bundled intermediate certs and adds them as trust anchors |
| 135 | + * in a composite SSLContext set as the JVM default, so HttpClient picks them up. |
| 136 | + */ |
| 137 | + private static void injectBundledIntermediates() { |
| 138 | + try { |
| 139 | + KeyStore extras = KeyStore.getInstance(KeyStore.getDefaultType()); |
| 140 | + extras.load(null, null); |
| 141 | + int added = 0; |
| 142 | + for (String name : new String[]{"google-we1"}) { |
| 143 | + try (InputStream in = Main.class.getResourceAsStream("/tls/" + name + ".pem")) { |
| 144 | + if (in == null) continue; |
| 145 | + X509Certificate cert = (X509Certificate) |
| 146 | + CertificateFactory.getInstance("X.509").generateCertificate(in); |
| 147 | + extras.setCertificateEntry(name, cert); |
| 148 | + added++; |
| 149 | + } |
| 150 | + } |
| 151 | + if (added == 0) return; |
| 152 | + |
| 153 | + TrustManagerFactory platformTmf = TrustManagerFactory.getInstance( |
| 154 | + TrustManagerFactory.getDefaultAlgorithm()); |
| 155 | + platformTmf.init((KeyStore) null); |
| 156 | + X509TrustManager platformTm = (X509TrustManager) platformTmf.getTrustManagers()[0]; |
| 157 | + |
| 158 | + TrustManagerFactory extrasTmf = TrustManagerFactory.getInstance( |
| 159 | + TrustManagerFactory.getDefaultAlgorithm()); |
| 160 | + extrasTmf.init(extras); |
| 161 | + X509TrustManager extrasTm = (X509TrustManager) extrasTmf.getTrustManagers()[0]; |
| 162 | + |
| 163 | + SSLContext ctx = SSLContext.getInstance("TLS"); |
| 164 | + ctx.init(null, new TrustManager[]{new X509TrustManager() { |
| 165 | + @Override |
| 166 | + public void checkClientTrusted(X509Certificate[] chain, String authType) |
| 167 | + throws java.security.cert.CertificateException { |
| 168 | + platformTm.checkClientTrusted(chain, authType); |
| 169 | + } |
| 170 | + |
| 171 | + @Override |
| 172 | + public void checkServerTrusted(X509Certificate[] chain, String authType) |
| 173 | + throws java.security.cert.CertificateException { |
| 174 | + try { |
| 175 | + platformTm.checkServerTrusted(chain, authType); |
| 176 | + } catch (java.security.cert.CertificateException e) { |
| 177 | + extrasTm.checkServerTrusted(chain, authType); |
| 178 | + } |
| 179 | + } |
| 180 | + |
| 181 | + @Override |
| 182 | + public X509Certificate[] getAcceptedIssuers() { |
| 183 | + return platformTm.getAcceptedIssuers(); |
| 184 | + } |
| 185 | + }}, null); |
| 186 | + SSLContext.setDefault(ctx); |
| 187 | + log.debug("Injected {} bundled intermediate CA(s) into SSLContext", added); |
| 188 | + } catch (Exception e) { |
| 189 | + log.warn("Failed to inject bundled TLS intermediates: {}", e.getMessage()); |
| 190 | + } |
| 191 | + } |
| 192 | + |
121 | 193 | private static void printUsage() { |
122 | 194 | System.out.print( |
123 | 195 | "\n" |
|
0 commit comments