11

I am trying to retrieve the public key from Vault. It is stored as secret.

I am trying to convert the string retrieved to a PUBLIC KEY to verify the signature.

Sample public key string looks like this

-----BEGIN PUBLIC KEY----- MIIBITANBgkqhkiG9w0BAQEFAAOCAQ4AMIIBCQKCAQBWeqVZ8Ub/o4VQ8nnm888B /Ydqv2IN5bObxupZ7njMKuT/WPgwlK8+Wc0Xjhy82E51XW6E4/0um8sIQ1cxvoSO QsrfkRagD+O9OrjQbb2TqrilDDhFx9EGjXuZpR3brDUufCG6SkypqiKSaMuoVoax c82TZ1uAIp5OSroWt1IdUkvam24X/7zDIf1l8XWCmbfCDrBb73hBYA4MgTjsSckC 5nz+GLcWTfz0wze4lwHCi1KYFv+1+WcYHWPLbqLtc8nzVqkuP5Ne/9HAFkaEAIw5 fKLccksaT/TLyIcrALcfuABlgX1yeBulVcbTAp+WiYRvo9+FKK23pbwkh+uy0tq1 AgMBAAE= -----END PUBLIC KEY-----

I have added the same in my secret value and there is no formatting.

However with the below code I am facing error InvalidKeyException: INVALID KEY FORMAT in the line

 PublicKey publicKey = fact.generatePublic(pubKeySpec);

Here is the code:

            String publicKeyAsString = secretClient.getSecret("key-name").getValue();
    
            byte[] keyContentAsBytes = publicKeyAsString.getBytes();
    
            KeyFactory fact = KeyFactory.getInstance("RSA");
            X509EncodedKeySpec pubKeySpec = new X509EncodedKeySpec(keyContentAsBytes);
            PublicKey publicKey = fact.generatePublic(pubKeySpec);

Edited with stacktrace:

Caused by: java.security.InvalidKeyException: invalid key format
    at sun.security.x509.X509Key.decode(X509Key.java:386) ~[?:?]
    at sun.security.x509.X509Key.decode(X509Key.java:401) ~[?:?]
    at sun.security.rsa.RSAPublicKeyImpl.<init>(RSAPublicKeyImpl.java:122) ~[?:?]
    at sun.security.rsa.RSAKeyFactory.generatePublic(RSAKeyFactory.java:330) ~[?:?]
    at sun.security.rsa.RSAKeyFactory.engineGeneratePublic(RSAKeyFactory.java:235) ~[?:?]

EDIT: PUBLIC KEY for testing:

-----BEGIN PUBLIC KEY----- MIIBITANBgkqhkiG9w0BAQEFAAOCAQ4AMIIBCQKCAQBWeqVZ8Ub/o4VQ8nnm888B /Ydqv2IN5bObxupZ7njMKuT/WPgwlK8+Wc0Xjhy82E51XW6E4/0um8sIQ1cxvoSO QsrfkRagD+O9OrjQbb2TqrilDDhFx9EGjXuZpR3brDUufCG6SkypqiKSaMuoVoax c82TZ1uAIp5OSroWt1IdUkvam24X/7zDIf1l8XWCmbfCDrBb73hBYA4MgTjsSckC 5nz+GLcWTfz0wze4lwHCi1KYFv+1+WcYHWPLbqLtc8nzVqkuP5Ne/9HAFkaEAIw5 fKLccksaT/TLyIcrALcfuABlgX1yeBulVcbTAp+WiYRvo9+FKK23pbwkh+uy0tq1 AgMBAAE= -----END PUBLIC KEY-----

The value of PublicKeyAsString looks like below:

