I am using this OpenSSL code on the iPhone to generate a PKCS#12 file given some data of a certificate/private key. I am able to verify that this PKCS#12 is parseable on OpenSSL, since it doesn't print out an error when I check for it in the code. However, when I transfer it to my server, it says: Error: PKCS#12 MAC could not be verified. Invalid password? Does anyone know why this is? I am using the same password, which is 'password'
- (NSData *)generateP12AsNSData:(NSData *)keyData certificate:(NSData *)certificateData {
//put the certificateData into a X509 certificate
const unsigned char *certificateDataBytes = (const unsigned char*)[certificateData bytes];
X509 *certificateX509 = d2i_X509(NULL, &certificateDataBytes, [certificateData length]);
EVP_PKEY *privateKey;
PKCS12 *p12;
//cast the key data as an unsigned char so that we can convert it to the OpenSSL key format
const unsigned char *bits = (unsigned char *) [keyData bytes];
int length = (int)[keyData length];
privateKey = EVP_PKEY_new();
//convert the unsigned char to OpenSSL Key format
RSA *rsa = NULL;
d2i_RSAPrivateKey(&rsa, &bits, &length);
EVP_PKEY_assign_RSA(privateKey, rsa);
//create the PKCS#12
OpenSSL_add_all_algorithms();
p12 = PKCS12_create("password", "ExtraDeviceP12", privateKey, certificateX509, NULL, 0,0,0,0,0);
//make sure the p12 exists
if(!p12) {
fprintf(stderr, "Error creating PKCS#12 ");
ERR_print_errors_fp(stderr);
return nil;
}
//error checking to make sure we generated the CSR correctly
STACK_OF(X509) *ca = NULL;
EVP_PKEY *parseKey;
X509 *parseCert;
if (!PKCS12_parse(p12, "password", &parseKey, &parseCert, &ca)) {
printf("error parsing PKCS#12 file");
return nil;
}
//convert the PKCS#12 to binary data
//create a new memory BIO. A BIO is used for basic I/O abstraction.
BIO *bio;
bio = BIO_new(BIO_s_mem());
//i2d_PKCS12_bio is used to export a PKCS12 object
i2d_PKCS12_bio(bio, p12);
BUF_MEM *buffer;
BIO_get_mem_ptr(bio, &buffer);
//int bioLen = BIO_pending(&buffer);
char *buff = (char*)malloc(buffer->length);
memcpy(buff, buffer->data, buffer->length - 1);
buff[buffer->length - 1] = 0;
NSData *data = [NSData dataWithBytes:buff length:buffer->length];
NSString *string = [data base64EncodedStringWithOptions:0];
NSLog(#"Base64 PKCS#12: %#", string);
BIO_free_all(bio);
return data;
}
EDIT:
Here is the code on my server side written in Javascript. In this case req.body is the NSData sent from the iPhone. I get the invalid password error.
var p12b64 = req.body.toString('base64');
var p12Der = forge.util.decode64(pk12b64);
var p12Asn1 = forge.asn1.fromDer(p12Der);
var p12 = forge.pkcs12.pkcs12FromAsn1(p12Asn1, 'password');
Try the following. It shows you where some key return values should be checked, and it avoids extra copies of the data:
BIO *bio = BIO_new(BIO_s_mem());
ASSERT(bio != NULL);
int ret = i2d_PKCS12_bio(bio, p12);
ASSERT(ret == 1);
BUF_MEM *buffer;
BIO_get_mem_ptr(bio, &buffer);
ASSERT(buffer != NULL);
NSData *data = [NSData dataWithBytes:buffer->data length:buffer->length];
BIO_free_all(bio);
You can find the docs for i2d_PKCS12_bio at i2d_PKCS12_bio man pages.
If you want, you can Base64 encode the binary data from i2d_PKCS12_bio. See Non-printable character after generating random n-byte Base64 string for using a BIO chain and ensuring the Base64 string is NULL terminated.
Related
The task is to encrypt some message with RSA public key which lies in .crt file - X509 certificate file.
With issue below I succesfully get sertificate information, but can not find any issue how to use public key from x to encrypt message using RSA algorythm.
When I trying to create BIO buffer with key data and to use PEM_read_bio_RSA_PUBKEY function to write key into RSA struct it returns an error "error:0906D06C:PEM routines:PEM_read_bio:no start line" which is clear why appear but not clear how to correctly solve. Are any issue how I can do encryption without creating another buffer and adding manualy lines with "-----BEGIN RSA PUBLIC KEY-----" and "-----END RSA PUBLIC KEY-----"?
Here are my code for this job:
unsigned char message[] = "Hello cryptographic world!";
unsigned char *encrypted_text;
int CyferWithHostKeyPub()
{
ERR_load_crypto_strings();
int res_length = 0;
RSA *public_key;
X509 *x;
BIO *fp = BIO_new_file("cert.crt", "r");
if (!fp)
{
printf("\nCan't open file.\n");
return -1;
}
x = PEM_read_bio_X509(fp, 0, 0, NULL);
if (!x) {
printf("%s\n", ERR_error_string(ERR_get_error(), NULL));
return 1;
}
BIO *membuf = BIO_new_mem_buf((void*)x->cert_info->key->public_key->data, x->cert_info->key->public_key->length);
public_key = PEM_read_bio_RSA_PUBKEY(membuf, &public_key, NULL, NULL);
if (!public_key) {
printf("%s\n", ERR_error_string(ERR_get_error(), NULL));
return 1;
}
int encrypted_length = RSA_size(public_key);
encrypted_text = new unsigned char[ encrypted_length ];
RSA_public_encrypt(sizeof(message), message, encrypted_text, public_key, RSA_PKCS1_PADDING);
RSA_free(public_key);
X509_free(x);
BIO_free(membuf);
BIO_free(fp);
return encrypted_length;
}
So, how can I do this correctly? Thanks!
Use the X509_get_pubkey() function to obtain a reference to the EVP_PKEY structure contained within the X509 structure. Then you can use EVP_PKEY_get1_RSA() to get a reference to the RSA structure.
See these man pages for further info:
https://www.openssl.org/docs/man1.1.0/crypto/X509_get_pubkey.html
https://www.openssl.org/docs/man1.1.0/crypto/EVP_PKEY_get1_RSA.html
I try to export pfx file which contain certificates chain and private key from windows certificates store, convert oit into PEM format and save it to file which be read by openssl based application.
I do it by the following steps (capi / openssl commands):
creating memory store - CertOpenStore
Open system store - CertOpenSystemStore
find desired certificate according to freindly name -
CertFindCertificateInStore
Add desired certificate to memory store - CertAddCertificateContextToStore
export memory store - PFXExportCertStoreEx
convert blob into base64 and save into file
Import certificates chain from PEM file- SSL_CTX_use_certificate_chain_file
Import private key from PEM file - SSL_CTX_use_PrivateKey_file
1) I didn't find a command which convert pfx (memory blob / file) to pem format
2) does this scenario export all certificate chain which were originally store in the pfx file
3) does SSL_CTX_use_certificate_chain_file import all certificates chain or I have to use other commands to import all chain into CTX structure
Thanks in advance
1) This worked for me
{
FILE* fp = NULL;
CString errorS = NULL;
PKCS12* p12 = NULL;
EVP_PKEY* pkey = NULL;
X509* cert = NULL;
STACK_OF(X509) *ca = NULL;
int i;
pkey = (EVP_PKEY*)new EVP_PKEY;
cert = (X509*)new X509;
do
{
if (fopen_s(&fp, CT2A(pkcs12File), "rb"))
{
errorS = ("Error opening file %s\n", CT2A(pkcs12File));
break;
}
p12 = d2i_PKCS12_fp(fp, NULL);
OpenSSL_add_all_algorithms();
ERR_load_crypto_strings();
if (!p12)
{
errorS = ("Error reading PKCS#12 file\n");
break;
}
if (!PKCS12_parse(p12, CT2A(szPassword), &pkey, &cert, &ca))
{
errorS = ("Error parsing PKCS#12 file\n");
break;
}
if (fopen_s(&fp, CT2A(pszNameString + L".pem"), "w"))
{
errorS = ("Error opening file %s\n", CT2A(pemFileName));
break;
}
if (pkey)
{
fprintf(fp, "***Private Key***\n");
PEM_write_PrivateKey(fp, pkey, NULL, NULL, 0, NULL, NULL);
}
if (cert)
{
fprintf(fp, "***User Certificate***\n");
PEM_write_X509_AUX(fp, cert);
}
if (ca && sk_X509_num(ca))
{
fprintf(fp, "***Other Certificates***\n");
for (i = 0; i < sk_X509_num(ca); i++)
PEM_write_X509_AUX(fp, sk_X509_value(ca, i));
}
} while (0);
PKCS12_free(p12);
sk_X509_pop_free(ca, X509_free);
X509_free(cert);
EVP_PKEY_free(pkey);
if (NULL != fp)
{
fclose(fp);
}
}
2+3) SSL_CTX_use_certificate_chain_file import all certificates chain
I generated RSA private key and public key as below,
openssl genpkey -algorithm RSA -out key.pem -pkeyopt rsa_keygen_bits:2048
openssl rsa -pubout -in pri.key -out pub.key
And encrypted text file as below,
openssl pkeyutl -encrypt -pubin -inkey ~/pub.key -in ~/1.txt -out ~/1e.txt
Then I wrote below program to decrypt the encryted file. However, it seemed that decrypt didn't work as expected.
#include <openssl/evp.h>
#include <openssl/rsa.h>
#include <openssl/pem.h>
#include <openssl/err.h>
#include <openssl/conf.h>
#include <iostream>
using namespace std;
void
cleanup()
{
EVP_cleanup();
CRYPTO_cleanup_all_ex_data();
ERR_free_strings();
}
int
main(int argc, char** argv)
{
ERR_load_crypto_strings();
OpenSSL_add_all_algorithms();
OPENSSL_config(nullptr);
cout<<"Initialize crypto library done"<<endl;
EVP_PKEY * key = EVP_PKEY_new();
if (key == nullptr) {
cout<<"Failed to contruct new key"<<endl;
return 1;
}
FILE * fpri = nullptr;
fpri = fopen("/home/stack/pri.key", "r");
if (fpri == nullptr) {
cout<<"Failed to load private key"<<endl;
return 1;
}
key = PEM_read_PrivateKey(fpri, &key, nullptr, nullptr);
if (key == nullptr) {
std::cout<<"Read private key failed"<<endl;
return 1;
}
cout<<"load private key successfully"<<endl;
EVP_PKEY_CTX *ctx = nullptr;
ctx = EVP_PKEY_CTX_new(key, nullptr);
EVP_PKEY_decrypt_init(ctx);
EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_NO_PADDING);
size_t outlen = 0, inlen = 0;
unsigned char * out = nullptr, * in = nullptr;
char buf[1024];
FILE * fe = nullptr;
fe = fopen("/home/stack/1e.txt", "r");
size_t len = fread(buf, 1, sizeof(buf), fe);
cout<<"data input length is "<<len<<endl;
EVP_PKEY_decrypt(ctx, NULL, &outlen, in, inlen);
cout<<"outlen is "<<outlen<<endl;
out = (unsigned char*)OPENSSL_malloc(outlen);
EVP_PKEY_decrypt(ctx, out, &outlen, in, inlen);
cout<<"decrypted data "<<out<<endl;
cleanup();
return 0;
}
When executing the code, the result is as below,
[stack#agent ~]$ ./test
Initialize crypto library done
load private key successfully
data input length is 256
outlen is 256
decrypted data
It seemed the decrypted data length was not correct and not printable.
When I commented out the instruction "EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_NO_PADDING);", it worked well.
I also tried with RSA_PKCS1_OAEP_PADDING, it didn't work either. If RSA PADDING is not set, it worked.
My question is as below,
Which padding is used in following command?
openssl pkeyutl -encrypt -pubin -inkey ~/pub.key -in ~/1.txt -out ~/1e.txt
Is padding necessary for RSA encrypt/decrypt? If so, how could I apply the padding mechanism?
You should use EVP_PKEY_CTX_set_rsa_padding if you use not default padding in openssl pkeyutl encryption. See openssl pkeyutl documentation for details about -rsa_padding_mode.
when i calculate the sha256-digest of a file with openssl-command-line it differs completely from the digest generated in c++ with the openssl-library.
command-line:
openssl dgst -binary -sha256 MyFile.txt
c++:
BIO* bio = BIO_new_file("MyFile.txt", "rb");
if (bio != NULL)
{
OpenSSL_add_all_digests();
const EVP_MD* md = EVP_sha256();
if (md != NULL)
{
EVP_MD_CTX* ctx = EVP_MD_CTX_create();
if (EVP_DigestInit_ex(ctx, md, NULL))
{
const int bufLength = 4096;
unsigned char buf[bufLength];
unsigned int len;
while (BIO_read(bio, buf, bufLength))
EVP_DigestUpdate(ctx, buf, bufLength);
unsigned char digest[EVP_MAX_MD_SIZE];
int ok = EVP_DigestFinal_ex(ctx, digest, &len); // ok == 1; digest in variable "digest" is totally different from the one calculated on command-line
}
EVP_MD_CTX_cleanup(ctx);
BIO_free_all(bio);
}
}
why is the digest calcuated via c++ totally different, than the one calculated on command-line? (MyFile.txt has no line-breaks or whitespaces in it)
kind regards,
matthias
You handle the reading wrong. With this codechange, the hash is correct:
int readlen;
while ( (readlen = BIO_read(bio, buf, bufLength)) > 0)
{
EVP_DigestUpdate(ctx, buf, readlen);
}
In general, you should have more error handling to make your code more robust.
Cheers,
/Erik Alapää
I want to work with XML file format of RSA public and private keys.
Now i found how to save these keys in PEM and binary (DER) formats (for example, PEM_write_RSAPrivateKey())
I have a string with xml-format RSA keys and i need to load them into EVP_PKEY or RSA OpenSSL structures.
XML format is like this:
<RSAKeyPair>
<Modulus>...</Modulus>
<Exponent>...</Exponent>
<P>...</P>
<Q>...</Q>
<DP>...</DP>
<DQ>...</DQ>
<InverseQ>
...
</InverseQ>
<D>...</D>
</RSAKeyPair>
Thanks!
//just a code for demo,not for actually use
int len;
RSA *rsa;
BIO *bio;
unsigned char *data;
bio = BIO_new(BIO_s_meme());
BIO *b64;
b64 = BIO_new(BIO_f_base64());
BIO_write(bio, "<RSAKeyPair>\n",strlen("<RSAKeyPair>\n"));
//write Modulus
len=BN_num_bytes(rsa->n);
data=(unsigned char *)OPENSSL_malloc(len);
if(data) {
BIO_write(bio," <Modulus>",strlen(" <Modulus>"));
BN_bn2bin(rsa->n,data);
bio = BIO_push(b64, bio);
BIO_write(bio, data, len);
(void)BIO_flush(bio);
BIO_pop(bio);
BIO_reset(b64);
BIO_write(bio,"</Modulus>",strlen("</Modulus>"));
}
//write Exp
...
//write the bignum in rsa structure you want
BIO_write(bio, "</RSAKeyPair>\n",strlen("</RSAKeyPair>"));