kzi
kzi

Reputation: 71

Using OpenSSL API how can I access a signer's certificate info from a signed file?

I have a CMS envelope with content type 'signedData'. How can I access the signer certificate's information such as validity, subject, issuer and so on?

Given the fact that

$>openssl cms -verify -signer foo.pem ...

writes the signer certificate(s) and I can parse the required info from foo.pem by other means, I deduce that it is available in principle. read in the X509 and drill down to the required info. However, to have to verify the signature in order to obtain a separate X509 object which I can then parse for the required cert info is not what I like to do.

I can extract other data from the CMS such as the signingTime attribute:

    BIO *in = NULL;
    CMS_ContentInfo *cms = NULL;
    STACK_OF(CMS_SignerInfo) *ssi = NULL;
    CMS_SignerInfo *si = NULL;
    int ret = 1;

    in = BIO_new_file(argv[1], "r");
    if (!in)
        goto err;

//    cms = PEM_read_bio_CMS(in, NULL, NULL, NULL);   //PEM
    cms = d2i_CMS_bio(in, NULL);                    //DER
    if (!cms)
        goto err;

    ssi = CMS_get0_SignerInfos(cms);
    if (!ssi)
        goto err;

    int issimax = sk_CMS_SignerInfo_num(ssi);
    for (int issi = 0; issi < issimax; ++issi) {
        si = sk_CMS_SignerInfo_value(ssi, issi);

        //signing time
        int ist = CMS_signed_get_attr_by_NID(si, NID_pkcs9_signingTime, -1);
        X509_ATTRIBUTE *xa = CMS_signed_get_attr(si, ist);
        ASN1_TYPE *at = sk_ASN1_TYPE_value(xa->value.set, 0);
        printTime("signing time", at);   //just a fancy reformat

        //...
    }

Following the CLI above I tried to access CMS_SignerInfo's 'signer' member as it is of type X509 (put this in place of the "//..."):

        X509 *c = si->signer;

However c would always be 0x0.

I also tried to get access to the X509 directly from CMS_ContentInfo like:

        STACK_OF(X509) *sc = NULL;
        sc = CMS_get0_signers(cms);
        if (!sc)
            continue;

        int iscmax = sk_X509_num(sc);
        for (int isc = 0; isc < iscmax; ++isc) {
            X509 *c = NULL;
            c = sk_X509_value(sc, isc);
        }

But the STACK_OF(X509) would also be 0x0 and its count iscmax 0.

How can I get access to the X509, from there to the X509_CINF, and from there to the actual data I need (edit:) without to have to verify the signature first in order to obtain a separate X509 object?

Alternatively, is the information hidden elsewhere in the object tree?

Upvotes: 1

Views: 2367

Answers (1)

kzi
kzi

Reputation: 71

I'm going to answer myself...

The solution

        STACK_OF(X509) *signers = CMS_get0_signers(cms);
        for (int i = 0; i < sk_X509_num(signers); ++i) {
            X509 *signer = sk_X509_value(signers, i);
            //do something with signer
        }

sketched out above is actually correct.

It failed because the digest table was not initialized.

Debugging into CMS_verify() I found that it ultimately failed because EVP_get_digestbyobj() returned NULL. Searching the net for that method name I learned from the manpage that

The digest table must be initialized using, for example, OpenSSL_add_all_digests() for these functions to work.

I added OpenSSL_add_all_digests() before my code and it worked.

edit: Added MCVE (sorry C++ style comments). Pass CMS signed file in DER format as first parameter for this to work.


#include <openssl/bio.h>
#include <openssl/cms.h>
#include "../crypto/cms/cms_lcl.h"  //CMS_SignerInfo_st

int main(int argc, char **argv)
{
    if (argc < 2)
        return -1;

    BIO *in = NULL;
    CMS_ContentInfo *cms = NULL;
    int flags = CMS_NO_SIGNER_CERT_VERIFY;  /* compare -noverify CLI switch */
    STACK_OF(CMS_SignerInfo) *signer_infos = NULL;
    CMS_SignerInfo *si = NULL;
    int ret = 1;

    OpenSSL_add_all_digests();

    in = BIO_new_file(argv[1], "r");
    if (!in)
        goto err;

//  cms = PEM_read_bio_CMS(in, NULL, NULL, NULL);   //PEM
    cms = d2i_CMS_bio(in, NULL);
    if (!cms)
        goto err;

//    We do not need to set up a store in case of CMS_NO_SIGNER_CERT_VERIFY.
//    If you do, copy it here from apps.c.
//    X509_STORE *store = setup_verify(/*bio_err*/NULL, /*CAfile*/NULL, /*CApath*/NULL);
//    if (!store)
//        goto err;

//    ...and neither do we need an output just for the cert info
//    BIO *out = BIO_new(BIO_s_mem());
//    if (!out)
//        goto err;

    //Initialize si->signer (see below).
    if (!CMS_verify(cms, /*certs*/NULL, /*store*/NULL, /*dcont*/NULL, /*out*/NULL, flags))
        goto err;

    signer_infos = CMS_get0_SignerInfos(cms);
    if (!signer_infos)
        goto err;

    for (int i = 0; i < sk_CMS_SignerInfo_num(signer_infos); ++i) {
        printf("%i:\n", i + 1);

        si = sk_CMS_SignerInfo_value(signer_infos, i);

        //signing time
        int iattr = CMS_signed_get_attr_by_NID(si, NID_pkcs9_signingTime, -1);
        X509_ATTRIBUTE *attr = CMS_signed_get_attr(si, iattr);
        ASN1_TYPE *atype = sk_ASN1_TYPE_value(attr->value.set, 0);
        printf("signed time: %s\n", atype->value.asn1_string->data); //nvm UTC vs. generalized

        //signer certificate info
        X509 *signer = si->signer;
        if (!signer) {
            printf("no signer certificate; continue\n");
            continue;
        }
        X509_CINF *cinfo = signer->cert_info;
        if (!cinfo) {
            printf("no cert info with signer certificate; continue\n");
            continue;
        }
        printf("signer valid not before: %s\n", cinfo->validity->notBefore->data);
        printf("signer valid not after:  %s\n", cinfo->validity->notAfter->data);
    }

    ret = 0;

err:
    if (ret) {
        ERR_print_errors_fp(stderr);
        return -1;
    }

}

Upvotes: 4

Related Questions