-----BEGIN PUBLIC KEY----- MIIBITANBgkqhkiG9w0BAQEFAAOCAQ4AMIIBCQKCAQBWeqVZ8Ub/o4VQ8nnm888B /Ydqv2IN5bObxupZ7njMKuT/WPgwlK8+Wc0Xjhy82E51XW6E4/0um8sIQ1cxvoSO QsrfkRagD+O9OrjQbb2TqrilDDhFx9EGjXuZpR3brDUufCG6SkypqiKSaMuoVoax c82TZ1uAIp5OSroWt1IdUkvam24X/7zDIf1l8XWCmbfCDrBb73hBYA4MgTjsSckC 5nz+GLcWTfz0wze4lwHCi1KYFv+1+WcYHWPLbqLtc8nzVqkuP5Ne/9HAFkaEAIw5 fKLccksaT/TLyIcrALcfuABlgX1yeBulVcbTAp+WiYRvo9+FKK23pbwkh+uy0tq1 AgMBAAE= -----END PUBLIC KEY-----

Rohi
  • 385
  • 2
  • 3
  • 15

1 Answers1

10

Initially I thought that your problem had to do with the kind of information returned by the Azure KeyVault Secret API, usually encoded as base 64.

In that case, you nee to perform a proper base 64 decoding before attempting to perform the actual key material processing:

String publicKeyAsString = secretClient.getSecret("key-name").getValue();
    
byte[] keyContentAsBytes = Base64.getDecoder().decode(publicKeyAsString);

But it seems that the Azure client is providing you the information as plain text.

In this case, the secret is a pem encoded public key.

The standard KeyFactory will not allow you to process the returned information out of the box, but yes with slight modifications. For example, try the following:

// Actually
// String publicKeyAsString = secretClient.getSecret("key-name").getValue();

String publicKeyAsString =
    "-----BEGIN PUBLIC KEY-----\n" +
    "MIIBITANBgkqhkiG9w0BAQEFAAOCAQ4AMIIBCQKCAQBWeqVZ8Ub/o4VQ8nnm888B\n" +
    "/Ydqv2IN5bObxupZ7njMKuT/WPgwlK8+Wc0Xjhy82E51XW6E4/0um8sIQ1cxvoSO\n" +
    "QsrfkRagD+O9OrjQbb2TqrilDDhFx9EGjXuZpR3brDUufCG6SkypqiKSaMuoVoax\n" +
    "c82TZ1uAIp5OSroWt1IdUkvam24X/7zDIf1l8XWCmbfCDrBb73hBYA4MgTjsSckC\n" +
    "5nz+GLcWTfz0wze4lwHCi1KYFv+1+WcYHWPLbqLtc8nzVqkuP5Ne/9HAFkaEAIw5\n" +
    "fKLccksaT/TLyIcrALcfuABlgX1yeBulVcbTAp+WiYRvo9+FKK23pbwkh+uy0tq1\n" +
    "AgMBAAE=\n" +
    "-----END PUBLIC KEY-----";

String publicKeyPem = publicKeyAsString
    .replace("-----BEGIN PUBLIC KEY-----", "")
    .replaceAll("\\n", "")
    .replace("-----END PUBLIC KEY-----", "");

byte[] keyContentAsBytes =  Base64.getDecoder().decode(publicKeyPem);

try {
  KeyFactory fact = KeyFactory.getInstance("RSA");
  X509EncodedKeySpec pubKeySpec = new X509EncodedKeySpec(keyContentAsBytes);
  PublicKey publicKey = fact.generatePublic(pubKeySpec);
  System.out.println(publicKey);
}catch (Throwable t) {
  t.printStackTrace();
}

Or better, use BouncyCastle PemReader for this task:

try (
    Reader reader = new StringReader(publicKeyAsString);
    PemReader pemReader = new PemReader(reader)
) {
  KeyFactory fact = KeyFactory.getInstance("RSA");
  PemObject pemObject = pemReader.readPemObject();
  byte[] keyContentAsBytesFromBC = pemObject.getContent();
  X509EncodedKeySpec pubKeySpec = new X509EncodedKeySpec(keyContentAsBytesFromBC);
  PublicKey publicKey = fact.generatePublic(pubKeySpec);
  System.out.println(publicKey);
} catch (Throwable t) {
  t.printStackTrace();
}

