On May 2, 2014, at 3:19 AM, Kevin Le Gouguec <kevin.le-goug...@insa-lyon.fr> 
wrote:

> (tl;dr : see questions at the end)
> 
> I'm trying to build nested CMS structures, as in, having a file F, a signer S 
> and a recipient R, I want to build a CMS-compliant message M which looks like:
> 
> M = SignedData(ECI, SignerInfo(S))
> 
> ECI = EncapsulatedContentInfo( EnvelopedData( RecipientInfo(R) )
> 
> Where RecipientInfo(R) contains:
> [ F ] = encrypted file, [ K ] = file-encryption key, encrypted with R's 
> public key.
> 
> Now the code compiles, I figure I can parse that back, but when I run 
> `openssl cms` or `openssl asn1parse` on the generated DER file, the whole 
> EnvelopedData show up as one big OctetString. Is that normal?

Yes, the idea is that you’d run your process over the data in multiple passes, 
so if you’re using the openssl command line program, you’d do the first pass to 
verify the signature and dump the enveloped data to a temp file*, and then run 
openssl again to decrypt the data from the temp file.  The openssl command line 
isn’t set up to handle CMS structures recursively — that’s also why if you use 
the command line to create this, you’d need to invoke openssl twice, the first 
time to encrypt, the second time to sign (in your case).

> Here's my code (all error checking has been left out for brevity):
> 
>    /* Make EnvelopedData structure */
> 
>    BIO* in = BIO_new_file(in_path, "rb");
> 
>    int flags = CMS_BINARY | CMS_USE_KEYID | CMS_PARTIAL;
> 
>    CMS_ContentInfo* edata = CMS_encrypt(NULL, NULL, cipher, flags);
> 
>    CMS_RecipientInfo* r_info = CMS_add1_recipient_cert(edata, r_cert, flags);
> 
>    CMS_final(edata, in, NULL, flags);
> 
>    BIO* tmp = BIO_new(BIO_s_mem());
>    i2d_CMS_bio(tmp, edata);            /* [1] */
> 
>    /* Make SignedData structure */
> 
>    flags|= CMS_NOSMIMECAP | CMS_NOCERTS;
> 
>    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);
> 
> Now, here's the output from cms:
> 
> CMS_ContentInfo: 
>  contentType: pkcs7-signedData (1.2.840.113549.1.7.2)
>  d.signedData: 
>    version: 3
>    digestAlgorithms:
>        algorithm: sha1 (1.3.14.3.2.26)
>        parameter: <ABSENT>
>    encapContentInfo: 
>      eContentType: pkcs7-envelopedData (1.2.840.113549.1.7.3)
>      eContent: 
>        < LARGE OCTET STRING, A BIT LARGER THAN THE FILE >
>    certificates:
>      <EMPTY>
>    crls:
>      <EMPTY>
>    signerInfos:
>        version: 3
>        d.subjectKeyIdentifier: 
>          < KEY ID IS HERE >
>        digestAlgorithm: 
>          algorithm: sha1 (1.3.14.3.2.26)
>          parameter: <ABSENT>
>        signedAttrs:
>            object: contentType (1.2.840.113549.1.9.3)
>            value.set:
>              OBJECT:pkcs7-envelopedData (1.2.840.113549.1.7.3)
> 
>            < ALSO UTC TIME WHATEVER >
> 
>            object: messageDigest (1.2.840.113549.1.9.4)
>            value.set:
>              OCTET STRING:
>                < HASH GOES HERE >
>        signatureAlgorithm: 
>          algorithm: ecdsa-with-SHA1 (1.2.840.10045.4.1)
>          parameter: <ABSENT>
>        signature: 
>          < SIG GOES HERE >
>        unsignedAttrs:
>          <EMPTY>
> 
> If I output what the BIO at [1] contains, I see this:
> 
> CMS_ContentInfo: 
>  contentType: pkcs7-envelopedData (1.2.840.113549.1.7.3)
>  d.envelopedData: 
>    version: <ABSENT>
>    originatorInfo: <ABSENT>
>    recipientInfos:
>      d.ktri: 
>        version: 2
>        d.subjectKeyIdentifier: 
>          < KEY ID IS HERE >
>        keyEncryptionAlgorithm: 
>          algorithm: rsaEncryption (1.2.840.113549.1.1.1)
>          parameter: NULL
>        encryptedKey: 
>          < 256-BYTES LONG OCTET STRING, SINCE RSA-2048 >
>    encryptedContentInfo: 
>      contentType: pkcs7-data (1.2.840.113549.1.7.1)
>      contentEncryptionAlgorithm: 
>        algorithm: aes-256-cbc (2.16.840.1.101.3.4.1.42)
>        parameter: OCTET STRING:
>          < 16 BYTES IV >
>      encryptedContent: 
>          < LARGE OCTET STRING, ABOUT THE SIZE OF MY FILE PLUS AES PADDING I 
> FIGURE >
>    unprotectedAttrs:
>      <EMPTY>
> 
> 
> So I guess my questions are:
> 
> - Does the code look OK to people who regularly use the API?

