C++ and OpenSSL Library: How can I set subjectAltName (SAN) from code? - c++

I'm trying to create self-signed request with subjectAltName from c++ code (trying to implement dynamic self-signed certificates like this to actual version of OpenResty, but there is not sollution for subjectAltName).
Please, provide some examples of setting SANs from C++/OpenSSL code. I trying some like this:
X509_EXTENSION *ext;
STACK_OF (X509_EXTENSION) * extlist;
char *ext_name = "subjectAltName";
char *ext_value = "DNS:lohsport.com";
extlist = sk_X509_EXTENSION_new_null ();
ext = X509V3_EXT_conf (NULL, NULL, ext_name, ext_value);
if(ext == NULL)
{
*err = "Error creating subjectAltName extension";
goto failed;
}
sk_X509_EXTENSION_push (extlist, ext);
if (!X509_REQ_add_extensions (x509_req, extlist)){
*err = "Error adding subjectAltName to the request";
goto failed;
}
sk_X509_EXTENSION_pop_free (extlist, X509_EXTENSION_free);
It's compiling successfully but not works.
I would be grateful for any help.
UPDATE
Now i trying to work as in selfsing.c demo of OpenSSL Library:
1) I defined a function for adding extensions to CSR:
int add_ext(STACK_OF(X509_EXTENSION) *sk, int nid, char *value)
{
X509_EXTENSION *ex;
ex = X509V3_EXT_conf_nid(NULL, NULL, nid, value);
if (!ex)
return 0;
sk_X509_EXTENSION_push(sk, ex);
return 1;
}
2) Add this block to my function which generates CSR:
char Buffer[512];
// Format the value
sprintf (Buffer, "DNS:%s", info->common_name);
xts = sk_X509_EXTENSION_new_null();
add_ext(exts, NID_subject_alt_name, Buffer);
if(X509_REQ_add_extensions(x509_req, exts) != 1) {
*err = "X509_REQ_add_extensions() failed";
goto failed;
}
sk_X509_EXTENSION_pop_free(exts, X509_EXTENSION_free);
The code again compiles correctly, certificates are generated on the fly, but alternative names still don't work, and I get an error in the browser:
NET :: ERR_CERT_COMMON_NAME_INVALID
and I don’t see the alternative name information in the certificate details.
What other solutions can there be for a SAN problem? I can provide all the code for example on githab if it can help.

