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

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

Related

getting a segmentation fault with RSA_public_encrypt

Here's my code:
#include <openssl/evp.h>
#include <openssl/pem.h>
#include <string.h>
int main (void)
{
char publicKey[] = "-----BEGIN RSA PUBLIC KEY-----\n"\
"MIIBCgKCAQEAqBa0jeqfHO8CFZuHyN5WgdTd3uThkU/I9lR+bb2R9khVU1tYhiyL\n"\
"Gfnm051K0039gCBAz9S7HHO2lqR/B1kiEGwBzg83N3tL2UzQXXbpxJz0b7vV8rXZ\n"\
"fB9j+FCAtD5znittXRjWrNqlEyJyxK+PuxNF3uPGWSjkYtRKv2f5HCXsfRJah40w\n"\
"5zU+OKZsZMrSSxweYN9Jwc++oMaSPCsGKog6NPbkbcTK8Akveof8v5rpG50I/zcQ\n"\
"9dCD69qJcmhm4ai4fAZFJlsFH2HmxV6DERFs3TNyzGlSQ1gd/XLER7U9nlrOUT87\n"\
"mXreUIXSkAFdCrlHXHOzj0eodfN8IfHjnQIDAQAB\n"\
"-----END RSA PUBLIC KEY-----\n";
char plaintext[] = "enp6";
// base64 decode plaintext
EVP_DecodeBlock(plaintext, plaintext, strlen(plaintext));
BIO* bo = BIO_new(BIO_s_mem());
BIO_write(bo, publicKey, strlen(publicKey));
EVP_PKEY* pkey = 0;
PEM_read_bio_PUBKEY(bo, NULL, NULL, NULL);
BIO_free(bo);
RSA* rsa = EVP_PKEY_get1_RSA(pkey);
char ciphertext[RSA_size(rsa)];
RSA_public_encrypt(strlen(plaintext), plaintext, ciphertext, rsa, RSA_NO_PADDING);
printf("%d\n", (int) strlen(ciphertext));
}
I'm trying to run it by doing gcc -x c test2.c -lcrypto && ./a.out. Any ideas as to what I'm doing wrong?
Your code contains several mistakes. The direct cause of the segfault is the fact that the variable pkey is never assigned any value after initializing it with 0. EVP_PKEY_get1_RSA(pkey) segfaults as a consequence. You probably intended to do
EVP_PKEY *pkey = PEM_read_bio_PUBKEY(bo, NULL, NULL, NULL);
However, with the PEM that you posted, that will not work. The header of the PEM indicates that this is an RSA key stored in "traditional" format. PEM_read_bio_RSAPublicKey is capable of reading such keys:
RSA *rsa = PEM_read_bio_RSAPublicKey(bo, NULL, NULL, NULL);
Then your choice of RSA_NO_PADDING when invoking RSA_public_encrypt is not right. In order to use that, the plaintext must be the exact same length as the key size. You could try using RSA_PKCS1_PADDING instead.
Finally, like mentioned in the comment section as well, using strlen on cipher text is incorrect, because it is not a 0-terminated string.
In general, you should check all return values for all OpenSSL functions invoked, to see when/if something went wrong.

ECDSA ciphering with OpenSSL results in memory problem

