OpenSSL C API: Certificate chain validation with CRL - c

I'm trying to perform certificate chain validation for Windows executable files, which also includes check for revoked certificates, using OpenSSL 1.0.2 C API.
I have the CRL files stored locally and I want to load them during verification (as opposed to download the CRL via "CRL Distribution Points" URL from certificates which have it).
Here's my simplified example of loading a single CRL file (omitting any error checking):
X509_STORE *store = NULL;
X509_STORE_CTX *ctx = NULL;
X509_VERIFY_PARAM *params = NULL;
X509_CRL *crl = d2i_X509_CRL_fp(fc, NULL); // fc is a file pointer to CRL file
X509_STORE_add_crl(store, crl);
X509_STORE_CTX_init(ctx, store, NULL, NULL);
params = X509_STORE_CTX_get0_param(ctx);
X509_VERIFY_PARAM_set_purpose(params, X509_PURPOSE_ANY);
X509_VERIFY_PARAM_set_flags(params, X509_V_FLAG_CRL_CHECK); // only want to check end entity
X509_STORE_set1_param(store, params);
// assume p7 is properly initialized PKCS7*
// assume bio is properly initialized BIO*
int ret = PKCS7_verify(p7, p7->d.sign->cert, store, bio, NULL, 0);
Above code will return ret == 0 with error: unable to get certificate CRL, which from my understanding means that OpenSSL is still trying to search CRL from the certificate itself instead of using the one I load locally.
What is the proper way of achieving this task?

Actually the code above is already correct to achieve my goal in performing CRL check.
One potential pitfall for someone new to X509 certificate structure is that the "CRL Distribution Points" URL for the certificate of interest is contained within that certificate itself, and not on the issuer's certificate. This was my mistake which led to the error I mentioned. I hope this may help people who just get started in understanding the X509 standard.

Related

Where to store a Authorization Bearer Token in Salesforce?

We have an external vendor that requires us to include a bearer token in the http request header when we communicate with the API. This token shouldn't be left in the code unencrypted so where is the best place to store it? The Named Credential type doesn't seem to support storing a simple token and the Custom Setting option seems overly complicated and unnecessary. This is a single token string that will be used for every API call regardless of which user. I have searched high and low on google and haven't found an obvious solution that works.
There are some options but they're limited for your code as end user. A determined developer/sysadmin will learn the value eventually.
If you'd build a managed package you could use a protected custom setting (managed package's code could see it but not the client code, even sysadmins)
Check some of these:
https://developer.salesforce.com/page/Secure_Coding_Storing_Secrets
https://salesforce.stackexchange.com/questions/226110/what-is-the-best-way-of-storing-username-and-password-in-salesforce
https://salesforce.stackexchange.com/questions/478/using-transient-keyword-to-store-password-in-hierarchy-custom-setting
https://salesforce.stackexchange.com/questions/55008/is-encrypting-passwords-in-protected-custom-settings-a-security-requirement
You could make a custom setting with 2 text fields, 1 with encryption key and 1 with encrypted value in it. Look at Crypto class.
Blob exampleIv = Blob.valueOf('Example of IV123');
Blob key = Crypto.generateAesKey(128);
Blob data = Blob.valueOf('Data to be encrypted');
Blob encrypted = Crypto.encrypt('AES128', key, exampleIv, data);
Blob decrypted = Crypto.decrypt('AES128', key, exampleIv, encrypted);
String decryptedString = decrypted.toString();
System.assertEquals('Data to be encrypted', decryptedString);
Your initialisation vector could be org's id or something else that's easy to access and unlikely to change (I don't know if your vendor's API has test and prod endpoints but it's an added bonus that after sandbox refresh this will fail to decrypt OK until you change the custom setting... you wouldn't want to send test messages to production API), you'd generate key once & store it in setting.

How to get next certificate in chain

I want to get parent certificate (or all certificates in chain for that matter) from Windows Certificate Store (assuming I know the location of the end certificate). I need to get each one in order to build my own custom X509_STORE (using OpenSSL).
I think the proper course of action would be:
obtain first certificate using CertFindCertificateInStore (done)
get the certificate chain using CertGetCertificateChain (done)
extract the certificates from chain (?)
for each certificate in chain, convert it using d2i_X509 (done)
or
obtain first certificate using CertFindCertificateInStore (done)
get the parent certificate (if exists) (?)
convert it using d2i_X509, go to 2. (done)
Then create the store.
The question to answer is then - how to get the parent certificate or all certificates in chain using Windows Certificate Store? I'm probably missing some more or less occult function here.
as the CertFindCertificateInStore outputs a chain context, one can access it's members using the beautiful construction:
chainContext->rgpChain[0]->rgpElement[iCertIndex]->pCertContext->pbCertEncoded
where iCertIndex is between 0 (end-certificate) and chainSize -1 (self-signed root certificate).

xmlsec library - obtain subject of signing key