It (mostly) looks like what I’d expect — see below.

> - Should I be worried the EnvelopedData show up as one big OctetString when 
> cms/asn1parse parse the SignedData?

No; I’d be concerned if it didn’t show up that way — that’s how CMS is defined 
to work.

> - Should I be worried the "version" field in the EnvelopedData shows <ABSENT>?

I’ll let someone else answer that; I would have expected version to be set 
properly.  I don’t recall seeing anything that would suggest you need to set 
the version yourself, since as you point out, the structures are opaque.

> Bonus points for anyone who can tell, on the spot, me how to recover:
> 
> - the signature;

If you look at your output, you can see where the signature is…  Anything that 
can parse CMS structures should be able to get to it without problems.  I’d 
recommend spending some time with RFC 5652 (I know, it’s poorly written, just 
read it several times, and look at the OpenSSL output, and you’ll figure it 
out).  If you’re not familiar with DER and BER encoding of ASN.1, I’d recommend 
reading http://luca.ntop.org/Teaching/Appunti/asn1.html first.  It’ll really 
help you understand some of the less than clear stuff in the RFC.

> - the bytes this signature actually signs;

Per the RFC, the bytes signed are the encapsulated data, and anything in 
signedAttrs.  Check the RFC to determine exactly which bytes of the signedAttrs 
are signed. :)

> - the encrypted key;

Recovering the encrypted key(s) is more complex — you’ll need to look at each 
of the RecipientInfos entries.  Each entry defines a recipient, and each entry 
indicates how to decrypt the key(s).  Again, the RFC will shed a lot of light 
on this process.  If you only deal with one X.509 certificate-based recipient, 
it’ll be much easier, as you’ll mostly need the detection code as a sanity 
check (e.g. verify the proper type of RecipientInfo is specified, and throw an 
error if it’s not).  

> - the encrypted file.

The encryptedContentInfo will indicate the encryption algorithm, iv, and any 
other parameters needed to actually decrypt the data.  The encryptedContent 
octet string is the actual encrypted data, what you’d pass to the decryption 
logic.

> 
> ... So that I may process them by some other, non-OpenSSL means on the 
> receiving end.
> 
> (I'm giving bonus points for this since OpenSSL implements all CMS types as 
> opaque structures, and I haven't digged enough yet to see how to recover 
> those fields. I'm starting to think maybe I should not use OpenSSL on the 
> receiving end...)
> 
> Thanks in advance!
> ______________________________________________________________________
> OpenSSL Project                                 http://www.openssl.org
> User Support Mailing List                    openssl-users@openssl.org
> Automated List Manager                           majord...@openssl.org
> 
______________________________________________________________________
OpenSSL Project                                 http://www.openssl.org
User Support Mailing List                    openssl-users@openssl.org
Automated List Manager                           majord...@openssl.org

Reply via email to