Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

PKCS#7 Signature Verification

I am trying to implement signature verification for PDFs. It is a big topic so I am taking it one step at a time, first I am trying to actually return a positive in the case of a PDF I have signed myself, using all the default values with the current Acrobat — that should be SHA256 for the digest, and a PKCS7 detached signature. So, I crack out openssl, and by reading the byte range given in the PDF and calling the SHA256_* functions I have a hash to compare against. So now I need to read the certificate data etc, and use the PKCS7_* functions. This one looks to be the one I want:

int PKCS7_verify(PKCS7 *p7, STACK_OF(X509) *certs, X509_STORE *store, BIO *indata, BIO *out, int flags);

as found in the documentation. Except said documentation doesn't tell me how to construct any of these things. Ok, so I think the BIO *indata can be made with some of the functions in here and the array of certs using these (despite having not worked out the precise details), but what about the PKCS7 *p7, or the STACK_OF(x) called for. I cannot find any documented way of initialising these structures. There are some pkcs7_ctrl functions in the pkcs7.h header:-

long PKCS7_ctrl(PKCS7 *p7, int cmd, long larg, char *parg);

int PKCS7_set_type(PKCS7 *p7, int type);
int PKCS7_set0_type_other(PKCS7 *p7, int type, ASN1_TYPE *other);
int PKCS7_set_content(PKCS7 *p7, PKCS7 *p7_data);
int PKCS7_SIGNER_INFO_set(PKCS7_SIGNER_INFO *p7i, X509 *x509, EVP_PKEY *pkey, const EVP_MD *dgst);
int PKCS7_SIGNER_INFO_sign(PKCS7_SIGNER_INFO *si);
int PKCS7_add_signer(PKCS7 *p7, PKCS7_SIGNER_INFO *p7i);
int PKCS7_add_certificate(PKCS7 *p7, X509 *x509);
int PKCS7_add_crl(PKCS7 *p7, X509_CRL *x509);
int PKCS7_content_new(PKCS7 *p7, int nid);
int PKCS7_dataVerify(X509_STORE *cert_store, X509_STORE_CTX *ctx,
    BIO *bio, PKCS7 *p7, PKCS7_SIGNER_INFO *si); 
int PKCS7_signatureVerify(BIO *bio, PKCS7 *p7, PKCS7_SIGNER_INFO *si, X509 *x509);

BIO *PKCS7_dataInit(PKCS7 *p7, BIO *bio);
int PKCS7_dataFinal(PKCS7 *p7, BIO *bio);
BIO *PKCS7_dataDecode(PKCS7 *p7, EVP_PKEY *pkey, BIO *in_bio, X509 *pcert);

but without some guidelines this doesn't seem like a forest it would be efficacious to start blindly poking around in.

Have I missed something obvious? How do I go about calling this function with the data values I have parsed from the PDF?

like image 711
Vic Smith Avatar asked Oct 16 '13 18:10

Vic Smith


People also ask

What is PKCS for?

The Public-Key Cryptography Standards (PKCS) comprise a group of cryptographic standards that provide guidelines and application programming interfaces (APIs) for the usage of cryptographic methods. As the name PKCS suggests, these standards put an emphasis on the usage of public key (that is, asymmetric) cryptography.

What is PKCS format?

PKCS#12 (also known as PKCS12 or PFX) is a binary format for storing a certificate chain and private key in a single, encryptable file. PKCS#12 files are commonly used to import and export certificates and private keys on Windows and macOS computers, and usually have the filename extensions . p12 or .

Is PKCS12 safe?

PKCS12 (aka PFX) files, on the other hand, are language-neutral and is more secure and has been around long enough that it's supported just about everywhere.

What is pkcs11 driver?

PKCS #11 modules are external modules which add to Firefox support for smartcard readers, biometric security devices, and external certificate stores.


1 Answers

Ok, found all this out the (very) hard way. This is how you do it, so that others might learn more easily.

Lets say we have the signature char* sig of length int sig_length, and verification data char* data, int data_length. (There are some subtleties here for PDF signatures but these are well documented in the PDF spec.)

OpenSSL_add_all_algorithms();
OpenSSL_add_all_digests();
EVP_add_digest(EVP_md5());
EVP_add_digest(EVP_sha1());
EVP_add_digest(EVP_sha256());

BIO* sig_BIO = BIO_new_mem_buf(sig, sig_length)
PKCS7* sig_pkcs7 = d2i_PKCS7_bio(sig_BIO, NULL);

BIO* data_BIO = BIO_new_mem_buf(data, data_length)
BIO* data_pkcs7_BIO = PKCS7_dataInit(sig_pkcs7, data_BIO);

// Goto this place in the BIO. Why? No idea!
char unneeded[1024*4];
while (BIO_read(dataPKCS7_BIO, unneeded, sizeof(buffer)) > 0);

int result;
X509_STORE *certificateStore = X509_STORE_new();
X509_STORE_CTX certificateContext;
STACK_OF(PKCS7_SIGNER_INFO) *signerStack = PKCS7_get_signer_info(sig_pkcs7);
int numSignerInfo = sk_PKCS7_SIGNER_INFO_num(signerStack);
for (int i=0; i<numSignerInfo; ++i) {
    PKCS7_SIGNER_INFO *signerInfo = sk_PKCS7_SIGNER_INFO_value(signerStack, i);
    result = PKCS7_dataVerify(certificateStore, &certificateContext, data_pkcs7_BIO, sig_pkcs7, signerInfo);
}

X509_STORE_CTX_cleanup(&certificateContext);
BIO_free(sig_BIO);
BIO_free(data_BIO);
BIO_free(data_pkcs7_BIO);
PKCS7_free(sig_pkcs7);
X509_STORE_free(certificateStore);

The function that does the work is actually PKCS7_dataVerify, and you don't need to run any digests yourself.

But wait, if you try this, it won't work! Why? Because the verification does both trust and integrity. In addition to this, you will also need to either establish trust by adding certs to the store, which is also complicated and undocumented. If you want fine grain results for you'll want to set a callback on the verification via the certificate store like this:

X509_VERIFY_PARAM_set_flags(certificateStore->param, X509_V_FLAG_CB_ISSUER_CHECK);
X509_STORE_set_verify_cb_func(certificateStore, verificationCallback);

where

static int verificationCallback(int ok, X509_STORE_CTX *ctx) {
    switch (ctx->error)
    {
        case X509_V_ERR_INVALID_PURPOSE: //...
        case X509_V_ERR_CERT_HAS_EXPIRED: //...
        case X509_V_ERR_KEYUSAGE_NO_CERTSIGN: //... 
        case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT: //...
        // ... etc
        default: break;
    }
    return ok;
}

You can set the error to ok and tell it to verify, for example if you want to ignore expired certs:

static int verificationCallback(int ok, X509_STORE_CTX *ctx) {
    switch (ctx->error)
    {
        case X509_V_ERR_CERT_HAS_EXPIRED: 
            X509_STORE_CTX_set_error(ctx, X509_V_OK);
            ok = 1;
            break;
    }
    return ok;
}
like image 114
Vic Smith Avatar answered Sep 20 '22 16:09

Vic Smith