0

UPDATE:

Here is the link to the official API documentation.

And here is a video with the requests I made from NodeJS and here is the java client app that I implemented.


I have a request in NodeJS and now I want to implement the same request in Java for my Android app. The NodeJS request looks like this:

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);
});

My problem is that I don't know how to include that certificate and private key to make the request. I found this answer regarding how to read the certificate and the key in Java, but I don't know how to configure the SSLContext to use the certificate and also the key.

Currently, I tried the next solution, but don't work. The first problem I have is that I get an error when I parse the key:

The error message: org.bouncycastle.openssl.PEMException: malformed sequence in RSA private key

The method used to read the 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();
  }

The private key:

-----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-----

The rest of the code:

    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();

Also, I want to know if there is an easier alternative.

GameZone RO
  • 63
  • 1
  • 8

2 Answers2

0

TLDR: your keyfile is labelled wrong.

Your privatekey file has PEM labels claiming it is RSA PRIVATE KEY which according to the de-facto standard should contain data which is the encoded form of a PKCS1-format privatekey. However, the data in your file is actually a PKCS8-format PrivateKeyInfo, which per rfc7468 should have labels PRIVATE KEY (NO RSA).

If you correct your file's labels to PRIVATE KEY with no RSA, BouncyCastle can read it, but you need to change the type and method used:

    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); 

However, you don't need BouncyCastle; this (unencrypted) format can be read by Java crypto directly:

    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));

Finally, your security scheme doesn't make sense. You aren't actually using this key for anything; you seem to be using the cert, only, as a CA cert (trust anchor). If this cert is indeed a CA cert, by including the CA's privatekey in your app you have allowed everyone who has a copy of the app to replace or impersonate your server(s?) and steal, modify, or destroy all your data -- and publishing it on Stack extends this to everyone in the world. Your nodejs code, in contrast, configures this key&cert to be used as a client key&cert and not as a CA or anchor at all -- although the server you specify in your URL doesn't appear to request a client key&cert at all, and it uses a cert under a normal root (Digicert) that doesn't require any modification to the Java defaults, and has a correct serve rname which also doesn't require disabling hostname verification.

Community
  • 1
  • 1
dave_thompson_085
  • 34,712
  • 6
  • 50
  • 70
  • First of all, thanks for your answer. I think the certificate and the private key are used for SSL stuff. I don't think is ok to alter the content of the private key. I made a video of using the cert and private key files in requests using NodeJS. Can you review the next video [1] and answer me back? Thanks. https://drive.google.com/open?id=1P5U6gw3PZ_4uPk-cfpWrpqVQVAugidtj – GameZone RO Mar 30 '20 at 10:16
  • NodeJS is able to read the privatekey file because it uses a part of the OpenSSL library that ignores the PEM labels and handles both algorithm-specific (including PKCS1) and PKCS8 key data. I didn't suggest altering the content at all (which would be a bad idea), only correcting the labels. I can't watch videos on my computer. – dave_thompson_085 Mar 31 '20 at 10:12
0

The problem was that you are not able to use .cert and .key files in Java because the KeyStore doesn't know to work with these files.

To fix it you need to convert your files to .p12 (PKCS#12) file. To do this I used KeyStore Explorer.

After I converted the file I was able to make the call. The solution code is here:

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();
    //...
}
GameZone RO
  • 63
  • 1
  • 8
  • `KeyStore` can't handle those files directly, but it can handle the objects created by reading them. Using a PKCS12 file is indeed an alternative, although I wouldn't call it simpler, but the much more important difference here compared to the question you posted is that you use this key&cert for the KeyManager (only) and not the TrustManager; that is a completely different thing to do and does match what your nodejs did. – dave_thompson_085 Mar 31 '20 at 10:17