将带有cert和key的NodeJS请求转换为Java



更新:

这是API官方文档的链接。

这是我从NodeJS发出请求的视频,这是我实现的java客户端应用程序。


我在NodeJS中有一个请求,现在我想在Java中为我的Android应用程序实现相同的请求。NodeJS请求如下所示:

var options = {
method: 'GET',
url: 'https://webapi.developers.erstegroup.com/api/bcr/sandbox/v1/aisp/v1/accounts',
cert: "-----BEGIN CERTIFICATE-----....-----END CERTIFICATE-----",
key: "-----BEGIN RSA PRIVATE KEY-----...-----END RSA PRIVATE KEY-----",
headers:
{
'x-request-id': '30fb2676-8c2e-11e9-b683-526af7764f64',
'web-api-key': '#########',
'Accept': 'application/json'
}
};
request(options, function (error, response, body) {
console.log(body);
});

我的问题是,我不知道如何包括证书和私钥来提出请求。我找到了关于如何在Java中读取证书和密钥的答案,但我不知道如何配置SSLContext来使用证书和密钥。

目前,我尝试了下一个解决方案,但没有成功。我遇到的第一个问题是,当我解析密钥时出错:

错误消息:org.bouncycastle.openssl.PEMException: malformed sequence in RSA private key

用于读取密钥的方法:

private static PrivateKey readPrivateKey(String filename) throws Exception {
PEMParser pemParser = new PEMParser(new BufferedReader(new InputStreamReader(Main.class.getClassLoader().getResourceAsStream(filename))));
JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider("BC");
PEMKeyPair pemKeyPair = (PEMKeyPair) pemParser.readObject();
KeyPair kp = converter.getKeyPair(pemKeyPair);
return kp.getPrivate();
}

私钥:

-----BEGIN RSA PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDJZunh6Nj5aBtb
hJJ9eaSDjDiiERBOZfVrmEwz9Ea5ldLAf8NUy+t71etzGeHtCKyTuSlhj1Clhla+
iG/r1uz25H3T6wUQbmw1+pFsaSovkamQaKy+2GJSPCx66li6z2JBv0I66DtoGl6Q
Xcy5JO2sBjZaO4m11bcFFVkvo9lh2QCC2x68w8bHeBuPUMnU6rupVQfgPPWMH+Wq
qaPgxoQr5KmQ03ItY2/TBqoX6xTTbL6+B8OMbX41Lxah+g+5XPOlDoC64HiBO2T+
FL62W+51ehUCORuuUt6/AYzXcCHSu1FXsk25KeObe9Na2AfobGNL83I68Bl0K2Wt
jbEtHs1FAgMBAAECggEAb7K3Bga4x2IYwiH9iL99IUQUaLXkAEcF3N2DbdENpIHW
d9KkB5RtDqouwhBZv7du1yL7M1Njm9mspFFRGVCC7c79hhmzHlDPjQRhwOl2bxlv
HFsha1rg9NDQrn7oJPs9eE9VsQv5Xpw5VAHht9EmS6DKZjLdBk74CUa0xvotZtkS
UrvRLOWhedsS0Ckf3vDfqU5NZBEecEj1vLGnD1ET6znRhag2VqUYS83fKUR5UpLq
X4PqMfYucF40ddd+L/iMeAnGmKukYEfew1R3ez5rKSS+cyJkoAKtL1WR0nFSDskw
zFQaf6PNIUIL7CDnOdUlyiRYx6a5y6NzJcQyI0bn5QKBgQDhUPck4ekVT8z29Llf
kkXWoF15l7KNiiq2DZfWlH2pax4G3NBUimb62fHcSQVovD0aLCJOF8N0n6vv5YbF
MIURRWXRTNxO2S16xUpUMD2Ospv50UpNIMAJlgnkTt/DTsd6MYQx2j+qyf4wPvWO
QSOQNpdebd/59LWbAye5WROuJwKBgQDk1D4sEWti9dR0LTJS0B+FHLPPJhpNg+cD
zqFEXvSICQjAhyJ3Fir/u3HhX4966+dhODaDphAOQcG+4iyXUQVEh+qQJ5p+MJQU
ue0yZgQvPIo5a+gnFyzEmCOtaENBqJqK1tbCklFZtbJswVEtlqjq+qMAzjNOzf2F
6krA9VC4swKBgQCIrJ5eJxNGNDP2kZho2se2W2yYN2a96NPjvvcd2NEpFasPKp7M
yW+SNuY5Y6n+UEEYQTFGAbA0bC7VxHst3jK5uUj73w28Xozx7f8adnDAwKNQtJ3H
j1gt+G9jqFyfkof6HVM9ElCQfxrLlUVK10SFVDgZtbipXMFUmGNeUSRY/QKBgQCW
a/Lmsxi9d84OBLvk9k0SCrkkfe6icAfHV+ho8maamh23ud1tHRRtAYIt3cyKyFJU
dUhYqCw7wvwih7k6SxdEYnhOBMqpEzP0n/gNvkQX7RsL/iQgtjpGjaA+WKCFo9jb
VbjdNKPnbep5VWcQqc4mkVXfrKzLq9txUX+McnZ6wwKBgAy2RVQ/ja3mvVUiZ3ZN
U4Y+nObKV6Z9JoMC4VNbbwufVPSoj/j20jj8uB29WupQ/BG2W0jHS3GfjrwH9IiN
7Pm+Nco8E/Yywh7YYNFJOxNesc7kB7RJuhfTabif9Ea+LqF6CQQ10rHzzT3WlY5p
rAJmu7k+JlKn5Aad+KQF4RXJ
-----END RSA PRIVATE KEY-----

