PEM_read_RSAPrivateKey: Getting RSA key public modulus and exponent - c

I'm using PEM_read_RSAPrivateKey function in this way:
void test(void)
{
RSA * privateKey = NULL;
FILE * fp;
if(NULL != (fp= fopen("./my_file.key", "r")) )
{
privateKey=PEM_read_RSAPrivateKey(fp,NULL,NULL,NULL);
if(privateKey==NULL)
{
printf("\n\tCould NOT read RSA private key file");
}
else
{
printf("\n\tRSA structure filled");
}
// This is working OK and privateKey is NOT NULL
}
}
Then, I try to retrieve modulus and public exponent to fill them into a personal structure:
struct
{
unsigned char modulus[256];
unsigned char pub_exp[8];
} s;
But all accesses I tried (I tried a lot) to privateKey->n will result in a segmentation fault.
for example:
unsigned char modulus [2048];
unsigned char exp[2048];
BN_bn2bin(privateKey->n, modulus); // Segmentation fault results from this call
So my question is: how to copy modulus or public exponent from RSA structure to my structure "s" fields?
May someone help about this?
Many thanks,
Regards,
Sylvain

how to copy modulus or public exponent from RSA structure
int req = BN_num_bytes(rsa->n);
assert(rc > 0);
unsigned char* buff = malloc(req);
assert(buff != NULL);
int rc = BN_bn2bin(rsa->n, buff);
assert(req == rc);
Be wary of trying to copy the byte buffers into fixed sized arrays. Someone might come along and get you to copy a 4096-bit modulus into your 2048-bit array.

From OpenSSL 1.1.0 many structures were made opaque and you can no longer do things like look directly at the fields in a struct.
You should use the provided accessor functions instead. Here you can find more infos: https://wiki.openssl.org/index.php/OpenSSL_1.1.0_Changes
Acces rsa->n in OpenSSL 1.1.0:
RSA *pRsa;
BIGNUM *n;
FILE *pFile = fopen("private.key","r");
pRsa = PEM_read_RSAPrivateKey(pFile, NULL, NULL, password);
RSA_get0_key(pRSA, &n, NULL,NULL);
BN_print_fp(stdout, n);

Related

Write Cert to DER

I'm trying to write a X509 Cert to DER format in memory.
Writing it to a file works perfectly.
I need the Cert in PEM format without the "-----BEGIN PRIVATE KEY-----" header, footer or newlines. I can't figure out how to do it directly so...
I'm outputting to der and base64 encoding.
THIS WORKS.
int X509_to_DER_file(X509 *cert) {
int res=0;
out = BIO_new(BIO_s_file());
if (NULL != out) {
if(BIO_write_filename(out, "my.der") > 0) {
res = i2d_X509_bio(out, cert);
}
BIO_free_all(out);
}
return (tres);
}
THIS DOES NOT.
It returns and mallocs the correct number of bytes and appears to write out to memory correctly but the resulting string is incorrect (the first 15 or so positions are correct).
char *X509_to_DER_mem(X509 *cert) {
char *der = NULL;
bio = BIO_new(BIO_s_mem());
if (NULL != bio) {
//load cert into bio
if (0 == i2d_X509_bio(bio, cert)) {
BIO_flush(bio);
BIO_free(bio);
return NULL;
}
der = (char *) malloc(bio->num_write + 1);
if (NULL == der) {
BIO_free(bio);
return NULL;
}
memset(der, 0, bio->num_write + 1);
BIO_read(bio, der, bio->num_write);
// Appears to work put "der" is incomplete.
BIO_free(bio);
}
return der;
}
It returns and mallocs the correct number of bytes and appears to
write out to memory correctly but the resulting string is incorrect
The result of i2d_X509_bio() is not a (zero-terminated) string, but a bunch of bytes. If you try to write it to a file as a string, it might look incomplete because you might encounter a 0-byte at a location before you reach the end. So in addition to the char * result, your function X509_to_DER_mem() will have to return the number of bytes that make up the result.
With regard to the memory BIO, another way of obtaining its data is with the BIO_get_mem_data() function. Something like this:
char *ptr = NULL;
long len = BIO_get_mem_data(bio, &ptr);
der = malloc(len);
memcpy(der, ptr, len);
Finally, your actual question is
I need the Cert in PEM format without the "-----BEGIN PRIVATE
KEY-----" header, footer or newlines.
Writing the certificate in DER format does not seem to give you what you need. This answer to another SO question explains how you could use the function PEM_read_bio() in combination with EVP_EncodeBlock() for that purpose.

Set up a specific key for RSA OpenSSL

