10

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?

4

1 回答 1

9

好的,以(非常)艰难的方式发现了这一切。这就是你的做法,以便其他人可以更轻松地学习。

假设我们有char* sig长度int sig_length和验证数据char* data的签名int data_length。(这里有一些关于 PDF 签名的微妙之处,但这些在 PDF 规范中有很好的记录。)

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);

完成这项工作的函数实际上是 PKCS7_dataVerify,您不需要自己运行任何摘要。

但是等等,如果你尝试这个,它不会工作!为什么?因为验证既信任又完整性。除此之外,您还需要通过向存储添加证书来建立信任,这也很复杂且没有文档记录。如果您想要细粒度的结果,您需要通过证书存储设置验证回调,如下所示:

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

在哪里

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;
}

您可以将错误设置为 ok 并告诉它进行验证,例如,如果您想忽略过期的证书:

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;
}
于 2013-10-25T11:16:01.250 回答