Please, be aware that I included several carriage returns in the definition of the variable publicKeyAsString, they were necessary in order to allow the program process the information.

Please, verify that Azure is returning the pem encoded key in a similar way: if it is not the case, it could be very likely the reason of the problem.

Also, pay attention in the fact that Azure KeyVault is returning the secret as you uploaded it: maybe the problem is there. Please, try the following instead:

PublicKey publicKey = ...
StringWriter writer = new StringWriter();
PemWriter pemWriter = new PemWriter(writer);
pemWriter.writeObject(
  new PemObject("PUBLIC KEY", publicKey.getEncoded())
);
pemWriter.flush();
pemWriter.close();
String publicKeyAsString = writer.toString();
// Upload to Azure KeyVault
jccampanero
  • 50,989
  • 3
  • 20
  • 49
  • I tried to do the same and I am getting illegal base 64 charachter 29 exception – Rohi Jan 20 '21 at 11:24
  • 1
    It is very strange @Rohi, it should work without any problem. Please, can you debug the value returned by `secretClient.getSecret("key-name").getValue()` and see if something is wrong with it? Does it print a right base 64 string? – jccampanero Jan 20 '21 at 11:27
  • Could you tell me how can I validate that it is a right base 64 string ? – Rohi Jan 20 '21 at 11:29
  • 1
    First, were you able to print the value? Does it looks like a valid base 64 encoding? Pease, can you tell me what OS are you using? – jccampanero Jan 20 '21 at 11:31
  • yes I Can print the value. I am using windows – Rohi Jan 20 '21 at 11:31
  • 1
    And, it looks like a valid base 64 encoding information? – jccampanero Jan 20 '21 at 11:32
  • I am checking that – Rohi Jan 20 '21 at 11:32
  • I did validate if the string is base 64 or not, and unfortunately it is not – Rohi Jan 20 '21 at 11:38
  • Ok @Rohi. By the way, in do not use windows in my day to day work, but I think you can decode a base64 encoded string either saving the information to a file and the apply something like `certutil -decode data.b64 data.txt` from the command line, or with Power shell, with something like this, `$ENCODED = "AFSF... your value" $DECODED = [System.Text.Encoding]::Unicode.GetString([System.Convert]::FromBase64String($ENCODED)) Write-Output $DECODED` – jccampanero Jan 20 '21 at 11:39
  • @Rohi, but you are using Azure KeyVault and the SDK libraries provided by Microsoft, aren't you? – jccampanero Jan 20 '21 at 11:40
  • Yes I am using keyvault.So the above method to do it a text file doesnt work . I need to do that in code itself. – Rohi Jan 20 '21 at 11:41
  • Of course @Rohi, I understand, I proposed the command line options just to verify that the information is base 64 encoded – jccampanero Jan 20 '21 at 11:42
  • Oh, Yeah thank you. I wil do that as well . currently I checked with regexpattern matching – Rohi Jan 20 '21 at 11:43
  • 1
    If you are using KeyVault, the value returned by the secret should be base 64 encoded. At least, it looks like base 64 encoded information? I mean, is it possible that only some chars are incorrect? Please, can you provide your stack trace? – jccampanero Jan 20 '21 at 11:47
  • I dont think so, I tried to save a public key in the key vault secret. I dont think there should be something wrong with that – Rohi Jan 20 '21 at 11:49
  • Please, can you provide some text, not all the text, as they are returned by `secretClient.getSecret("key-name").getValue()` and the stack trace of the error? – jccampanero Jan 20 '21 at 11:52
  • I have provided it in the description above and edited with the stacktrace. – Rohi Jan 20 '21 at 11:54
  • 1
    Thank you very much Please, see my update. I hope it helps. – jccampanero Jan 20 '21 at 12:26
  • 1
    @Rohi, please, see the updated content of the answer, I forgot that you of course need to decode also the text within the `----BEGIN PUBLIC KEY-----` and `----END PUBLIC KEY-----` boundaries :(: `byte[] keyContentAsBytes = Base64.getDecoder().decode(publicKeyPem);` – jccampanero Jan 20 '21 at 13:20
  • However the base64.getDecode.decode(); throws the exception and I am not able to proceed further – Rohi Jan 20 '21 at 13:40
  • Do you mean the first or the second one? Sorry, when I saw the stack trace I thought that tit was raised after processing the information returned by Azure. Do you follow the examples? Does the variable `publicKeyPem` look like it should be? It should be great if you can include, even in a comment, a small fragment of the base 64 text that is causing the problem and the stack trace of `Base64.getDecoder().decode(...)` – jccampanero Jan 20 '21 at 13:42
  • I am trying to follow the second example with the bouncyCastle. and in the second line there is this base64.getdecoder.decode() method where the exception is already thrown. So I could not proceed further to look into the issue – Rohi Jan 20 '21 at 13:47
  • 1
    I see. Please, can include in a comment a small fragment of the text that is causing the problem, the value obtained from `secretClient.getSecret("key-name").getValue()`? – jccampanero Jan 20 '21 at 13:51
  • 1
    @Rohi, please, can you try the BouncyCastle approach but without decoding? I mean, substitute the line `byte[] pemContentAsBytes = Base64.getDecoder().decode(publicKeyAsString);` by `byte[] pemContentAsBytes = publicKeyAsString.getBytes();`. You are getting the right value for the secret without decoding indeed – jccampanero Jan 20 '21 at 13:56
  • the pemObject throws a null pointer exception in line pemobject.getcontent – Rohi Jan 20 '21 at 14:01
  • This is probably because pemobject is not resolved correctly. Please, can you try the first and simpler approach? Please, try with the aforementioned change `byte[] pemContentAsBytes = publicKeyAsString.getBytes();` – jccampanero Jan 20 '21 at 14:06
  • I tried that and got IllegalArgumentException: Illegal base64 charachter 20 – Rohi Jan 20 '21 at 14:08
  • I see @Rohi. Is the value you indicated for the variable `publickeyAsAString` the actual one? I mean, can I use it for testing? – jccampanero Jan 20 '21 at 14:09
  • I dont think you could use that for testing, I modified it. I can provide something similar for testing – Rohi Jan 20 '21 at 14:10
  • 1
    Thank you very much, I appreciate that. Yes, please, never post something that is actually valuable in the answer. I must leave now, but I will review the example later. – jccampanero Jan 20 '21 at 14:13
  • I have edited the question with a public key that could be used for testing and the value that is retrieved from Vault – Rohi Jan 20 '21 at 14:36
  • 1
    Thank you very much @Rohi. I updated my answer. In fact, all seems to be fine, I think the only problem are the base 64 encoding of the public key, it seems to be incorrect. Maybe you uploading the key with the wrong format? Please, see the answer. I tested the provided code and it works properly. – jccampanero Jan 20 '21 at 16:43
  • Just one more question .Could a public key be PKC8Encoded format ? – Rohi Jan 20 '21 at 17:20
  • 1
    Hi @Rohi. Were you able to solve the problem? Regarding your question, AFAIK no, only private keys can be stored in PKCS#8 format. – jccampanero Jan 20 '21 at 17:44
  • No, I am not able to . But if I provide it as string in my code, then the code helps I can do it. However, the secret returned from vault , is somehow badly formatted or not pem encoded. – Rohi Jan 20 '21 at 17:45
  • Assuming the above public key , how would you provide it as a secret value in Vault ? – Rohi Jan 20 '21 at 17:47
  • I am going to mark your solution as answered :) Since the issue is from the value returned from Vault. Any input on that also would be helpful . – Rohi Jan 20 '21 at 17:54
  • Thank you very much @Rohi ;). Please, see the update, I hope it helps. – jccampanero Jan 20 '21 at 18:08
  • let me try that. IF i understood that correctly, that means, I will use the code to get the publickey as a string and upload it to my azure vault right ? – Rohi Jan 20 '21 at 18:09
  • Yes, that is the idea @Rohi – jccampanero Jan 20 '21 at 18:14