Pergunta

Some context: I have a PKCS #11-compliant cryptographic engine. This engine will be used to handle signed/enveloped data, i.e. verify the data's ECDSA/SHA1 signature, unwrap the symmetric key with RSAES-OAEP, and decrypt this data. This means the symmetric key will be wrapped with my engine's public key: hence I'd like the certificate for this public key to actually read "Subject Public Key Algorithm: RSAES-OAEP".

I'm looking for a C library which will let me manipulate objects comforming to the Cryptographic Message Syntax (CMS) and X.509 standards in the following way:

  1. create a X.509 Certificate Signing Request (CSR), setting the Subject Public Key Algorithm to RSAES-OAEP
  • let me handle the signing part: my private key is only accessible via a PKCS #11 handle, so I need the library to give me the bytes to sign, and then let me set the CSR's ProofOfPossession field with what my crypto engine computed

  • export the complete CSR to something (DER or PEM)

  1. create CMS structures to hold something like SignedData( EnvelopedData( stuff )). The library could handle the actual encryption/key wrapping/signature, or it could let some other software engine do it and just allow me to set the octet strings

  2. let me easily parse back the message and recover those octet strings

  • meaning I want to open a DER/PEM file which contains this CMS message, and get the bytes for the signature, the wrapped key and the encrypted stuff, so that I can feed them to my PKCS #11 interface