代码的其余部分:

String keyStoreType = KeyStore.getDefaultType();
KeyStore keyStore = KeyStore.getInstance(keyStoreType);
keyStore.load(null, null);
final X509Certificate cert = readCertificate("public-key-bcr.cer");
keyStore.setCertificateEntry("key-bcr", cert);
keyStore.setKeyEntry("key-bcr", readPrivateKey("private-key-bcr.key"), "".toCharArray(), new Certificate[]{cert});
// Create a TrustManager that trusts the CAs in our KeyStore
String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
tmf.init(keyStore);
final SSLContext sslContext = SSLContext.getInstance("SSL");
sslContext.init(null, tmf.getTrustManagers(), null);
final OkHttpClient.Builder builder = new OkHttpClient.Builder()
.sslSocketFactory(sslContext.getSocketFactory())
.hostnameVerifier(new HostnameVerifier() {
@Override
public boolean verify(String hostname, SSLSession session) {
return true;
}
});
OkHttpClient client = builder.build();

此外,我想知道是否还有更简单的选择。

TLDR:您的密钥文件标记错误

你的私钥文件有PEM标签,声称它是RSA PRIVATE KEY,根据事实上的标准,它应该包含PKCS1格式私钥的编码形式的数据。但是,文件中的数据实际上是PKCS8格式的PrivateKeyInfo,根据rfc7468,它应该具有标签PRIVATE KEY(NORSA(。

如果您将文件的标签更正为不带RSAPRIVATE KEY,BouncyCastle可以读取它,但您需要更改使用的类型和方法:

PEMParser pemParser = new PEMParser(/* appropriate Reader */);
JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider("BC");
// you don't actually need to set the provider, the default provider(s) work fine.
PrivateKeyInfo privkey = (PrivateKeyInfo) pemParser.readObject();
return /*PrivateKey*/ converter.getPrivateKey(privkey); 

然而,您不需要BouncyCastle这个(未加密(格式可以通过Java加密直接读取:

String pem = /* read all chars from file/resource/whatever, or read all bytes and convert to String */;
byte[] der = Base64.getDecoder().decode( pem.replaceAll("-----(BEGIN|END) PRIVATE KEY-----r?n", "") );
KeyFactory fact = KeyFactory.getInstance("RSA");
return /*PrivateKey*/ fact.generatePrivate(new PKCS8EncodedKeySpec(der));

最后,您的安全方案没有意义。你实际上并没有用这个钥匙做任何事情;您似乎只是将证书用作CA证书(信任锚点(。如果这个证书确实是CA证书,通过在你的应用程序中包含CA的私钥,你就允许所有拥有该应用程序副本的人替换或模拟你的服务器,窃取、修改或销毁你的所有数据,并将其发布在Stack上,这将扩展到世界上的每个人。相反,您的nodejs代码配置了这个密钥&要用作客户端密钥的证书&cert,而不是作为CA或锚点——尽管您在URL中指定的服务器似乎不会请求客户端密钥&cert,并且它使用普通根目录下的cert(Digicert(,不需要对Java默认值进行任何修改,并且具有正确的服务rname,也不需要禁用主机名验证。

问题是您不能在Java中使用.cert和.key文件,因为KeyStore不知道如何处理这些文件。

要修复它,您需要将文件转换为.p12(PKCS#12(文件。为此,我使用了KeyStore资源管理器。

转换文件后,我可以打电话了。解决方案代码在这里:

private static SSLSocketFactory getFactory(String fileName, String password) {
KeyStore keyStore = KeyStore.getInstance("PKCS12");
keyStore.load(Main.class.getClassLoader().getResourceAsStream(fileName), password.toCharArray());
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("SunX509");
keyManagerFactory.init(keyStore, password.toCharArray());
SSLContext context = SSLContext.getInstance("SSL");
context.init(
keyManagerFactory.getKeyManagers(),
null,
new SecureRandom()
);
return context.getSocketFactory();
}
public static void main(String[] args) throws Exception {
final OkHttpClient.Builder builder = new OkHttpClient.Builder()
.sslSocketFactory(getFactory("converted_file.p12", "1234"))
.hostnameVerifier((hostname, session) -> true);
OkHttpClient client = builder.build();
//...
}

最新更新