I'm currentlyt trying to implement some test vectors for RSA implementations, and I wanted to test them over OpenSSL v1.1.0f implementation. However, when I try to set up the key (for e, n p, q or d), I have the following error :
erreur : dereferencing pointer to incomplete type « RSA {alias struct rsa_st} »
My code is the following :
int rsa_encrypt(byte *in, size_t in_len, byte *out, const char *n, const char *e, char padding){
int err = -1;
RSA *keys = NULL;
keys = RSA_new();
BN_hex2bn(&keys->n, n); // error here
BN_hex2bn(&keys->e, e); // and here
out = malloc(RSA_size(keys));
if (padding == OAEP ) {
err = RSA_public_encrypt(in_len, in, out, keys, RSA_PKCS1_OAEP_PADDING);
}
else if (padding == v1_5) {
err = RSA_public_encrypt(in_len, in, out, keys, RSA_PKCS1_PADDING);
}
RSA_free(keys);
return err;
}
Where n and e are a string representing my parameter in hexa.
I've looked for the structure corresponding to the RSA type, and found this.
I don't understand why I can't set up n and e... Any idea ?
The OpenSSL structs are opaque in 1.1.x, you can't talk about their fields. Among other things, it allows for new struct fields to be added during servicing releases, since caller code can't know the field offset.
For 1.1.x you'd need to do something akin to
BN* bnN = NULL;
BN* bnE = NULL;
RSA* keys = RSA_new();
BN_hex2bn(&bnN, n);
BN_hex2bn(&bnE, e);
RSA_set0_key(keys, bnN, bnE, NULL);
...
RSA_free(keys);
// do not free bnN or bnE, they were freed by RSA_free.
Note that I left out the requisite error checking.

Segmentation fault with generating an RSA and saving in ASN.1/DER?

#include <string.h>
#include <openssl/aes.h>
#include <openssl/rand.h>
#include <openssl/bio.h>
#include <openssl/rsa.h>
#include <openssl/evp.h>
#include <openssl/pem.h>
#define RSA_LEN 2048
#define RSA_FACTOR 65537
int genRSA2048(unsigned char **pub,unsigned int *pub_l,unsigned char **priv,unsigned int *priv_l){
RSA *pRSA = NULL;
pRSA = RSA_generate_key(RSA_LEN,RSA_FACTOR,NULL,NULL);
if (pRSA){
pub_l = malloc(sizeof(pub_l));
*pub_l = i2d_RSAPublicKey(pRSA,pub);
priv_l = malloc(sizeof(priv_l));
*priv_l = i2d_RSAPrivateKey(pRSA,priv);
return 0;
} else {
return 1;
}
}
int main(){
unsigned char *pub = NULL;
unsigned int publ;
unsigned char *priv = NULL;
unsigned int privl;
genRSA2048(&pub,&publ,&priv,&privl);
RSA *privrsa = NULL;
d2i_RSAPrivateKey(&privrsa,(const unsigned char **)&priv,privl);
RSA *pubrsa = NULL;
d2i_RSAPublicKey(&pubrsa,(const unsigned char **)&pub,publ);
unsigned char * data ="01234567890123456789012345678912";
unsigned char encrypted[256];
unsigned char decrypted[32];
int len = RSA_private_encrypt(32,data,encrypted,privrsa,RSA_PKCS1_PADDING);
RSA_public_decrypt(len,encrypted,decrypted,pubrsa,RSA_PKCS1_PADDING);
}
I've tried to find the bug by checking with gdb but as being fairly new to C I haven't find any clue to tell me what is happening but I believe it's an allocation problem, however according to the d2i_RSAPrivateKey and similar, they're supposed to allocate the space by itself.
Any help would be greatly appreciated.
compiled as cc foo.c -lcrypto
This is the follow up of this question:
Generate RSA public/private key with OpenSSL?
As I reference I used #WhozCraig example in the comments, which can be found here, even when it's quite different, it was a lot of help.
http://coliru.stacked-crooked.com/a/ae64a70076436165
pub_l = malloc(sizeof(pub_l)); is simply not needed. Nor is priv_l = malloc(sizeof(priv_l));. Remove them both from your function.
You should be populating your out-parameters; instead you're throwing out the caller's provided addresses to populate and (a) populating your own, then (b) leaking the memory you just allocated.
The result is the caller's privl and publ are untouched and thus the decoding back to RSA is dysfunctional, as both values are indeterminate.
pRSA = RSA_generate_key(RSA_LEN,RSA_FACTOR,NULL,NULL);
I think this is wrong. I know you are supposed to use RSA_generate_key_ex, and I think it needs a BIGNUM, not an integer. You should have gotten a warning. See RSA_generate_key(3) for details.
Your code should look something like:
BIGNUM* exp = BN_new();
ASSERT(exp != NULL);
int rc = BN_set_word(exp, RSA_F4);
ASSERT(rc == 1);
RSA* rsa = RSA_new();
ASSERT(rsa != NULL);
rc = RSA_generate_key_ex(rsa, 2048, exp, NULL);
ASSERT(rc == 1);
Be sure to call BN_free on the BIGNUM, and RSA_free on the RSA pointer.
RSA *privrsa = NULL;
d2i_RSAPrivateKey(&privrsa,(const unsigned char **)&priv,privl);
RSA *pubrsa = NULL;
d2i_RSAPublicKey(&pubrsa,(const unsigned char **)&pub,publ);
For this, it looks like you are trying to separate the public key and private key. For that, use RSAPublicKey_dup and RSAPrivateKey_dup. See Separating public and private keys from RSA keypair variable.
Its not clear to me what you are trying to do with the following. You should state what you are trying to do...
pub_l = malloc(sizeof(pub_l));
*pub_l = i2d_RSAPublicKey(pRSA,pub);
priv_l = malloc(sizeof(priv_l));
*priv_l = i2d_RSAPrivateKey(pRSA,priv);
I'm just guessing, but I'm going to say its all wrong. sizeof(priv_l) is the size of a pointer, so its 4 or 8 bytes. You're also overwriting the pointer passed in by the caller...
Also see OpenSSL's rsautl cannot load public key created with PEM_write_RSAPublicKey. It talks about saving the keys with SubjectPublicKeyInfo and PrivateKeyInfo in both ASN.1/DER and PEM formats.
By writing the {Public|Private}KeyInfo, the OID gets written to the key. That's important for interop. You also use the RSA* (and even an EVP_PKEY*), and not byte arrays.