I'm trying to encrypt a message using the ECDSA algorithm with OpenSSL (1.1.1), but I must be doing something wrong with my pointers, because everytime I run the code, it gives me a different result.
Here's what I do:
create a key using custom curve parameters (with BN_hex2bn);
sign a simple message using ECDSA_do_sign;
split the message into the R and S points (ECDSA_SIG_get0_r and ECDSA_SIG_get0_s);
display the resulting R point.
And the display value is always different, while I expect it to be the same (because same message and same key). I think some memory is deallocated somewhere, giving me a pointer to invalid data, but I'm not sure about it; I have checked:
the keys are ok, and they are valid according to EC_KEY_check_key;
the two variables s and r are already wrong, hence it doesn't come from BN_bn2bin.
Here is my code if you want to give a try:
#include <openssl/bn.h>
#include <openssl/ec.h>
#include <openssl/bio.h>
#include <openssl/evp.h>
#include <openssl/ecdsa.h>
#include <openssl/buffer.h>
int main(void) {
ECDSA_SIG *sig;
EC_KEY *eckey;
BIGNUM *priv_key = BN_new();
BIGNUM *x_key = BN_new();
BIGNUM *y_key = BN_new();
const char digest[] = "Hello, world!";
eckey = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1);
// Set predefined keys
BN_hex2bn(&priv_key, "8e9b109e719098bf980487df1f5d77e9cb29606ebed2263b5f57c213df84f4b2");
BN_hex2bn(&x_key, "7fcdce2770f6c45d4183cbee6fdb4b7b580733357be9ef13bacf6e3c7bd15445");
BN_hex2bn(&y_key, "c7f144cd1bbd9b7e872cdfedb9eeb9f4b3695d6ea90b24ad8a4623288588e5ad");
EC_KEY_set_private_key(eckey, priv_key);
EC_KEY_set_public_key_affine_coordinates(eckey, x_key, y_key);
sig = ECDSA_do_sign(digest, 32, eckey);
// Get the resulting point
const BIGNUM *r = ECDSA_SIG_get0_r(sig);
const BIGNUM *s = ECDSA_SIG_get0_s(sig);
unsigned char rc[256];
unsigned char sc[256];
BN_bn2bin(r, rc);
BN_bn2bin(s, sc);
// Print the result: some memory problem...
for(int i = 0; rc[i] != 0; i++) {
printf("%d ", rc[i]);
}
return 0;
}
Do no forget to link the libraries when you compile it: gcc -g -o main main.c -lssl -lcrypto.

How to calculate SHA512/224 and SHA512/256 hashes using OpenSSL?

Here's how I calculate a SHA512 hash in C.
#include <openssl/sha.h>
#include <stdio.h>
char *calc_sha512(char *data) {
SHA512_CTX ctx;
char *md = malloc(sizeof(char)*(SHA512_DIGEST_LENGTH+1));
SHA512_Init(&ctx);
SHA512_Update(&ctx, data, strlen(data));
SHA512_Final(md, &ctx);
md[SHA512_DIGEST_LENGTH] = '\0';
return md;
}
int main() {
printf("%s\n", calc_sha512("foo"));
return 1;
}
Now when I try to transform it to 512/t (either 512/224 or 512/256), it doesn't work. I initially thought that for calculating a 512/t hash, you'd simply have to truncate the output to t bits, but upon further reading, it doesn't only truncate the output, it also have different initial constant values. So I'm trying to figure out any function in OpenSSL that supports that? So far I have looked into docs and header files, I can't find such method under either SHA or EVP. Does OpenSSL even support SHA512/t hashing algorithms? Does it provide the functionality out of the box? Any pointers to that would be helpful.
As you noticed, those truncated sha512 hashes use different initalisation vectors, probably to avoid giving away part of the actual sha512 hash if they would just literally truncate those bytes.
Support for sha512_256 has been implemented in the OpenSSL master tree a few months ago and will likely be in OpenSSL 1.1.1.
Check the changelog:
https://www.openssl.org/news/changelog.txt
Or the commit on Github:
https://github.com/openssl/openssl/commit/4bed94f0c11ef63587c6b2edb03c3c438e221604
... so depending on the platform you're on, you could use those new functions in the near future just like you are doing with the sha512 example you provided.
this function should return the SHA512/256 hash (binary form, not printable)
char *calc_sha512_256(char *data, unsigned int data_len) {
char *hash = malloc(SHA256_DIGEST_LENGTH);
if (!EVP_Digest(data, data_len, hash, NULL, EVP_sha512_256(), NULL)) {
free(hash);
hash = NULL;
}
return hash;
}

PEM_read_RSAPrivateKey: Getting RSA key public modulus and exponent

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

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