Vahagn
Vahagn

Reputation: 4820

How to convert JWK public key to PEM format in C++

Here is an online tool that converts JWK to PEM and vice-versa.

I want the same in C++ code.

For JWK:

{  
  "kty":"RSA",
  "e":"AQAB",
  "kid":"18b4f6a6-f9ec-456b-a3e8-04af5e97790e",
  "n":"tVKUtcx_n9rt5afY_2WFNvU6PlFMggCatsZ3l4RjKxH0jgdLq6CScb0P3ZGXYbPzXvmmLiWZizpb-h0qup5jznOvOr-Dhw9908584BSgC83YacjWNqEK3urxhyE2jWjwRm2N95WGgb5mzE5XmZIvkvyXnn7X8dvgFPF5QwIngGsDG8LyHuJWlaDhr_EPLMW4wHvH0zZCuRMARIJmmqiMy3VD4ftq4nS5s8vJL0pVSrkuNojtokp84AtkADCDU_BUhrc2sIgfnvZ03koCQRoZmWiHu86SuJZYkDFstVTVSR0hiXudFlfQ2rOhPlpObmku68lXw-7V-P7jwrQRFfQVXw"
}

The online tool gives PEM:

-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtVKUtcx/n9rt5afY/2WF
NvU6PlFMggCatsZ3l4RjKxH0jgdLq6CScb0P3ZGXYbPzXvmmLiWZizpb+h0qup5j
znOvOr+Dhw9908584BSgC83YacjWNqEK3urxhyE2jWjwRm2N95WGgb5mzE5XmZIv
kvyXnn7X8dvgFPF5QwIngGsDG8LyHuJWlaDhr/EPLMW4wHvH0zZCuRMARIJmmqiM
y3VD4ftq4nS5s8vJL0pVSrkuNojtokp84AtkADCDU/BUhrc2sIgfnvZ03koCQRoZ
mWiHu86SuJZYkDFstVTVSR0hiXudFlfQ2rOhPlpObmku68lXw+7V+P7jwrQRFfQV
XwIDAQAB
-----END PUBLIC KEY-----

And vice-versa. So the kty and kid fields are also somehow included in the PEM.

I've tried OpenSSL like this:

 std::string_view nnInBase64Url = "tVKUtcx_n9rt5afY_2WFNvU6PlFMggCatsZ3l4RjKxH0jgdLq6CScb0P3ZGXYbPzXvmmLiWZizpb-h0qup5jznOvOr-Dhw9908584BSgC83YacjWNqEK3urxhyE2jWjwRm2N95WGgb5mzE5XmZIvkvyXnn7X8dvgFPF5QwIngGsDG8LyHuJWlaDhr_EPLMW4wHvH0zZCuRMARIJmmqiMy3VD4ftq4nS5s8vJL0pVSrkuNojtokp84AtkADCDU_BUhrc2sIgfnvZ03koCQRoZmWiHu86SuJZYkDFstVTVSR0hiXudFlfQ2rOhPlpObmku68lXw-7V-P7jwrQRFfQVXw";
  std::string_view eeInBase64Url = "AQAB";
  auto nnBin = cppcodec::base64_url_unpadded::decode(nnInBase64Url);
  auto eeBin = cppcodec::base64_url_unpadded::decode(eeInBase64Url);
  BIGNUM* modul = BN_bin2bn(nnBin.data(),nnBin.size(),NULL);
  BIGNUM* expon = BN_bin2bn(eeBin.data(),eeBin.size(),NULL);
  RSA* rr = RSA_new();
  RSA_set0_key(rr, modul, expon, NULL);
  BIO* ff = BIO_new_file("public.pem","w+");
  PEM_write_bio_RSAPublicKey(ff, rr);

But it gave me a different PEM, that should be obvious, as at least, I didn't specify the kid.

Finally, the questions is: how can I implement the proper conversion using OpenSSL or another C++ lib, so it will also take into consideration the kid and kty fields and result with the same PEM as the online tool gives?

Upvotes: 2

Views: 3242

Answers (2)

NQH
NQH

Reputation: 21

You were very close to the solution in your first implementation already, only at the last step, instead of PEM_write_bio_RSAPublicKey you should have used PEM_write_bio_RSA_PUBKEY.

  std::string_view nnInBase64Url = "tVKUtcx_n9rt5afY_2WFNvU6PlFMggCatsZ3l4RjKxH0jgdLq6CScb0P3ZGXYbPzXvmmLiWZizpb-h0qup5jznOvOr-Dhw9908584BSgC83YacjWNqEK3urxhyE2jWjwRm2N95WGgb5mzE5XmZIvkvyXnn7X8dvgFPF5QwIngGsDG8LyHuJWlaDhr_EPLMW4wHvH0zZCuRMARIJmmqiMy3VD4ftq4nS5s8vJL0pVSrkuNojtokp84AtkADCDU_BUhrc2sIgfnvZ03koCQRoZmWiHu86SuJZYkDFstVTVSR0hiXudFlfQ2rOhPlpObmku68lXw-7V-P7jwrQRFfQVXw";
  std::string_view eeInBase64Url = "AQAB";
  auto nnBin = cppcodec::base64_url_unpadded::decode(nnInBase64Url);
  auto eeBin = cppcodec::base64_url_unpadded::decode(eeInBase64Url);
  BIGNUM* modul = BN_bin2bn(nnBin.data(),nnBin.size(),NULL);
  BIGNUM* expon = BN_bin2bn(eeBin.data(),eeBin.size(),NULL);
  RSA* rr = RSA_new();
  RSA_set0_key(rr, modul, expon, NULL);
  BIO* ff = BIO_new_file("public.pem","w+");
  PEM_write_bio_RSA_PUBKEY(ff, rr);

For more information, please refer to the quotes from this discussion

The RSAPublicKey functions process an RSA public key using an RSA structure. The public key is encoded using a PKCS#1 RSAPublicKey structure.

The RSA_PUBKEY functions also process an RSA public key using an RSA structure. However the public key is encoded using a SubjectPublicKeyInfo structure and an error occurs if the public key is not RSA .

and from RFC 3280,

4.1.2.7 Subject Public Key Info

This field is used to carry the public key and identify the algorithm with which the key is used (e.g., RSA, DSA, or Diffie-Hellman). The algorithm is identified using the AlgorithmIdentifier structure specified in section 4.1.1.2. The object identifiers for the supported algorithms and the methods for encoding the public key materials (public key and parameters) are specified in [PKIXALGS].

Upvotes: 2

Vahagn
Vahagn

Reputation: 4820

Here is the solution I found using CryptoPP and CryptoPP-PEM:

std::string getRSAPublicKeyInPEMFormat(std::string_view nnInBase64UrlUnpadded, std::string_view eeInBase64UrlUnpadded)
{
  auto nnBin = cppcodec::base64_url_unpadded::decode(nnInBase64UrlUnpadded);
  auto eeBin = cppcodec::base64_url_unpadded::decode(eeInBase64UrlUnpadded);
  CryptoPP::Integer nn(nnBin.data(), nnBin.size(), CryptoPP::Integer::UNSIGNED, CryptoPP::BIG_ENDIAN_ORDER);
  CryptoPP::Integer ee(eeBin.data(), eeBin.size(), CryptoPP::Integer::UNSIGNED, CryptoPP::BIG_ENDIAN_ORDER);
  CryptoPP::RSA::PublicKey pubKey;
  pubKey.Initialize(nn, ee);
  std::ostringstream pem;
  CryptoPP::FileSink sink(pem);
  CryptoPP::PEM_Save(sink, pubKey);
  return pem.str();
}

Upvotes: 0

Related Questions