Reading PEM-formatted RSA keyfile with the OpenSSL C API

I am trying to read in an AES-128-CBC encrypted PEM key file generated using Ruby with the OpenSSL API. The code that generated the PEM key is the following:
OpenSSL::PKey::RSA.new(2048).to_pem(OpenSSL::Cipher::AES.new('128-CBC'), "password")
Here's the code that reads the PEM file:
RSA *rsa;
BIO *pem = BIO_new(BIO_s_mem());
BIO_puts(pem, "-----BEGIN RSA PRIVATE KEY-----\n"
"Proc-Type: 4,ENCRYPTED\n"
"DEK-Info: AES-128-CBC,BB13D39833DD6ED1FF9843644E7981EE\n\n
"Eugt8JZNKQKErsabWkwfm3wQhU/Tmp9T0QaP5HM8VIWpZwKlDmRlSUDptADU6RPD\n"
"5VtG3DPieXcf+6deyARImid9sBBmQ9mK2omkNRcTMemqTOhAuaKBu78TMt9G4YSf\n"
"RjoXWSqu3jMwrlcGkpn7bIum8wImITRZ3p28oSzk9aDUNBrIU/2Si8DM4RYIZ/fK\n"
"Uvvgdok9dgcd0SjvucivX2HaGeg/IUz23q1jg9inDpimZvFJD1FJfGEUWDgyfJfa\n"
"M8JIxTKbWOPEopONDkT7u4dC5VcSjK29MVbfd7iCKFPMh5UN+c96rPxTng/OWyW5\n"
"0tvzHyyyvAG9p0Hx5Lr4pDbv21GHyu43sA6wbs9jWyqO3AB7CaoEEQhumwfLsdjj\n"
"YGrX6bWThpYv/XNBDmmvltHlKFfe01NCybivOb4KwBnvi45x21PBqaZCKDTFdEkL\n"
"iwDMTiG2iTxSUvPFLy30VFozE+pGyMcGDUyZDVqjsaqI/MRj8khnn5nyubXc27G3\n"
"8Kbsnlix2SW2M0VDxqiy9dyjcxXrkFRSnOFYVs1PFlgjFVTG4Mwh6CZxKw8mFVbi\n"
"EmLvUYwzoDZ1ve4VXSPp/vrKEh33JuHhM0vJOpqI6wqw0QR0I2o6etM1ZRJClPcw\n"
"VIcgcvwenEgLOkoHDqOr0IZQAtYWvAuqq822wKt258hc6z8+ALQf5iMroqk7ADd4\n"
"FlRLz4XTwqlg7pPtTde/emI1DT8dQWzq++QI0lr0CS/N1GXJKqTQDvauXLIiI3Qy\n"
"KfFYFpV9jyYfRfTjNtisI/edPtp98auK0mb9o/wS/hruFI9behgv63iW1IwAOXCW\n"
"ZlkWgobUH13gS864rL+AcrAXreo2j4dDQouTeRaJUEG0HoYTP65Zun/VsCi2aSOH\n"
"JwSnnmHz9OxvcGY80WJDN3kqOCBRIJoDKBv6jcOxGVCsVK+WSdGZ7cfb8lwp7aA8\n"
"8ND1bwL9FYkwkeIsoakj91iinqv4o3+3PUPgCU5oe68WYvAFjuU+criyf+EhmXJV\n"
"JQ1vFFZPrGzgntJz19uXXh1h2iwQPggRouJm2RozYwvv1nz4eQ40Y3eT1F9UOYJU\n"
"CKEhOtI2NpLeVOayqo8g9wO2oC+CQVhZhdYBE5o7pM7akFnYLvRg9s1UsWdcvT0G\n"
"IpFmejLSRJ/F954aQMHTUc6vBOJZH/VNC5Qt+ulFXl634Sr9wQQK2qlqSJyA04TR\n"
"1ixbCNOX71esvpFImsrlsO5oTA22T3h2GyJPUM10XhqGtDXtsTnal6smLna9U9B3\n"
"gTVxFWWukQOF5Lm8ZFQipo2loHWjkozTBc4REPYP44SoXJXstv7k4pt1cK7x6/2H\n"
"ElspXzjveqMhcrveWv1KaA2OGd+hGfUiNsCoIdapJjLz1Bd/+oIQ/ZWQeo0nRowE\n"
"R/HlbbED3V+fRIdJpgydFEAw6gK5E9sYJcgF7uf/n2NabFxxEZL3g6MJQ64Dtusg\n"
"DEH/MpvIYDSX4Navh1gTwCtOeG1CzW3diYaqbZK+UZCBLFU7j27YvVPSd6F2+Wud\n"
"WnAqU3S5BCPqk5OD3wqZv+sEcqJgGPGy1Gv0tl8ARJomdKAru03KsRn2eIWqR5/C\n"
"-----END RSA PRIVATE KEY-----\n");
// Retrieve RSA key from PEM file.
rsa = PEM_read_bio_RSAPrivateKey(pem, NULL, pem_password_callback, "password");
And here's a dummy password callback (not sure about this function's purpose, but I think it may return the length of the password):
int pem_password_callback(char *buf, int max_len, int flag, void *pwd)
{
return 8;
}
Currently, the rsa = ... part does not throw an error, but doesn't return a well-formed result either.
And here's a dummy password callback (not sure about this function's
purpose, but I think it may return the length of the password):
int pem_password_callback(char *buf, int max_len, int flag, void *pwd)
{
return 8;
}
No, its not a dummy. In your example, you just returned a buffer with 8 junk characters (whatever happened to be in buf).
The password callback is where you programmatically plug in your password. You are supplied with a buff of max_len, and you need to copy the password into the buffer and return the number of bytes copied.
int pem_password_callback(char *buf, int max_len, int flag, void *ctx)
{
const char* PASSWD = "password";
int len = strlen(PASSWD);
if(len > max_len)
return 0;
memcpy(buf, PASSWD, len);
return len;
}
The flag is a read/write flag to denote if you are reading a key or writing a key. In practice, I have never used it.
You will use it similar to:
FILE* file = ...;
EVP_PKEY* pkey = PEM_read_PrivateKey(file, NULL, pem_password_callback, NULL);
Unlike the write routine (which needs an EVP_* cipher), the read routine knows what you used to encrypt the key with because its encoded in the private key.
In my systems, I actually use the context for a label to ensure the same passwords arrive at different derived keys:
EVP_PKEY* pkey = PEM_read_PrivateKey(file, NULL, pem_password_callback, "Some Context");
Then, in my password callback:
int pem_password_callback(char *buf, int max_len, int flag, void *ctx)
{
// "Some Context" in the example above
char* label = (char*)ctx;
// Hash password and label
// ...
// Copy hash to buffer, return length
...
}

