使用AWS Java SDK通过IoT凭证获取临时凭证时遇bad_certificate错误
已按AWS IoT凭证提供者流程完成配置,curl请求可正常获取临时凭证,但Java SDK实现时抛出javax.net.ssl.SSLHandshakeException: bad_certificate。已验证设备证书、私钥有效,且用keytool确认客户端PKCS12密钥库和信任库创建正常。尝试过以下操作仍未解决:
- 通过
System.setProperty配置客户端密钥库 - 使用
TlsKeyManagersProvider传入密钥管理器 - 切换为系统默认
cacerts信任库
相关配置与代码
- 生成PKCS12客户端密钥库命令
openssl pkcs12 -export -in certificate.pem -inkey private.key -out keystore.p12 -name exampleName -CAFile AmazonRootCA1.pem -caname root -certpbe PBE-SHA1-3DES -keypbe PBE-SHA1-3DES -macalg sha1
- 信任库生成代码
private void createTrustStore() { Path pemPath = Paths.get("/directory/AmazonRootCA1.pem"); try (final InputStream is = Files.newInputStream(pemPath)) { CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509"); X509Certificate cert = (X509Certificate) certificateFactory.generateCertificate(is); String alias = cert.getSubjectX500Principal().getName(); KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType()); keystore.load(null); keystore.setCertificateEntry(alias, cert); FileOutputStream out = new FileOutputStream("/directory/CAtrustStore.jks"); keystore.store(out, "password".toCharArray()); System.setProperty("javax.net.ssl.trustStore", "/directory/CAtrustStore.jks"); System.setProperty("javax.net.ssl.trustStorePassword", "password"); System.setProperty("javax.net.ssl.trustStoreType", "jks"); } catch (Exception e) { // 异常处理 } }
- 凭证请求代码
public void getCredentials() { HttpExecuteResponse response; Path clientKeyStore = Path.of("keystore.p12"); TlsKeyManagersProvider provider = FileStoreTlsKeyManagersProvider.create(clientKeyStore, "pkcs12", "password"); KeyManager[] keyManagers = provider.keyManagers(); SSLContext sslContext = SSLContext.getInstance("TLS"); sslContext.init(keyManagers, null, null); ConnectionSocketFactory socketFactory = new SdkTlsSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE); try (SdkHttpClient client = ApacheHttpClient.builder().socketFactory(socketFactory).build()) { SdkHttpRequest httpRequest = SdkHttpRequest.builder() .method(SdkHttpMethod.GET) .uri(new URI("https://<myPrefix>.credentials.iot.us-east-2.amazonaws.com/role-aliases/<myAlias>/credentials")) .putHeader("x-amzn-iot-thingname", "<myThingName>") .build(); HttpExecuteRequest request = HttpExecuteRequest.builder() .request(httpRequest) .build(); response = client.prepareRequest(request).call(); } catch (Exception e) { e.printStackTrace(); } Log.MainLog().info(response.httpResponse().toString()); Log.MainLog().info(response.responseBody().toString()); }
1. 确认客户端密钥库包含完整证书链
bad_certificate常见原因是客户端未提供完整证书链(设备证书 + AWS IoT根CA)。检查PKCS12密钥库内容:
keytool -list -v -keystore keystore.p12 -storetype pkcs12
输出中需看到exampleName别名下包含设备证书和AmazonRootCA1的完整链条。若缺失,重新生成PKCS12时确保-CAFile参数正确指向根CA文件。
2. 显式加载信任库到SSL上下文
当前代码中sslContext.init(keyManagers, null, null)未传入信任管理器,可能导致系统默认信任库未正确加载。修改代码显式加载信任库:
// 加载信任库 KeyStore trustStore = KeyStore.getInstance("JKS"); try (InputStream trustStoreStream = Files.newInputStream(Paths.get("/directory/CAtrustStore.jks"))) { trustStore.load(trustStoreStream, "password".toCharArray()); } TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); trustManagerFactory.init(trustStore); TrustManager[] trustManagers = trustManagerFactory.getTrustManagers(); // 初始化SSLContext时传入信任管理器 SSLContext sslContext = SSLContext.getInstance("TLSv1.2"); sslContext.init(keyManagers, trustManagers, new SecureRandom());
3. 验证设备证书与Thing Name的关联
- 登录AWS IoT控制台,检查
<myThingName>关联的证书列表,确认当前使用的设备证书已激活且关联正确。 - 检查IoT角色别名的权限策略,确保允许该证书对应的Thing调用凭证提供者。
4. 恢复主机名验证(禁用存在安全风险)
当前代码使用NoopHostnameVerifier.INSTANCE跳过主机名验证,Java SSL默认会验证服务器域名与证书SAN/CN字段是否匹配。建议移除该配置,使用默认验证器,同时确认请求URL域名与AWS IoT凭证提供者证书的SAN字段一致。
5. 指定兼容的TLS版本
AWS IoT服务要求使用TLS 1.2及以上版本,显式指定TLS版本避免兼容性问题:
SSLContext sslContext = SSLContext.getInstance("TLSv1.2");
同时检查Java环境jdk.tls.client.protocols系统属性是否包含TLSv1.2。
6. 启用SSL调试日志定位细节
添加JVM参数启用SSL调试日志,查看握手过程中的具体错误:
-Djavax.net.debug=ssl:handshake:verbose
日志会显示证书验证失败的核心原因(如证书过期、链不完整、根CA不被信任等)。
内容的提问来源于stack exchange,提问作者Joshua S

