I need to get the raw public key bytes (with openssl this is 'der' format) from a public key SecKeyRef to implement public key pinning with. The actual pinning function is already written, cannot change, and takes a unsigned char *pubkey, size_t pubkeylen with which to compare it's pins against.
This is what I have tried so far:
SecTrustRef trust;
OSStatus ret = SSLCopyPeerTrust(ctx, &trust);
SecKeyRef keyRef = SecTrustCopyPublicKey(trust);
CFDataRef publicKeyBits;
OSStatus success = SecItemExport(keyRef, kSecFormatOpenSSL, 0, NULL, &publicKeyBits) == errSecSuccess;
//OSStatus success = SecItemExport(keyRef, kSecFormatPEMSequence, 0, NULL, &publicKeyBits) == errSecSuccess;
size_t pubkeylen = CFDataGetLength(publicKeyBits);
printf("pubkeylen: %d\n", pubkeylen);
unsigned char *pubkey = CFDataGetBytePtr(publicKeyBits);
The problem is this does not seem to be DER format at all, I expect pubkeylen to be 550 for this particular key, but it prints 526, and the data isn't correct either. I also tried kSecFormatPEMSequence and then base64 decoding it with the same results (it does change into the identical 526 length array as before, so at least it's consistent).
edit:
It looks like when exported to kSecFormatPEMSequence the PEM contains:
-----BEGIN RSA PUBLIC KEY-----
instead of the expected
-----BEGIN PUBLIC KEY-----
Which is a different format that does not base64-decode to DER, maybe it could be parsed with more API calls back into a DER format someway?
What am I missing? Thanks for any help!
edit:
I have also tried the following:
CFDataRef d_tag = CFDataCreate(NULL, "com.bob", 7);
const void *cKeys[] = {kSecClass, kSecAttrKeyType,
kSecAttrKeyClass, kSecValueData,
kSecReturnData
};
const void *cValues[] = {kSecClassKey, kSecAttrKeyTypeRSA,
kSecAttrKeyClassPublic, keyRef,
kCFBooleanTrue
};
CFDictionaryRef saveDict = CFDictionaryCreate(NULL, cKeys, cValues,
5L, NULL, NULL);
OSStatus secStatus = SecItemCopyMatching(saveDict, (CFTypeRef)&publicKeyBits);
if (secStatus == errSecDuplicateItem ) {
printf("errSecDuplicateItem\n");
SecItemDelete(saveDict);
secStatus = SecItemAdd(saveDict, (CFTypeRef)&publicKeyBits );
}
if ( secStatus != noErr ) {
printf("return NULL\n");
return NULL;
}
size_t pubkeylen = CFDataGetLength(publicKeyBits);
printf("pubkeylen: %d\n", pubkeylen);
unsigned char *pubkey = CFDataGetBytePtr(publicKeyBits);
And the results are even worse, pubkeylen is consistently 96, but the data in pubkey is always different!
I have also tried converting https://github.com/datatheorem/TrustKit/blob/master/TrustKit/Pinning/public_key_utils.m#L65 from objective-c, I get:
OSStatus resultAdd, resultDel = noErr;
CFDataRef d_tag = CFDataCreate(NULL, "com.bobe", 9);
const void *peerPublicKeyAddcKeys[] = {kSecClass, kSecAttrApplicationTag,
kSecValueRef, kSecAttrAccessible, kSecReturnData
};
const void *peerPublicKeyAddcValues[] = {kSecClassKey, d_tag,
keyRef, kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly,
kCFBooleanTrue
};
CFDictionaryRef peerPublicKeyAdd = CFDictionaryCreate(NULL, peerPublicKeyAddcKeys, peerPublicKeyAddcValues,
5L, NULL, NULL);
const void *publicKeyGetcKeys[] = {kSecClass, kSecAttrApplicationTag,
kSecReturnData
};
const void *publicKeyGetcValues[] = {kSecClassKey, d_tag,
kCFBooleanTrue
};
CFDictionaryRef publicKeyGet = CFDictionaryCreate(NULL, publicKeyGetcKeys, publicKeyGetcValues,
3L, NULL, NULL);
resultAdd = SecItemAdd(peerPublicKeyAdd, &publicKeyBits);
resultDel = SecItemDelete(publicKeyGet);
if ((resultAdd != errSecSuccess) || (resultDel != errSecSuccess))
{
// Something went wrong with the Keychain we won't know if we did get the right key data
printf("return NULL\n");
return NULL;
}
And this time publicKeyBits is always NULL, I'm starting to tear my hair out here!
Related
migrated key loading api's using openssl 3.0.7, failed to give same signing file.
we are migrating the our application code from openssl 1.1.1q(case 1) to 3.0.7(case 2).
we are trying to sign the same file using above openssl version but migrated code or case2 is we are suspecting a key loading is not proper.
we are fetching n, e , d values from our private key and loading to PKEY in both case 1 and 2.
Please let me know where we missed or any suggestion on key loading part.
case 1:KEY loading using openssl 1.1.1q:where privateModulus and privateExponent are from our private key
EVP_PKEY *pk8_key;
pk8_key = EVP_PKEY_new();
RSA *rsa = RSA_new();
BIGNUM *rsa_n, *rsa_e, *rsa_d;
rsa_n = BN_new();
rsa_n=BN_bin2bn((const unsigned char*)privateModulus,(int)expModLength,rsa_n);
if (!(rsa_n))
{
throw_exception("Error in assigning modulus(n)");
}
rsa_d = BN_new();
rsa_d=BN_bin2bn((const unsigned char*)privateExponent,(int)expModLength,rsa_d);
if (!(rsa_d))
{
throw_exception("Error in assigning exponent(d)");
}
rsa_e = BN_new();
if(!BN_hex2bn(&rsa_e,"0x03"))
{
throw_exception("Error in assigning exponent(d)");
}
// Note - memory management of BIGNUMS are transfered and freed with RSA object.
int ret = RSA_set0_key(rsa,rsa_n,rsa_e,rsa_d);
if (1 != ret)
{
throw_exception("Error creating RSA key from inputs");
}
RSA_blinding_off(rsa);
rsa_assign_status = EVP_PKEY_assign_RSA(pk8_key, rsa);
case2: KEY loading using openssl 3.0.7:where privateModulus and privateExponent are from our private key
EVP_PKEY* pk8_key = NULL;
EVP_PKEY_CTX* ctx = NULL;
BIGNUM* rsa_n =NULL , * rsa_d=NULL;
OSSL_PARAM* params = NULL;
OSSL_PARAM_BLD* bld = NULL;
OSSL_LIB_CTX *libctx = NULL;
int result = 0,i;
unsigned int rsa_e = 0;
rsa_n = BN_new();
rsa_n=BN_bin2bn((const unsigned char*)privateModulus,(int)expModLength,rsa_n);
if (!(rsa_n))
{
throw_exception("Error in assigning modulus(n)");
}
rsa_d = BN_new();
rsa_d=BN_bin2bn((const unsigned char*)privateExponent,(int)expModLength,rsa_d);
if (!(rsa_d))
{
throw_exception("Error in assigning exponent(d)");
}
rsa_e = 0x03;
bld = OSSL_PARAM_BLD_new();
OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_RSA_N, rsa_n);
OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_RSA_D, rsa_d);
//OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_RSA_E, rsa_e);
OSSL_PARAM_BLD_push_uint(bld, OSSL_PKEY_PARAM_RSA_E, rsa_e);
params = OSSL_PARAM_BLD_to_param(bld);
libctx = OSSL_LIB_CTX_new();
ctx = EVP_PKEY_CTX_new_from_name( libctx, "RSA", NULL );
if (ctx == NULL || EVP_PKEY_fromdata_init(ctx) <= 0
|| EVP_PKEY_fromdata(ctx, &pk8_key, EVP_PKEY_KEYPAIR, params) <= 0)
{
printf("error");
}
Not sure whether EVP_PKEY_fromdata will assign params same as EVP_PKEY_assign_RSA api.
we tried to set params different in many ways but not sure whether fromdata will assign params same as assign_rsa api.
we tried to sign the same file using both case and expecting same result but using our new way of constructing key we are unable to get same sign.
Please let us know about our key construction part and for same input file signing should be same or not.
In my get_keypair function,I want to create an EC_KEY structure by EC_KEY_new_by_curve_name(NID_secp256k1) .By doing so,I got a crash on my vs2019 with no details.Below comes my code.
bool get_keypair(EC_KEY** key, bool getcompressed,uint8_t* rawprikey, uint8_t rawprikeylen) {
BIGNUM* priv; //prikey bignum
BN_CTX* ctx; //hold the operation
const EC_GROUP* group;
EC_POINT* pub; //pubkey point st
*key = EC_KEY_new_by_curve_name(NID_secp256k1); //secp256k1, !!!crashed here
if (*key == 0) {
printf("new ec key failed");
exit(-1);
}
priv = BN_new(); //create bignum
BN_bin2bn(rawprikey, 32, priv); //fill the bigbum with prikey
EC_KEY_set_private_key(*key, priv); //put prikey bignum to key pair
ctx = BN_CTX_new(); //create context
BN_CTX_start(ctx); //init context
group = EC_KEY_get0_group(*key); //G
pub = EC_POINT_new(group); //create pub st
EC_POINT_mul(group, pub, priv, NULL, NULL, ctx); //P = n * G
EC_KEY_set_public_key(*key, pub); //fill the key pair, !!!crashed here
EC_KEY_set_conv_form(*key, getcompressed ? POINT_CONVERSION_COMPRESSED : POINT_CONVERSION_UNCOMPRESSED); //compressd or not
uint64_t pubkeylen = i2o_ECPublicKey(*key, NULL);
uint8_t *pubkey = calloc(pubkeylen, sizeof(uint8_t));
uint8_t* pub_copy = pubkey;
if (i2o_ECPublicKey(*key, &pub_copy) != pubkeylen) {
puts("Unable to decode public key");
return false;
}
/*for (int i = 0; i < *pubkeylen; i++) {
printf("%02X", (*pubkey)[i]);
}*/
EC_POINT_free(pub);//free all
BN_CTX_end(ctx);
BN_CTX_free(ctx);
BN_clear_free(priv);
return true;
}
Just a proof of work,No checks was added.
And I keet rerun the program many times,saw that the program will crash at different code.
Below are some pictures captured when the program crashed.
crashed at EC_KEY_new_by_curve_name
crashed at EC_KEY_set_public_key
crashed on main exit
linked librares
You need to allocate "EC_KEY** key" variable before call get_keypair() function.
This question already has answers here:
Use OpenSSL RSA key with .Net
(1 answer)
How to generate RSA private key using OpenSSL?
(3 answers)
Closed 5 years ago.
I need to write a C program that generates an RSA key, and saves an X.509 public key in DER format and a PKCS#8 private key in DER format. I've used Google, but haven't really found much. What I have so far is this:
#include <stdio.h>
#include <openssl/rsa.h>
#include <openssl/pem.h>
void main() {
int ret = 0;
RSA *r = NULL;
BIGNUM *bne = NULL;
BIO *bp_public = NULL, *bp_private = NULL;
int bits = 2048;
unsigned long e = RSA_F4;
// Generate the RSA key
printf("Generating RSA key...\n");
bne = BN_new();
ret = BN_set_word(bne, e);
if(ret != 1) {
goto free_all;
}
r = RSA_new();
ret = RSA_generate_key_ex(r, bits, bne, NULL);
if(ret != 1) {
goto free_all;
}
// Save the public key in PEM format
printf("Writing key files...\n");
bp_public = BIO_new_file("public.pem", "w+");
ret = PEM_write_bio_RSAPublicKey(bp_public, r);
if(ret != 1) {
goto free_all;
}
// Save the private key in PEM format
bp_private = BIO_new_file("private.pem", "w+");
ret = PEM_write_bio_RSAPrivateKey(bp_private, r, NULL, NULL, 0, NULL, NULL);
// Free everything
free_all:
BIO_free_all(bp_public);
BIO_free_all(bp_private);
RSA_free(r);
BN_free(bne);
printf("Done!\n");
}
This is obviously writing the keys in PEM format. I also need to be able to actually have the data in memory in the code, not just write it directly to a file, as there's some other stuff I need to do with the public key.
Thank you for any help
Your question is a little ambiguous in what you actually mean by "saves an X.509 public key in DER format". Assuming you actually mean "save it as a SubjectPublicKeyInfo structure" (which is the bit of an X.509 certificate that holds public keys) then you should use i2d_RSA_PUBKEY (or i2d_RSA_PUBKEY_fp or i2d_RSA_PUBKEY_bio) to write it out (no need to convert it to an EVP_PKEY first).
For the PKCS#8 private key in DER format, your current method is incorrect for PEM format. The PEM_write_bio_RSAPrivateKey() function will write this out in traditional format (not PKCS#8).
I assume you don't want to do anything complicated like encrypting the key first. For this one you will need to convert it to an EVP_PKEY (using EVP_PKEY_assign_RSA() as mentioned by #JawguyChooser). Next you obtain a PKCS8_PRIV_KEY_INFO structure using the (sadly undocumented) function EVP_PKEY2PKCS8.
PKCS8_PRIV_KEY_INFO *EVP_PKEY2PKCS8(EVP_PKEY *pkey);
You need to free this structure when your done using PKCS8_PRIV_KEY_INFO_free(). Next write out the PKCS8 DER using i2d_PKCS8_PRIV_KEY_INFO() (or i2d_PKCS8_PRIV_KEY_INFO_fp() or i2d_PKCS8_PRIV_KEY_INFO_bio).
See the man page for info on various of these functions:
https://www.openssl.org/docs/man1.1.0/crypto/i2d_RSAPublicKey.html
I think you're going to need to convert your key to an EVP_PKEY using EVP_PKEY_assign_RSA. Then you can use i2d_PUBKEY_bio to write out to a bio.
The following modification of your code works for me:
include the header file <openssl/evp.h>
declare BIO for writing out the public_der
create EVP_PKEY structure with EVP_PKEY_new()
convert using EVP_PKEY_assign_RSA
write out to bio with i2d_PUBKEY_bio
In context:
#include <stdio.h>
#include <openssl/rsa.h>
#include <openssl/pem.h>
#include <openssl/evp.h>
void main() {
int ret = 0;
RSA *r = NULL;
BIGNUM *bne = NULL;
BIO *bp_public = NULL, *bp_private = NULL, *bp_public_der = NULL;
int bits = 2048;
unsigned long e = RSA_F4;
// Generate the RSA key
printf("Generating RSA key...\n");
bne = BN_new();
ret = BN_set_word(bne, e);
if(ret != 1) {
goto free_all;
}
r = RSA_new();
ret = RSA_generate_key_ex(r, bits, bne, NULL);
if(ret != 1) {
goto free_all;
}
// Save the public key in PEM format
printf("Writing key files...\n");
bp_public = BIO_new_file("public.pem", "w+");
ret = PEM_write_bio_RSAPublicKey(bp_public, r);
if(ret != 1) {
goto free_all;
}
// Save the private key in PEM format
bp_private = BIO_new_file("private.pem", "w+");
ret = PEM_write_bio_RSAPrivateKey(bp_private, r, NULL, NULL, 0, NULL, NULL);
// Save in DER
EVP_PKEY *evp = EVP_PKEY_new();
ret = EVP_PKEY_assign_RSA(evp, r);
if(ret != 1){
printf("failure %i\n", ret);
}
bp_public_der = BIO_new_file("public.key", "w+");
ret = i2d_PUBKEY_bio(bp_public_der, evp);
// Free everything
free_all:
BIO_free_all(bp_public);
BIO_free_all(bp_public_der);
BIO_free_all(bp_private);
RSA_free(r);
BN_free(bne);
printf("Done!\n");
}
You can now find the DER of the public key in public.key. And you should be able to do the same thing for the private.
Hope this helps.
I have a server which would listen on HTTPS using OpenSSL. For this, I have to provide the certificate to use. However, the current implementation uses a filename to be provided to the OpenSSL API.
I want the certificate information to be read from memory, so that I don't have to ship the certificate file opening. I tried to google, but I didn't come up with any options.
Is is possible? If so, how do I read certificate files from memory instead of a file using OpenSSL?
EDIT: The following was moved from the comments to the question.
// CURRENT
void start_server()
{
const char *fileName = "cert_and_key.pem";
set_server_ssl_file(fileName);
}
set_server_ssl_file(const char *fileName)
{
//initialize context
SSL_CTX_use_certificate_file(CTX, pem, SSL_FILETYPE_PEM);
SSL_CTX_use_PrivateKey_file(CTX, pem, SSL_FILETYPE_PEM);
}
//REQUIRED
void start_server()
{
const char *cert = "--BEGIN CERTIFICATE--............";
const char *key = "--BEGIN RSA PRIVATE KEY--.......";
set_server_ssl_options(cert, key);
}
set_server_ssl_options(const char *cert, const char *key)
{
//IMPLEMENTATION REQUIRED
}
The following code did the job for me:
SSL_CTX *CTX;
X509 *cert = NULL;
RSA *rsa = NULL;
BIO *cbio, *kbio;
const char *cert_buffer = "";
const char *key_buffer = "";
cbio = BIO_new_mem_buf((void*)cert_buffer, -1);
cert = PEM_read_bio_X509(cbio, NULL, 0, NULL);
assert(cert != NULL);
SSL_CTX_use_certificate(CTX, cert);
kbio = BIO_new_mem_buf((void*)key_buffer, -1);
rsa = PEM_read_bio_RSAPrivateKey(kbio, NULL, 0, NULL);
assert(rsa != NULL);
SSL_CTX_use_RSAPrivateKey(CTX, rsa);
The other snippets will only load one certificate. The content of files like http://curl.haxx.se/ca/cacert.pem that contain a lot of different certificates need a new approach. This is adapted from openssl 1.0.1p (mostly openssl-1.0.1p\crypto\x509\by_file.c, char* buf contains the content of a *.pem file, ctx is a boost::asio::ssl::context), add error handling on your own:
BIO *cbio = BIO_new_mem_buf((void*)buf, (int)length);
X509_STORE *cts = SSL_CTX_get_cert_store(ctx.native_handle());
if(!cts || !cbio)
return false;
X509_INFO *itmp;
int i, count = 0, type = X509_FILETYPE_PEM;
STACK_OF(X509_INFO) *inf = PEM_X509_INFO_read_bio(cbio, NULL, NULL, NULL);
if (!inf)
{
BIO_free(cbio);//cleanup
return false;
}
//itterate over all entries from the pem file, add them to the x509_store one by one
for (i = 0; i < sk_X509_INFO_num(inf); i++) {
itmp = sk_X509_INFO_value(inf, i);
if (itmp->x509) {
X509_STORE_add_cert(cts, itmp->x509);
count++;
}
if (itmp->crl) {
X509_STORE_add_crl(cts, itmp->crl);
count++;
}
}
sk_X509_INFO_pop_free(inf, X509_INFO_free); //cleanup
BIO_free(cbio);//cleanup
unsigned char *cert_data = (....);
int cert_len = (....);
X509 *cert = d2i_X509(NULL, &cert_data, cert_len);
SSL_CTX_use_certificate(ctx, cert);
unsigned char *pkey_data = /* ... */;
int pkey_len = /* ... */;
RSA *pkey = d2i_RSAPrivateKey(NULL, &pkey_data, pkey_len);
SSL_CTX_use_RSAPrivateKey(ctx, pkey);
Don't forget & before cert_data and pkey_data - and note that OpenSSL modifies these pointers.
There is another response that uses X509_STORE_add_cert, which is up-voted but incorrect. That answer is a way to do SSL_CTX_load_verify_locations in memory, but does not load the server certificate chain. Replies to that comment also indicate that it does not work.
The following code is a load-from-memory implementation of SSL_CTX_use_certificate_chain_file based on the implementation of that function in OpenSSL:
bool load_cert_chain_from_shared_mem(SSL_CTX *context, const char *cert_buffer)
{
BIO *cbio = BIO_new_mem_buf((void*)cert_buffer, -1);
if (!cbio)
return false;
X509_INFO *itmp;
int i, count = 0, type = X509_FILETYPE_PEM;
STACK_OF(X509_INFO) *inf = PEM_X509_INFO_read_bio(cbio, NULL, NULL, NULL);
if (!inf)
{
BIO_free(cbio);
return false;
}
/* Iterate over contents of the PEM buffer, and add certs. */
BOOL first = TRUE;
for (i = 0; i < sk_X509_INFO_num(inf); i++) {
itmp = sk_X509_INFO_value(inf, i);
if (itmp->x509)
{
/* First cert is server cert. Remaining, if any, are intermediate certs. */
if (first)
{
first = FALSE;
/*
* Set server certificate. Note that this operation increments the
* reference count, which means that it is okay for cleanup to free it.
*/
if (!SSL_CTX_use_certificate(context, itmp->x509))
goto Error;
if (ERR_peek_error() != 0)
goto Error;
/* Get ready to store intermediate certs, if any. */
SSL_CTX_clear_chain_certs(context);
}
else
{
/* Add intermediate cert to chain. */
if (!SSL_CTX_add0_chain_cert(context, itmp->x509))
goto Error;
/*
* Above function doesn't increment cert reference count. NULL the info
* reference to it in order to prevent it from being freed during cleanup.
*/
itmp->x509 = NULL;
}
}
}
sk_X509_INFO_pop_free(inf, X509_INFO_free);
BIO_free(cbio);
return true;
Error:
sk_X509_INFO_pop_free(inf, X509_INFO_free);
BIO_free(cbio);
return false;
}
I'm implementing a process elevation helper for Windows. It's a program that will run in elevated mode and launch other programs with administrator privileges without displaying additional UAC prompts. For security reasons, I want to make sure only binaries that are digitally signed with my company's Authenticode key can be executed.
The WinVerifyTrust function gets me halfway there, but it only ensures that a binary is signed by some key that is part of Microsoft's chain of trust. Is there a relatively simple way to perform the Authenticode verification AND ensure that it is signed by our private key?
I believe what you're looking for is CryptQueryObject.
With it you should be able to pull the involved certificate out of a PE, and do any additional checks you want.
By way of example, this will get you to a HCRYPTMSG. From there you can use CryptMsgGetParam to pull out whatever you want. I'd hoped to make something more 'robust', but these APIs are pretty hairy insomuch as they require a lot of branching to handle all their return cases.
So, here's a p/invoke-rific c# example (I started in C, but that was basically unreadable):
static class Crypt32
{
//Omitting flag constants; you can look these up in WinCrypt.h
[DllImport("CRYPT32.DLL", EntryPoint = "CryptQueryObject", CharSet = CharSet.Auto, SetLastError = true)]
public static extern bool CryptQueryObject(
int dwObjectType,
IntPtr pvObject,
int dwExpectedContentTypeFlags,
int dwExpectedFormatTypeFlags,
int dwFlags,
out int pdwMsgAndCertEncodingType,
out int pdwContentType,
out int pdwFormatType,
ref IntPtr phCertStore,
ref IntPtr phMsg,
ref IntPtr ppvContext);
}
class Program
{
static void Main(string[] args)
{
//Path to executable here
// I tested with MS-Office .exe's
string path = "";
int contentType;
int formatType;
int ignored;
IntPtr context = IntPtr.Zero;
IntPtr pIgnored = IntPtr.Zero;
IntPtr cryptMsg = IntPtr.Zero;
if (!Crypt32.CryptQueryObject(
Crypt32.CERT_QUERY_OBJECT_FILE,
Marshal.StringToHGlobalUni(path),
Crypt32.CERT_QUERY_CONTENT_FLAG_ALL,
Crypt32.CERT_QUERY_FORMAT_FLAG_ALL,
0,
out ignored,
out contentType,
out formatType,
ref pIgnored,
ref cryptMsg,
ref context))
{
int error = Marshal.GetLastWin32Error();
Console.WriteLine((new Win32Exception(error)).Message);
return;
}
//expecting '10'; CERT_QUERY_CONTENT_PKCS7_SIGNED_EMBED
Console.WriteLine("Context Type: " + contentType);
//Which implies this is set
Console.WriteLine("Crypt Msg: " + cryptMsg.ToInt32());
return;
}
To get the certificate information from signed code use this:
using System.Security.Cryptography.X509Certificates;
X509Certificate basicSigner = X509Certificate.CreateFromSignedFile(filename);
X509Certificate2 cert = new X509Certificate2(basicSigner);
Then you can get the cert details like this:
Console.WriteLine(cert.IssuerName.Name);
Console.WriteLine(cert.SubjectName.Name);
// etc
these are some of the nastiest APIs I've ever worked with
A word of warning: it's worse than you already thought.
At least since introducing SHA-256 signing (has this always been the case?), it's possible for Authenticode to have multiple signatures. They're not encoded as multiple signatures in the PKCS-7 signature message; instead, they're unauthenticated message attributes of type OID_NESTED_SIGNATURE, each containing another complete PKCS-7 signature message.
WinVerifyTrust will tell you the file is valid if any of the signatures are valid and come from a trusted certificate chain. However it won't tell you which of the signatures was valid. If you then use CryptQueryObject to read the full PKCS-7 message, and only look at the certificate for the primary signature (as in the code samples here and on MSDN), you're not necessarily looking at a verified certificate. The associated signature might not match the executable, and/or the certificate might not have a trusted CA chain.
If you're using the details of the primary signature to validate that the certificate is one your software trusts, you're vulnerable to a situation where WinVerifyTrust is trusting a secondary signature, but your code is checking the primary signature's certificate is what you expected, and you haven't noticed that the signature from the primary certificate is nonsense. An attacker could use your public certificate without owning its private key, combined with some other code-signing certificate issued to someone else, to bypass a publisher check this way.
From Win8 onwards, WinVerifyTrust can optionally validate specific signatures, so you should be able to iterate the signatures to find one that is valid and one that satisfies your requirements.
If you have to be Win7-compatible, though, as far as I know the best you can manage is MsiGetFileSignatureInformation. From experimentation (as for everything else here, the actual documentation is frustratingly woolly), it seems to return the trusted certificate when WinVerifyTrust trusts one. But if there isn't a trusted signature, it returns the primary signature's certificate anyway, so you still have to use WinVerifyTrust to check that first.
Of course there also plenty of possible time-of-check/time-of-use problems here.
found the solution here:
http://www.ucosoft.com/how-to-program-to-retrieve-the-authenticode-information.html
here it is with indentation:
#define _UNICODE 1
#define UNICODE 1
#include <windows.h>
#include <tchar.h>
#include <wincrypt.h>
#include <Softpub.h>
#include <stdio.h>
#include <stdlib.h>
#pragma comment (lib, "Crypt32")
// the Authenticode Signature is encode in PKCS7
#define ENCODING (X509_ASN_ENCODING | PKCS_7_ASN_ENCODING)
// Information structure of authenticode sign
typedef struct
{
LPWSTR lpszProgramName;
LPWSTR lpszPublisherLink;
LPWSTR lpszMoreInfoLink;
DWORD cbSerialSize;
LPBYTE lpSerialNumber;
LPTSTR lpszIssuerName;
LPTSTR lpszSubjectName;
}
SPROG_SIGNATUREINFO, *PSPROG_SIGNATUREINFO;
VOID GetProgAndPublisherInfo(PCMSG_SIGNER_INFO pSignerInfo, PSPROG_SIGNATUREINFO pInfo);
VOID GetCertificateInfo(HCERTSTORE hStore, PCMSG_SIGNER_INFO pSignerInfo, PSPROG_SIGNATUREINFO pInfo);
BOOL GetAuthenticodeInformation(LPCTSTR lpszFileName, PSPROG_SIGNATUREINFO pInfo)
{
HCERTSTORE hStore = NULL;
HCRYPTMSG hMsg = NULL;
PCMSG_SIGNER_INFO pSignerInfo = NULL;
DWORD dwSignerInfo;
BOOL bRet = FALSE;
__try
{
// as CryptQueryObject() only accept WCHAR file name, convert first
WCHAR wszFileName[MAX_PATH];
#ifdef UNICODE
if ( !lstrcpynW( wszFileName, lpszFileName, MAX_PATH))
__leave;
#else
if ( mbstowcs( wszFileName, lpszFileName, MAX_PATH) == -1)
__leave;
#endif
//Retrieve the Message Handle and Store Handle
DWORD dwEncoding, dwContentType, dwFormatType;
if ( !CryptQueryObject( CERT_QUERY_OBJECT_FILE, wszFileName,
CERT_QUERY_CONTENT_FLAG_PKCS7_SIGNED_EMBED,
CERT_QUERY_FORMAT_FLAG_BINARY, 0, &dwEncoding,
&dwContentType, &dwFormatType, &hStore,
&hMsg, NULL))
__leave;
//Get the length of SignerInfo
if ( !CryptMsgGetParam( hMsg, CMSG_SIGNER_INFO_PARAM, 0, NULL, &dwSignerInfo))
__leave;
// allocate the memory for SignerInfo
if ( !(pSignerInfo = (PCMSG_SIGNER_INFO)LocalAlloc( LPTR, dwSignerInfo)))
__leave;
// get the SignerInfo
if ( !CryptMsgGetParam( hMsg, CMSG_SIGNER_INFO_PARAM, 0, (PVOID)pSignerInfo, &dwSignerInfo))
__leave;
//get the Publisher from SignerInfo
GetProgAndPublisherInfo( pSignerInfo, pInfo);
//get the Certificate from SignerInfo
GetCertificateInfo( hStore, pSignerInfo, pInfo);
bRet = TRUE;
}
__finally
{
// release the memory
if (pSignerInfo != NULL) LocalFree(pSignerInfo);
if (hStore != NULL) CertCloseStore(hStore, 0);
if (hMsg != NULL) CryptMsgClose(hMsg);
}
return bRet;
}
LPWSTR AllocateAndCopyWideString(LPCWSTR inputString)
{
LPWSTR outputString = NULL;
// allocate the memory
outputString = (LPWSTR)VirtualAlloc(NULL, (wcslen(inputString) + 1) * sizeof(TCHAR), MEM_COMMIT, PAGE_READWRITE);
// copy
if (outputString != NULL)
{
lstrcpyW(outputString, inputString);
}
return outputString;
}
VOID GetProgAndPublisherInfo(PCMSG_SIGNER_INFO pSignerInfo, PSPROG_SIGNATUREINFO pInfo)
{
PSPC_SP_OPUS_INFO OpusInfo = NULL;
DWORD dwData;
__try
{
// query SPC_SP_OPUS_INFO_OBJID OID in Authenticated Attributes
for (DWORD n = 0; n < pSignerInfo->AuthAttrs.cAttr; n++)
{
if (lstrcmpA(SPC_SP_OPUS_INFO_OBJID, pSignerInfo->AuthAttrs.rgAttr[n].pszObjId) == 0)
{
// get the length of SPC_SP_OPUS_INFO
if ( !CryptDecodeObject(ENCODING,
SPC_SP_OPUS_INFO_OBJID,
pSignerInfo->AuthAttrs.rgAttr[n].rgValue[0].pbData,
pSignerInfo->AuthAttrs.rgAttr[n].rgValue[0].cbData,
0,
NULL,
&dwData))
__leave;
// allocate the memory for SPC_SP_OPUS_INFO
if ( !(OpusInfo = (PSPC_SP_OPUS_INFO)LocalAlloc(LPTR, dwData)))
__leave;
// get SPC_SP_OPUS_INFO structure
if ( !CryptDecodeObject(ENCODING,
SPC_SP_OPUS_INFO_OBJID,
pSignerInfo->AuthAttrs.rgAttr[n].rgValue[0].pbData,
pSignerInfo->AuthAttrs.rgAttr[n].rgValue[0].cbData,
0,
OpusInfo,
&dwData))
__leave;
// copy the Program Name of SPC_SP_OPUS_INFO to the return variable
if (OpusInfo->pwszProgramName)
{
pInfo->lpszProgramName = AllocateAndCopyWideString(OpusInfo->pwszProgramName);
}
else
pInfo->lpszProgramName = NULL;
// copy the Publisher Info of SPC_SP_OPUS_INFO to the return variable
if (OpusInfo->pPublisherInfo)
{
switch (OpusInfo->pPublisherInfo->dwLinkChoice)
{
case SPC_URL_LINK_CHOICE:
pInfo->lpszPublisherLink = AllocateAndCopyWideString(OpusInfo->pPublisherInfo->pwszUrl);
break;
case SPC_FILE_LINK_CHOICE:
pInfo->lpszPublisherLink = AllocateAndCopyWideString(OpusInfo->pPublisherInfo->pwszFile);
break;
default:
pInfo->lpszPublisherLink = NULL;
break;
}
}
else
{
pInfo->lpszPublisherLink = NULL;
}
// copy the More Info of SPC_SP_OPUS_INFO to the return variable
if (OpusInfo->pMoreInfo)
{
switch (OpusInfo->pMoreInfo->dwLinkChoice)
{
case SPC_URL_LINK_CHOICE:
pInfo->lpszMoreInfoLink = AllocateAndCopyWideString(OpusInfo->pMoreInfo->pwszUrl);
break;
case SPC_FILE_LINK_CHOICE:
pInfo->lpszMoreInfoLink = AllocateAndCopyWideString(OpusInfo->pMoreInfo->pwszFile);
break;
default:
pInfo->lpszMoreInfoLink = NULL;
break;
}
}
else
{
pInfo->lpszMoreInfoLink = NULL;
}
break; // we have got the information, break
}
}
}
__finally
{
if (OpusInfo != NULL) LocalFree(OpusInfo);
}
}
VOID GetCertificateInfo(HCERTSTORE hStore, PCMSG_SIGNER_INFO pSignerInfo, PSPROG_SIGNATUREINFO pInfo)
{
PCCERT_CONTEXT pCertContext = NULL;
__try
{
CERT_INFO CertInfo;
DWORD dwData;
// query Signer Certificate in Certificate Store
CertInfo.Issuer = pSignerInfo->Issuer;
CertInfo.SerialNumber = pSignerInfo->SerialNumber;
if ( !(pCertContext = CertFindCertificateInStore( hStore,
ENCODING, 0, CERT_FIND_SUBJECT_CERT,
(PVOID)&CertInfo, NULL)))
__leave;
dwData = pCertContext->pCertInfo->SerialNumber.cbData;
// SPROG_SIGNATUREINFO.cbSerialSize
pInfo->cbSerialSize = dwData;
// SPROG_SIGNATUREINFO.lpSerialNumber
pInfo->lpSerialNumber = (LPBYTE)VirtualAlloc(NULL, dwData, MEM_COMMIT, PAGE_READWRITE);
memcpy( pInfo->lpSerialNumber, pCertContext->pCertInfo->SerialNumber.pbData, dwData);
// SPROG_SIGNATUREINFO.lpszIssuerName
__try
{
// get the length of Issuer Name
if (!(dwData = CertGetNameString( pCertContext,
CERT_NAME_SIMPLE_DISPLAY_TYPE,
CERT_NAME_ISSUER_FLAG, NULL, NULL, 0)))
__leave;
// allocate the memory
if ( !(pInfo->lpszIssuerName = (LPTSTR)VirtualAlloc(NULL, dwData * sizeof(TCHAR), MEM_COMMIT, PAGE_READWRITE)))
__leave;
// get Issuer Name
if (!(CertGetNameString(pCertContext,
CERT_NAME_SIMPLE_DISPLAY_TYPE,
CERT_NAME_ISSUER_FLAG, NULL, pInfo->
lpszIssuerName, dwData)))
__leave;
}
__finally
{
}
// SPROG_SIGNATUREINFO.lpszSubjectName
__try
{
//get the length of Subject Name
if (!(dwData = CertGetNameString( pCertContext, CERT_NAME_SIMPLE_DISPLAY_TYPE, 0, NULL, NULL, 0)))
__leave;
// allocate the memory
if ( !(pInfo->lpszSubjectName = (LPTSTR)VirtualAlloc(NULL, dwData * sizeof(TCHAR), MEM_COMMIT, PAGE_READWRITE)))
__leave;
// get Subject Name
if (!(CertGetNameString( pCertContext, CERT_NAME_SIMPLE_DISPLAY_TYPE, 0, NULL, pInfo->lpszSubjectName, dwData)))
__leave;
}
__finally
{
}
}
__finally
{
if (pCertContext != NULL)
CertFreeCertificateContext(pCertContext);
}
}
int _tmain(int argc, TCHAR *argv[])
{
if (argc != 2)
{
_tprintf(_T("Usage: SignedFileInfo \n"));
return 0;
}
else
{
SPROG_SIGNATUREINFO SignInfo;
ZeroMemory(&SignInfo, sizeof(SignInfo));
GetAuthenticodeInformation( argv[1], &SignInfo);
wprintf(L"Program Name: %s\n", SignInfo.lpszProgramName);
wprintf(L"Publisher Link: %s\n", SignInfo.lpszPublisherLink);
wprintf(L"More Info Link: %s\n", SignInfo.lpszMoreInfoLink);
{
_tprintf(_T("Serial Number: "));
DWORD dwData = SignInfo.cbSerialSize;
for (DWORD n = 0; n < dwData; n++)
{
_tprintf(_T("%02x "),
SignInfo.lpSerialNumber[dwData - (n + 1)]);
}
_tprintf(_T("\n"));
}
_tprintf(_T("Issuer Name: %s\n"), SignInfo.lpszIssuerName);
_tprintf(_T("Subject Name: %s\n"), SignInfo.lpszSubjectName);
if ( SignInfo.lpszProgramName) VirtualFree(SignInfo.lpszProgramName, 0, MEM_RELEASE);
if ( SignInfo.lpszPublisherLink) VirtualFree(SignInfo.lpszPublisherLink, 0, MEM_RELEASE);
if ( SignInfo.lpszMoreInfoLink) VirtualFree(SignInfo.lpszMoreInfoLink, 0, MEM_RELEASE);
if ( SignInfo.lpSerialNumber) VirtualFree(SignInfo.lpSerialNumber, 0, MEM_RELEASE);
if ( SignInfo.lpszIssuerName) VirtualFree(SignInfo.lpszIssuerName, 0, MEM_RELEASE);
if ( SignInfo.lpszSubjectName) VirtualFree(SignInfo.lpszSubjectName, 0, MEM_RELEASE);
return 0;
}
}