Before anyone suggests OpenSSL's libcrypto, I've looked at it (looked as in, "spent the last week trying to understand how the structures work, how the ASN.1 representation works, how I can recover the bytes I'm interested in from OpenSSL's structures..."), and I have some issues with it (as of 1.0.1f):

  • (cf 1.) I cannot set the Subject Public Key Algorithm to RSAE-OAEP. Starting from demos/x509/mkreq.c, and going all the way back to the deep reaches of x509t.h's weird #define IMPLEMENT_ASN1_ENCODE_FUNCTIONS_fname(stname, itname, fname) macros, I think I can affirm that X509_REQ_set_pubkey() cannot handle OAEP.

  • (cf 2.) Neither can the CMS part of the crypto lib, for that matter. cms_RecipientInfo_ktri_encrypt() calls EVP_PKEY_CTX_ctrl(), which I guess resolves to crypto/rsa/rsa_pmeth.c:pkey_rsa_ctrl(): when p2 is EVP_PKEY_CTRL_CMS_ENCRYPT, p2 is not parsed, so RSA padding is not set (if it turns out I'm wrong and I just cannot read code correctly, please tell me).

So while I'm glad this guy managed to make it "work like a charm", I cannot share his enthusiasm.

I guess for 2. I could use OpenSSL to create CMS blank structures, compute the future EnvelopedData content (ie stuff encrypted with a symmetric key + symmetric key wrapped with RSA OAEP), and stuff this content into the structures, bypassing CMS_encrypt() completely, before encapsulating into a SignedData (I'm assuming CMS_sign() will handle ECDSA/SHA1). As long as I don't want to use fancy parameters for OAEP (although this other guy managed to patch the lib to use OAEP with SHA-256).

But this might end up requiring a tad too much fiddling with OpenSSL's ASN.1 API. Hence my interrogation: does anyone know of a C library to build CMS structures and feed them the octet strings computed by some other engines? Also how to build certificates which read "THIS KEY IS MEANT TO BE USED WITH RSAES-OAEP".

I've looked at libksba and cryptlib and while I guess they could work, I cannot see how to use them yet (might have something to do with my eyes bleeding from staring at OpenSSL's code so much - I do not mean to say that OpenSSL's code is bad or anything, just that I've been looking at it hard, and the doc is slightly lacking).

Actually, I guess, I could drop the C requirement (mainly there because communicating with the crypto engine is done in PKCS #11/C). Here's what the library should be able to do:

  1. build a CSR

  2. ... featuring "RSAES-OAEP" as Subject Public Key Algorithm

  3. give me the bytes to sign for the Proof-of-Possession part

  4. take the signature and output a complete X.509 CSR

  5. parse a CMS structure

  6. (SignedData) give me the bytes corresponding to the signedInfo->signature and encapsulatedContentInfo fields so that I can verify them with some other engine

  7. (EnvelopedData) give me the bytes corresponding to the keyTransRecipientInfo->encryptedKey and encryptedContentInfo->encryptedContent fields so that I can unwrap/decrypt with some other engine

  8. build a CMS structure, either...

  9. letting some external engine set the fields mentioned above, and letting me specify the algorithms

  10. actually implementing the algorithms, and building the CMS from just the data (... with RSAES-OAEP for key wrapping)

Right now I'm going with a "all-OpenSSL" approach, because I feel like I'm in too deep and should not start wandering somewhere else. If anybody has a better idea, I am all ears.

As for setting that subject public key algorithm... Well, either I'll just leave regular RSA and have my application "know" that wrapping is RSAES-OAEP, or... I don't know. And as for signing the request... is POP all that useful anyway? (This is not a serious question)

NB: edited to remove the whole "I want my certificate to read OAEP", since I just found out about RFC 5756 (also found an interesting discussion from 2006 when this RFC was not out yet).

Foi útil?

Solução

Here's what I managed to get working so far.

1. Building a CSR, signing it with some other engine

I mostly followed demos/x509/mqreq.c, with some twists.

(NB: error checking, fancy modulus length/label/subject DN generation/handling has been left out for brevity and focus on actual flow).

unsigned char* mod = NULL;
unsigned char* exp = NULL;
size_t mod_l = 0;
size_t exp_l = 0;

P11_handle h_key = P11_gen_rsa(&mod, &mod_l, &exp, &exp_l);

RSA* rsa = RSA_new();

rsa->n = BN_bin2bn(rsa_mod, rsa_mod_l, NULL);
rsa->e = BN_bin2bn(rsa_exp, rsa_exp_l, NULL);

EVP_PKEY* pkey = EVP_PKEY_new();

EVP_PKEY_assign_RSA(pkey, rsa);

X509_REQ* csr = X509_REQ_new();
X509_REQ_set_pubkey(csr, pkey);

/* Boring X509_NAME/X509_EXTENSION stuff */

X509_REQ_INFO* csr_req = csr->req_info;

unsigned char* pop_in = NULL;
size_t pop_in_l = ASN1_item_i2d((void*)csr_req, &pop_in,
                                ASN1_ITEM_rptr(X509_REQ_INFO));

unsigned char* sig = NULL;
size_t sig_l = 0;

P11_make_pop(SIGN_RSA_PKCS, DIGEST_SHA256,
             pop_in, pop_in_l, &sig, &sig_l,
             h_key);

/* Add signature to CSR (heavily inspired from ASN1_item_sign_ctx())
 * (please don't ask about the flags) */

if (csr->signature->data != NULL) OPENSSL_free(csr->signature->data);
csr->signature->data = sig;
csr->signature->length = sig_l;
csr->signature->flags&= ~(ASN1_STRING_FLAG_BITS_LEFT|0x07);
csr->signature->flags|= ASN1_STRING_FLAG_BITS_LEFT;

/* Add signature algorithm information to CSR */

int sig_algo_nid = 0;
OBJ_find_sigid_by_algs(&sig_algo_nid,
                       EVP_MD_nid(EVP_sha256()), EVP_PKEY_RSA);

X509_ALGOR_set0(csr->sig_alg, OBJ_nid2obj(sig_algo_nid),
                V_ASN1_NULL, NULL));

After that, the X509_REQ structure is good for PEM export. openssl req -verify seems to validate the process, so as far as I'm concerned this works.

2. Building nested CMS structures (as in SignedData(EnvelopedData(Data)))

Finally got it, using 1.0.2 (any previous version would have needed patching or ASN.1-level parsing). Many thanks to Dr. Stephen Henson and Tom Francis for helping me with this via the mailing list.

/* Make EnvelopedData structure */
BIO* in = BIO_new_file(in_path, "rb");

int flags = CMS_BINARY | CMS_USE_KEYID | CMS_PARTIAL | CMS_KEY_PARAM;

CMS_ContentInfo* edata = CMS_encrypt(NULL, NULL, cipher, flags);

CMS_RecipientInfo* r_info = CMS_add1_recipient_cert(edata, r_cert, flags);
EVP_PKEY_CTX* wrap_ctx = CMS_RecipientInfo_get0_pkey_ctx(r_info);

EVP_PKEY_CTX_set_rsa_padding(wrap_ctx, RSA_PKCS1_OAEP_PADDING);
EVP_PKEY_CTX_set_rsa_oaep_md(wrap_ctx, EVP_sha256());
EVP_PKEY_CTX_set_rsa_mgf1_md(wrap_ctx, EVP_sha256());
EVP_PKEY_CTX_set0_rsa_oaep_label(wrap_ctx, oaep_label, oaep_label_l);
/* NB: oaep_label must be heap-allocated, and will be freed by OSSL */

CMS_final(edata, in, NULL, flags);

BIO* tmp = BIO_new(BIO_s_mem());
i2d_CMS_bio(tmp, edata);

/* Make SignedData structure */

flags|= CMS_NOSMIMECAP | CMS_NOCERTS;
flags&= ~(CMS_KEY_PARAM);

CMS_ContentInfo* sdata = CMS_sign(NULL, NULL, NULL, NULL, flags);

ASN1_OBJECT* ectype_edata = OBJ_nid2obj(NID_pkcs7_enveloped);
CMS_set1_eContentType(sdata, ectype_edata);

CMS_SignerInfo* s_info =
    CMS_add1_signer(sdata, s_cert, s_key, NULL, flags);

CMS_SignerInfo_sign(s_info);

CMS_final(sdata, tmp, NULL, flags);

BIO* out = BIO_new_file(out_path, "wb");
i2d_CMS_bio(out, sdata);
BIO_flush(out);

3. Parsing the structure and getting the fields I need.

I basically wrote my own CMS parser. ASN.1 is actually simple to parse when you know the spec. I've tried compiling the ASN.1 modules in the RFC using some "ASN.1 to C structs" compilers but had no luck (they kept choking on the syntax).

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top