I am using the xmlsec library to verify the signature of an SAML assertion. My code is almost identical to the verify4.c example provided on the xmlsec web page.
I am linking against the xmlsec-openssl lib, so using openssl as the crypto engine.
I was expecting that xmlsec would consider the signature valid only if it was signed with one of the specific certificate(s) I had loaded into the key manager.
However, the signature seems to be considered valid if signed with ANY certificate that can be verified by openssl. This means that someone could forge an SAML response just by buying a certificate from a trusted root CA and using that to sign whatever response they want.
Not only that, but the xmlsec1 command line tool provided with the library seems to do the same thing:
xmlsec1 --verify --dtd-file saml.dtd --pubkey-cert-pem my_cert.cer sample_saml_assertion.xml
...
OK
SignedInfo References (ok/all): 1/1
Manifests References (ok/all): 0/0
Actually, in an ideal world, I would be happy for any valid signing key to be used as long as I could identify the subject of the key and therefore confirm it was signed by the entity I expected. That would simplify matters when the sender of the SAML responses changed their signing key. But I have not been able to find a simple way to extract the details of the cert that was used to verify the signature.
Failing that, can I make it accept only the certificate(s) I have specified when verifying the signature?
While writing the question I realized I had not tried the --print-debug option for xmlsec1. When I tried that I found that it does indeed print the subject and issuer of the cert that was used to verify the signature.
That led me to realize that the information must be present, so it is a question of how to access it. Tracing through the code, I was able to write this little snippet which does the trick:
/* If signature is valid, then the list dsigCtx->signKey contains
the signing key, data dsigCtx->signKey->dataList contains the certificate */
xmlSecPtrListPtr keyDataList = dsigCtx->signKey->dataList;
/* Iterate through the data list to find the X509 cert */
xmlSecSize n = xmlSecPtrListGetSize(keyDataList);
xmlSecSize i;
for (i=0; i<n; i++) {
xmlSecKeyDataPtr item = xmlSecPtrListGetItem(keyDataList, i);
if (xmlSecKeyDataIsValid(item) && xmlSecKeyDataCheckId(item, xmlSecOpenSSLKeyDataX509Id)) {
/* Extract openssl cert */
X509* cert = xmlSecOpenSSLKeyDataX509GetKeyCert(item);
char cn_buff[256];
if(cert != NULL) {
/* Get the CN */
X509_NAME * subject_name = X509_get_subject_name(cert);
int nid_cn = OBJ_txt2nid("CN");
X509_NAME_get_text_by_NID(subject_name, nid_cn, cn_buff, 255);
/* Here you would compare it to the expected certificate */
fprintf(stdout, "CN=%s\n", cn_buff);
} else {
fprintf(stdout, "Failed to obtain signing key cert\n");
}
}
}
This seems a very complex way to obtain something so fundamental, so I am sure there must be an easier way.

SignedXml.CheckSignature() is true, but SignedXml.CheckSignature(certificate) is false

I'm generating a SAML2 token from ADFS, signed by certificate. Now I'm trying to verify that signature, using the same certificate.
X509Certificate2 cert = LoadCert();
XmlDocument token = LoadXmlToken(); //SAML2 token
XmlElement signature = GetSignatureElement(token);
SignedXml signedXml = new SignedXml(token);
signedXml.LoadXml(signature);
bool result1 = signedXml.CheckSignature(); //true
bool result2 = signedXml.CheckSignature(cert, false); //false
CheckSignature() verifies signature against the public key contained in the token.
CheckSignature(cert, [true/false]) verifies signature against the private key from the certificate.
How can it be that one works and the other doesn't?
The method signedXml.CheckSignature() evaluates the xml signature integrity against the certificate contained inside the own signature.
The method SignedXml.CheckSignature(X509Certificate2, Boolean) evaluates the xml signature integrity against the certificate passed as first parameter, and optionally if the second parameter is false it checks also the validity of the certificate in the first parameter.
Probably the second method returns false because you are specifying a wrong certificate: is not the certificate which performs the signature or its state is revoked or expired or it is issued by an untrusted certificate authority.
We had to enable IP address and/or URL's on our outbound firewall for the checksignature method when using the certificate check. In our case it tried to communicate with the root CA and the sub CA's website. With the firewall closed the method failed, but once we identified the URL's being accessed and opened up the firewall it started to work as expected.
The difference is in the second parameter (boolean). If you look at documentation of parameterless CheckSignature method you can find this:
This method also computes the digest of the references and the value of the signature.
The second method has this documentation. If the second parameter is set to
false then verify both the signature and certificate.
To verify certificate this method will probably build whole certificate chain and check revocation information of all certificates in this chain.

Adding AuthorityKeyIdentifier to a CertRequest

I am having problem using the exemple provided by OpenSSL to create a certificate Request with v3 extensions. This entire code can be found in the mkreq.c in Openssl/demos/x509/
Adding some x509v3 extensions to a cert request is working good.
I can add the Key usage or a subject alt name
add_ext(exts, NID_key_usage, "critical,digitalSignature,keyEncipherment");
add_ext(exts, NID_subject_alt_name, "email:steve#openssl.org");
but when I try to add an AuthorityKeyIdentifier this is not working...
add_ext(exts, NID_authority_key_identifier, "keyid,issuer");
The add_ext is also provided in the mkreq :
int add_ext(STACK_OF(X509_REQUEST) *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;
}
Do somebody have a clue why some extensions are working and some not ? When I add the same extensions for self-signed its working well...
After reasearching for some time, it appears that this is not possible since you don't know the CA when you are creating a certificate request...
I find the following worthwhile to share, though I am not sure this is relevant to your situation.
When generating self signed certs on the command line, the order of some extensions is important. If you want the keyid to be used as the authority key id, you must declare the subjectKeyIdentifier first.
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer
In that case authorityKeyIdentifier will be filled with the keyid, and issuer will not be used.
If you declare in the opposite order, authorityKeyIdentifier will be filled with the issuer instead. Possibly because the program tries to init authorityKeyIdentifier based on subjectKeyIdentifier.
If you declared authorityKeyIdentifier = keyid:always, then a hard error is thrown because keyid is not known.
I hope it will help someone. It took me a while to tackle that one.
Also avoid CA constraint
authorityKeyIdentifier = keyid,issuer
basicConstraints = CA:FALSE
keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment
subjectAltName = #alt_names
[alt_names]
DNS.1 = localhost

Resources