Hello I have done something like that (but I am working with X509 object not X509_REQ object like you :
static int cs_cert_set_subject_alt_name(X509 *x509_cert)
{
char *subject_alt_name = "IP: 192.168.1.1";
X509_EXTENSION *extension_san = NULL;
ASN1_OCTET_STRING *subject_alt_name_ASN1 = NULL;
int ret = -1;
subject_alt_name_ASN1 = ASN1_OCTET_STRING_new();
if (!subject_alt_name_ASN1) {
goto err;
}
ASN1_OCTET_STRING_set(subject_alt_name_ASN1, (unsigned char*) subject_alt_name, strlen(subject_alt_name));
if (!X509_EXTENSION_create_by_NID(&extension_san, NID_subject_alt_name, 0, subject_alt_name_ASN1)) {
goto err;
}
ASN1_OCTET_STRING_free(subject_alt_name_ASN1);
ret = X509_add_ext(x509_cert, extension_san, -1);
if (!ret) {
goto err;
}
X509_EXTENSION_free(extension_san);
err:
if (subject_alt_name_ASN1) ASN1_OCTET_STRING_free(subject_alt_name_ASN1);
if (extension_san) X509_EXTENSION_free(extension_san);
return -1;
}
It worked for me for the moment, I still have some trouble when I want to update an already existing certificate with a new subject alt name (because of a new ip address).
To see the result and check if the subject alt name is made :
$ openssl x509 -text -in cert.pem

Instead of X509V3_EXT_conf function try with X509V3_EXT_conf_nid function in which you pass the NID instead of name.
ext = X509V3_EXT_conf (NULL, NULL, ext_name, ext_value);
can be
ext = X509V3_EXT_conf_nid(NULL, NULL, NID_subject_alt_name, ext_value);
Your code might not be working because you might not be exactly matching the extension name with the one in OpenSSL code.

Related

Add CRL number extension to CRL using OpenSSL

For some client testing I need to generate certificates and revocation lists "on the fly". I am able to setup a revocation list with the CRL number extension using OpenSSL console commands and configuration files. However I can't make this work in code.
Here are my relevant functions:
X509* g_certificate[MAX_CERTIFICATE_COUNT];
EVP_PKEY* g_certificateKeyPair[MAX_CERTIFICATE_COUNT];
X509_CRL* g_crl;
UINT16 GenerateCrl(UINT16 issuerCertificateIndex, UINT16 crlNumber, INT32 lastUpdate, INT32 nextUpdate)
{
ASN1_TIME* lastUpdateTime = ASN1_TIME_new();
ASN1_TIME* nextUpdateTime = ASN1_TIME_new();
char crlNumberString[32];
int result = 1;
if (g_crl != NULL) X509_CRL_free(g_crl);
g_crl = X509_CRL_new();
result &= X509_CRL_set_version(g_crl, 1);
result &= X509_CRL_set_issuer_name(g_crl, X509_get_subject_name(g_certificate[issuerCertificateIndex]));
// there are multiple X509 certificate objects stored in memory, which I use to setup certificate chains
ASN1_TIME_set(lastUpdateTime, time(NULL) + lastUpdate);
ASN1_TIME_set(nextUpdateTime, time(NULL) + nextUpdate);
result &= X509_CRL_set1_lastUpdate(g_crl, lastUpdateTime);
result &= X509_CRL_set1_nextUpdate(g_crl, nextUpdateTime);
ASN1_TIME_free(lastUpdateTime);
ASN1_TIME_free(nextUpdateTime);
_itoa_s((int)crlNumber, crlNumberString, 10);
// my CRLs need to have the authority key identifier and CRL number extensions
result &= SetCrlExtension(issuerCertificateIndex, NID_authority_key_identifier, "keyid:always"); // this does add the auth key id extension
result &= SetCrlExtension(issuerCertificateIndex, NID_crl_number, crlNumberString); // this does not add CRL number the extension
result &= X509_CRL_sign(g_crl, g_certificateKeyPair[issuerCertificateIndex], EVP_sha256());
return result;
}
INT16 SetCrlExtension(UINT16 issuerCertificateIndex, INT16 extensionNid, const char* extensionData)
{
X509V3_CTX ctx;
X509_EXTENSION* extension;
lhash_st_CONF_VALUE conf; // actually I have no idea what this is for, probably it is not required here
int result = 1;
X509V3_set_ctx(&ctx, g_certificate[issuerCertificateIndex], NULL, NULL, g_crl, 0);
extension = X509V3_EXT_conf_nid(&conf, &ctx, extensionNid, (char*)extensionData);
result &= X509_CRL_add_ext(g_crl, extension, -1);
return result;
}
void SaveCrlAsPem(const char* fileName)
{
FILE* f;
fopen_s(&f, fileName, "wb");
PEM_write_X509_CRL(f, g_crl);
if (f != NULL) fclose(f);
}
So e.g.
GenerateCrl(1, 1234, -3600, 36000);
SaveCrlAsPem("crl.pem");
should result in a CRL having said extension. But it does only contain the authority key identifier extension. Everything else is fine. Im also adding the certificate extensions in basically the same way and dont't have any issues there.
So how can I get the CRL number attached to my CRL?
You mention that you are able to setup the CRL number extension from OpenSSL command line. You should probably take a look at the source code of the particular command then.
I haven't used CRLs, but I believe you are using the ca command. Looking for NID_crl_number in its source at apps/ca.c (from OpenSSL 1.1.1g) shows the following code:
/* Add any extensions asked for */
if (crl_ext != NULL || crlnumberfile != NULL) {
X509V3_CTX crlctx;
X509V3_set_ctx(&crlctx, x509, NULL, NULL, crl, 0);
X509V3_set_nconf(&crlctx, conf);
if (crl_ext != NULL)
if (!X509V3_EXT_CRL_add_nconf(conf, &crlctx, crl_ext, crl))
goto end;
if (crlnumberfile != NULL) {
tmpser = BN_to_ASN1_INTEGER(crlnumber, NULL);
if (!tmpser)
goto end;
X509_CRL_add1_ext_i2d(crl, NID_crl_number, tmpser, 0, 0);
ASN1_INTEGER_free(tmpser);
crl_v2 = 1;
if (!BN_add_word(crlnumber, 1))
goto end;
}
}
So, it seems you can use X509V3_EXT_CRL_add_nconf or X509_CRL_add1_ext_i2d for the purpose. Please refer the apps/ca.c of the OpenSSL version that you are using.
Another solution:
Maybe not the best approach, but you can probably launch the same OpenSSL commands as processes from code and process their output if it's acceptable.

Verifying message signature produce HEADER TOO LONG error

Background
I am trying to verify signature of a given binary file using openssl. Actual signing of binary hash is done by a 3rd party. Both 3rd party and I have the same exact certificate - they sent me the certificate.
I have verified health of my certificate by running openssl x509 -noout -text -inform DER -in CERT_PATH. This displays contents of cert correctly.
Following is my code so far - I based it on openssl wiki example here:
static std::vector<char> ReadAllBytes(char const* filename){
std::ifstream ifs(filename, std::ios::binary|std::ios::ate);
std::ifstream::pos_type pos = ifs.tellg();
std::vector<char> result(pos);
ifs.seekg(0, std::ios::beg);
ifs.read(result.data(), pos);
return result;
}
int main(int ac, const char * av[]) {
OpenSSL_add_all_algorithms();
ERR_load_crypto_strings();
// most of error check omitted for brevity
auto foundBinBytes = ReadAllBytes("BINARY_PATH");
auto foundSgnBytes = ReadAllBytes("SIGNATURE_PATH");
auto foundCertBytes = ReadAllBytes("CERT_PATH");
ERR_clear_error();
BIO *b = NULL;
X509 *c;
b = BIO_new_mem_buf(reinterpret_cast<const unsigned char *>(foundCertBytes.data()), foundCertBytes.size());
c = d2i_X509_bio(b, NULL);
EVP_MD_CTX* ctx = NULL;
ctx = EVP_MD_CTX_create();
const EVP_MD* md = EVP_get_digestbyname("SHA256");
int rc = EVP_DigestInit_ex(ctx, md, NULL);
EVP_PKEY *k = NULL;
k = X509_get_pubkey(c);
rc = EVP_DigestVerifyInit(ctx, NULL, md, NULL, k);
rc = EVP_DigestVerifyUpdate(ctx, reinterpret_cast<const unsigned char *>(foundBinBytes.data()), foundBinBytes.size());
ERR_clear_error();
rc = EVP_DigestVerifyFinal(ctx, reinterpret_cast<const unsigned char *>(foundSgnBytes.data()), foundSgnBytes.size());
ERR_print_errors_fp( stdout );
// openssl free functions omitted
if(ctx) {
EVP_MD_CTX_destroy(ctx);
ctx = NULL;
}
return 0;
}
Issue
Running this code produces following errors:
4511950444:error:0D07207B:asn1 encoding routines:ASN1_get_object:header too long:/.../crypto/asn1/asn1_lib.c:152:
4511950444:error:0D068066:asn1 encoding routines:ASN1_CHECK_TLEN:bad object header:/.../crypto/asn1/tasn_dec.c:1152:
4511950444:error:0D07803A:asn1 encoding routines:ASN1_ITEM_EX_D2I:nested asn1 error:/.../crypto/asn1/tasn_dec.c:314:Type=X509_SIG
Question
What is wrong with my setup/code? Did I miss something along the way?
You never check the errors when reading the files. You might have errors there (does the file "CERT_PATH" exist? Do you have read permissions? ...).
If "CERT_PATH" cannot be read, then foundCertBytes.data() is an empty byte array, and this explains the subsequent errors.
If you get these errors in a mosquitto secured server's log, check the config file.
My mosquitto.conf was containing :
require_certificate true
my meross device doesn't send certificate. Turning this to "false" solved my problem after restart

error:0906D06C:PEM routines:PEM_read_bio:no start

Getting this very annoying error. error:0906D06C:PEM routines:PEM_read_bio:no start
Code:
RSA* publickey = cWrapper.getPublicKey("C:/rsa-stuff/public.pem");
QByteArray plain = "The man in the black fled into the desert and the gunslinger followed...";
QByteArray encrypted = cWrapper.encryptRSA(publickey, plain);
In encryptRSA():
const char* publicKeyStr = data.constData();
qDebug() << publicKeyStr;
BIO* bio = BIO_new_mem_buf((void*)publicKeyStr, -1);
BIO_set_flags(bio, BIO_FLAGS_BASE64_NO_NL);
RSA* rsaPubKey = PEM_read_bio_RSAPublicKey(bio, NULL, NULL, NULL);
if(!rsaPubKey) {
qDebug() << "Could not load public key, " << ERR_error_string(ERR_get_error(), NULL); // error is here
}
BIO_free(bio);
This is how I read file:
QByteArray data;
QFile file(filename);
if(!file.open(QFile::ReadOnly))
{
printf("Error reading file: %s\n", file.errorString());
return data;
}
data = file.readAll();
file.close();
return data;
When I print out publicKeyStr, looks fine. This is notepad++ view with all characters enabled:
Anyone know what I am doing wrong? Super annoying issue :(
First of all, it's not this problem because I don't get the trusted part. Anyhow, I did try all the "solutions" and none of them worked, same error.
Your RSA public key is in SubjectPublicKeyInfo PEM format, but you are trying to read it using PEM_read_bio_RSAPublicKey which tries to read a PEM RSA key in PKCS#1 format. Try using PEM_read_bio_RSA_PUBKEY instead.
https://www.openssl.org/docs/man1.1.0/crypto/PEM_read_bio_RSAPublicKey.html
I got that same error on an openSSL1.1.0f I ported. The error showed up in my logger when reading out the root certificate from an mqtt client connection, until I figured out that I had forwarded the ERR_put_error() directly to my logger, whereas in openssl - the "real" error handling is kept in an ERR_STATE error buffer, and so sometimes (like in this case), errors are "expected", and the ERR_STATE error buffer is cleared (before anyone should check it).
in crypto/pem/pem_info.c, line 65:
i = PEM_read_bio(bp, &name, &header, &data, &len);
if (i == 0) {
error = ERR_GET_REASON(ERR_peek_last_error());
if (error == PEM_R_NO_START_LINE) {
ERR_clear_error();
break;
}
goto err;
meaning it runs througth the BIO_gets inside the PEM_read_bio until it returns zero, and if you get this PEM_R_NO_START_LINE, then thats just a way of saying its done.
By that time though, the error had already landed in my logger. So for anyone being confused by errors he or she is forwarding directly from ERR_put_error, use the ERR_print_errors_fp(stderr); in your errorhandling routine instead. In my case, as I dont have a stderr, I made a patched version of it, like:
void errorhandling()
{
unsigned long l;
char buf[256];
const char *file, *data;
int line, flags;
while ((l = ERR_get_error_line_data(&file, &line, &data, &flags)) != 0)
{
ERR_error_string_n(l, buf, sizeof buf);
printf("%s:%s:%d:%s\n", buf, file, line, (flags & ERR_TXT_STRING) ? data : "");
}
}

How to avoid SIGABRT when generating RSA Signature at EVP_SignFinal

I'm trying to generate a RSA Signature with libopenssl for c++:
But when I run my code, I get a SIGABRT. I did some deep debugging into libopenssl internal stuff to see where the Segfault comes from. I'll come to this later on.
First I want to make clear, that the RSA PrivateKey was successfully loaded from a .pem file. So Im pretty sure that's not the problem's origin.
So my question is: How to avoid the SIGABRT and what is the cause of it ?
I'm doing this for my B.Sc. Thesis so I really appreciate your help :)
Signature Generation Function:
DocumentSignature* RSASignatureGenerator::generateSignature(ContentHash* ch, CryptographicKey* pK) throw(PDVSException) {
OpenSSL_add_all_algorithms();
OpenSSL_add_all_ciphers();
OpenSSL_add_all_digests();
if(pK == nullptr)
throw MissingPrivateKeyException();
if(pK->getKeyType() != CryptographicKey::KeyType::RSA_PRIVATE || !dynamic_cast<RSAPrivateKey*>(pK))
throw KeyTypeMissmatchException(pK->getPem()->getPath().string(), "Generate RSA Signature");
//get msg to encrypt
const char* msg = ch->getStringHash().c_str();
//get openssl rsa key
RSA* rsaPK = dynamic_cast<RSAPrivateKey*>(pK)->createOpenSSLRSAKeyObject();
//create openssl signing context
EVP_MD_CTX* rsaSignCtx = EVP_MD_CTX_create();
EVP_PKEY* priKey = EVP_PKEY_new();
EVP_PKEY_assign_RSA(priKey, rsaPK);
//init ctxt
if (EVP_SignInit(rsaSignCtx, EVP_sha256()) <=0)
throw RSASignatureGenerationException();
//add data to sign
if (EVP_SignUpdate(rsaSignCtx, msg, std::strlen(msg)) <= 0) {
throw RSASignatureGenerationException();
}
//create result byte signature struct
DocumentSignature::ByteSignature* byteSig = new DocumentSignature::ByteSignature();
//set size to max possible
byteSig->size = EVP_MAX_MD_SIZE;
//alloc buffer memory
byteSig->data = (unsigned char*)malloc(byteSig->size);
//do signing
if (EVP_SignFinal(rsaSignCtx, byteSig->data, (unsigned int*) &byteSig->size, priKey) <= 0)
throw RSASignatureGenerationException();
DocumentSignature* res = new DocumentSignature(ch);
res->setByteSignature(byteSig);
EVP_MD_CTX_destroy(rsaSignCtx);
//TODO open SSL Memory leaks -> where to free open ssl stuff?!
return res;
}
RSA* rsaPK = dynamic_cast(pK)->createOpenSSLRSAKeyObject();
virtual RSA* createOpenSSLRSAKeyObject() throw (PDVSException) override {
RSA* rsa = NULL;
const char* c_string = _pem->getContent().c_str();
BIO * keybio = BIO_new_mem_buf((void*)c_string, -1);
if (keybio==NULL)
throw OpenSSLRSAPrivateKeyObjectCreationException(_pem->getPath());
rsa = PEM_read_bio_RSAPrivateKey(keybio, &rsa, NULL, NULL);
if(rsa == nullptr)
throw OpenSSLRSAPrivateKeyObjectCreationException(_pem->getPath());
//BIO_free(keybio);
return rsa;
}
SigAbrt origin in file openssl/crypto/mem.c
void CRYPTO_free(void *str, const char *file, int line)
{
if (free_impl != NULL && free_impl != &CRYPTO_free) {
free_impl(str, file, line);
return;
}
#ifndef OPENSSL_NO_CRYPTO_MDEBUG
if (call_malloc_debug) {
CRYPTO_mem_debug_free(str, 0, file, line);
free(str);
CRYPTO_mem_debug_free(str, 1, file, line);
} else {
free(str);
}
#else
free(str); // <<<<<<< HERE
#endif
}
the stacktrace
stacktrace screenshot from debugger (clion - gdb based)
I just found the Bug (and Im really not sure if this is a libopenssl bug..)
//set size to max possible
byteSig->size = EVP_MAX_MD_SIZE;
//alloc buffer memory
byteSig->data = (unsigned char*)malloc(byteSig->size);
The problem was when I set the buffer size to EVP_MAX_MD_SIZE!
The (in my opinion) very very strange thing is, that you have to keep the size uninitialized! (not even set to 0 - just "size_t size;" ).
Strange thing here is that then you also HAVE TO allocate memory just like I did. I dont understand this because then an undefined size of memory gets allocated..
What the really weird is that libopenssl internally sets the size back to 0 and allocates the memory itself.. (I detected this by browsing the libopenssl source code)

SSL signature verrification cross language issue

I have the following code in an C websocket server application I have. The code performs ssl signature verification on a message with a given public key. The code works fine in the C application, but recently I started writing it on c++.The issue I encountered is that the same code, that is below, is in both applications, without change, both times receiving the same input data, but the one compiled with c++ yields SSL error bad signature.
Here is the code:
int verifyMessageSignature(const char* decoded_message, int pos,
unsigned char* signature, char* publicKey)
{
SSL_library_init();
SSL_load_error_strings();
ERR_load_BIO_strings();
// OpenSSL_add_all_algorithms()
if (!publicKey)
{
printf("publicKey is null\n");
}
BIO* keyBio = BIO_new_mem_buf(publicKey, -1);
if(!keyBio)
{
printf("failed to created BIO\n");
printError(ERR_get_error());
}
BIO_set_mem_eof_return(keyBio, 0);
RSA* rsa = PEM_read_bio_RSA_PUBKEY(keyBio, NULL, NULL, NULL);
if (!rsa)
{
printf("Error in PEM_read_bio_RSA_PUBKEY\n");
printError(ERR_get_error());
}
EVP_MD_CTX *mdctx = NULL;
if (!(mdctx = EVP_MD_CTX_create()))
{
printf("Error in ctx\n");
printError(ERR_get_error());
}
EVP_PKEY* pk = EVP_PKEY_new();
if (EVP_PKEY_set1_RSA(pk, rsa) != 1)
{
printf("err in EVP_PKEY_set1_RSA\n");
printError(ERR_get_error());
}
if (EVP_DigestVerifyInit(mdctx, NULL, EVP_sha1(), NULL, pk) != 1)
{
printf("error in EVP_DigestVerifyInit\n");
printError(ERR_get_error());
}
if (EVP_DigestVerifyUpdate(mdctx, decoded_message, pos) != 1)
{
printf("error in EVP_DigestVerifyUpdate\n");
printError(ERR_get_error());
}
if (EVP_DigestVerifyFinal(mdctx, signature, 512) == 1)
{
/* Success */
printf("Successful verification!\n");
}
else
{
/* Failure */
printf("Unsuccessful verification!\n");
printError(ERR_get_error());
BIO_free_all(keyBio);
RSA_free(rsa);
EVP_PKEY_free(pk);
EVP_MD_CTX_destroy(mdctx);
ERR_free_strings();
return 1;
}
BIO_free_all(keyBio);
RSA_free(rsa);
EVP_PKEY_free(pk);
EVP_MD_CTX_destroy(mdctx);
ERR_free_strings();
return 0;
}
This code works fine in C. it successfully verifies the signature in my tests, whilst the same code, with the same input data (keys, messages, etc..) in c++ yields bad signature.
I am compiling under Ubuntu, using gcc and g++ (latest)
What could be causing this issue?
Solved. The issue was that i was using std::string to pass the decoded message around, which somehow was fucking it up. I switched the c++ code to use also char* strings for this section and is fine now.
Thanks for the replies, I hope someone else finds this useful.
Edit:
PS: I am using another function for generating the decoded message itself. Usage of std::string there was causing this issue.