ASN1_INTEGER to ASN1_STRING

I am using openssl to get data about x509 certificate.
Is there a way to convert ASN1_INTEGER to ASN1_STRING, which can than easily be transformed to char array? Is there a way to convert it to any other human readable format?
EDIT: I'm using openssl compiled for iOS, as I am having the iOS project. Here is the code I am using to extract the serial number from the certificate:
ASN1_INTEGER *serial = X509_get_serialNumber(certificateX509);
long value = ASN1_INTEGER_get(serial);
NSLog(#"Serial %ld", value);
certificateX509 is a valid X509 object and I have managed to get some other fields from it (issuer name, expiry date and so on)
EDIT 2:
I finally came to a solution, which may not be the most straightforward one:
ASN1_INTEGER *serial = X509_get_serialNumber(certificateX509);
BIGNUM *bnser = ASN1_INTEGER_to_BN(serial, NULL);
int n = BN_num_bytes(bnser);
unsigned char outbuf[n];
int bin = BN_bn2bin(bnser, outbuf);
char *hexbuf = (char*) outbuf;
hexBuf then contains characters whose value needs to be read as hex integer in order to retrieve logical values.
I use NSMutableString to create a human readable string:
NSMutableString *str = [[NSMutableString alloc] init];
for (int i=0; i<n; i++) {
NSString *temp = [NSString stringWithFormat:#"%.6x", hexbuf[i]];
[str appendString:[NSString stringWithFormat:#"%# ", temp]];
}
If there is a simpler way, I would really like to know it.
The ascii hex conversion be done more simply using the built in BN_bn2hex(BIGNUM *) function
ASN1_INTEGER *serial = X509_get_serialNumber(certificateX509);
BIGNUM *bnser = ASN1_INTEGER_to_BN(serial, NULL);
char *asciiHex = BN_bn2hex(bnser);
One possibility is that you can extract the value of the ASN1_INTEGER as a normal C integer:
#include <openssl/asn1.h>
#include <stdio.h>
int main(int argc, char** argv) {
long value;
ASN1_INTEGER asn1int = {0};
ASN1_INTEGER_set(&asn1int, 42);
value = ASN1_INTEGER_get(&asn1int);
printf("The value is %ld.\n", value);
return 0;
}
Compiled like this:
gcc -Wall -o sploots sploots.c -lcrypto
this produces the output:
The value is 42.
To have the value as a string in an array of char, use snprintf.
I suspect there are also possibilities for using the BIO printing routines to dump the value to a BIO of some sort (perhaps a memory BIO). However, this approach seems simpler.
The way I arrived at this answer is that I looked through the OpenSSL headers for ASN1_INTEGER. After looking around for suitable APIs for a BIO-based solution, I noticed the ASN1_INTEGER_get function.
Looking around in OpenSSL header files is typically the way I learn how to use OpenSSL, since so much of the API is undocumented or incorrectly or incompletely documented.
I finally came to a solution, which may not be the most straightforward one:
ASN1_INTEGER *serial = X509_get_serialNumber(certificateX509);
BIGNUM *bnser = ASN1_INTEGER_to_BN(serial, NULL);
int n = BN_num_bytes(bnser);
unsigned char outbuf[n];
int bin = BN_bn2bin(bnser, outbuf);
char *hexBuf = (char*) outbuf;
hexBuf then contains characters whose value needs to be read as hex integer in order to retrieve logical values.
I use NSMutableString to create a human readable string:
NSMutableString *str = [[NSMutableString alloc] init];
for (int i=0; i<n; i++) {
NSString *temp = [NSString stringWithFormat:#"%.6x", hexbuf[i]];
[str appendString:[NSString stringWithFormat:#"%# ", temp]];
}
If you just want a readable NSString, BN_bn2dec is more consize than BN_bn2hex or BN_bn2bin.
No need to mess with hex encoding.
Here's my way, in iOS/ObjC, using pod 'OpenSSL-Universal', '1.0.2.10' :
ASN1_INTEGER *serialAsn1 = X509_get_serialNumber(certX509);
BIGNUM *serialBigNumber = ASN1_INTEGER_to_BN(serialAsn1, NULL);
char *serialChar = BN_bn2dec(serialBigNumber);
NSString *serialString = [NSString stringWithCString:(const char *) serialChar
encoding:NSUTF8StringEncoding];
Cheers.
This solution worked for me to get the serial number in hex:
ASN1_INTEGER* serial = X509_get_serialNumber(X509_certificate_ptr);
BIGNUM* bn = ASN1_INTEGER_to_BN(serial, NULL);
if (!bn) {
// log here "Unable to convert ASN1INTEGER to BN";
return "";
}
char* hex = BN_bn2hex(bn);
if (!hex) {
// Log here "Unable to convert BN to hex string";
return "";
}
cout << hex; // this is your serial number. Can be converted using std::to_string()
BN_free(bn);
ASN1_INTEGER_free(serial);
OPENSSL_free(hex);
return serial_number;
Without error checking and memory management the code is basically:
ASN1_INTEGER* serial = X509_get_serialNumber(X509_certificate_ptr);
BIGNUM* bn = ASN1_INTEGER_to_BN(serial, NULL);
char* hex = BN_bn2hex(bn);

Resources