Переведите NodeJS запрос с сертификатом и ключом на Java - PullRequest
0 голосов
/ 28 марта 2020

ОБНОВЛЕНИЕ:

Вот ссылка на официальную документацию 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();

Кроме того, я хочу знать, есть ли более простая альтернатива.

Ответы [ 2 ]

0 голосов
/ 31 марта 2020

Проблема заключалась в том, что вы не можете использовать файлы .cert и .key в Java, потому что KeyStore не знает, как работать с этими файлами.

Чтобы исправить это, вам нужно конвертировать ваши файлы в файл .p12 (PKCS # 12). Для этого я использовал KeyStore Explorer .

После того, как я преобразовал файл, я смог сделать звонок. Код решения здесь:

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();
    //...
}
0 голосов
/ 29 марта 2020

TLDR: ваш ключевой файл помечен неверно.

В вашем файле privatekey есть метки PEM, утверждающие, что это RSA PRIVATE KEY, что в соответствии со стандартом де-факто должно содержать данные в кодированном виде форма закрытого ключа в формате PKCS1. Однако данные в вашем файле на самом деле представляют собой PrivateKeyInfo в формате PKCS8, который для rfc7468 должен иметь метки PRIVATE KEY ( NO RSA).

Если вы исправите метки вашего файла на PRIVATE KEY без RSA, 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 в ваше приложение, вы разрешаете всем, у кого есть копия приложения, заменять или выдавать себя за ваш сервер (ы?) И красть, изменять или уничтожать все ваши данные - и публикация его в стеке распространяется на всех в мире. Ваш код nodejs, напротив, настраивает этот ключ и сертификат для использования в качестве клиентского ключа и сертификата, а не в качестве ЦС или якоря вообще - хотя сервер, указанный вами в URL-адресе, не отображается для запроса клиентский ключ и сертификат вообще, и он использует сертификат с нормальным root (Digicert), который не требует каких-либо изменений по умолчанию Java, и имеет правильное имя сервера, которое также не требует отключения проверки имени хоста.

...