Thank you for your input!
> No; I’d be concerned if it didn’t show up that way — that’s how CMS is defined > to work. Just took another look at RFC 5652 and indeed, EncapsulatedContentInfo is a SEQUENCE of { ContentType, EXPLICIT OCTET STRING OPTIONAL }. > 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. Using asn1parse, I got this: 0:d=0 hl=4 l=3980 cons: SEQUENCE 4:d=1 hl=2 l= 9 prim: OBJECT :pkcs7-envelopedData 15:d=1 hl=4 l=3965 cons: cont [ 0 ] 19:d=2 hl=4 l=3961 cons: SEQUENCE 23:d=3 hl=2 l= 1 prim: INTEGER :00 26:d=3 hl=4 l= 304 cons: SET 30:d=4 hl=4 l= 300 cons: SEQUENCE 34:d=5 hl=2 l= 1 prim: INTEGER :02 37:d=5 hl=2 l= 20 prim: cont [ 0 ] 59:d=5 hl=2 l= 13 cons: SEQUENCE 61:d=6 hl=2 l= 9 prim: OBJECT :rsaEncryption 72:d=6 hl=2 l= 0 prim: NULL 74:d=5 hl=4 l= 256 prim: OCTET STRING [HEX DUMP]:<SNIP ENCRYPTED KEY> 334:d=3 hl=4 l=3646 cons: SEQUENCE 338:d=4 hl=2 l= 9 prim: OBJECT :pkcs7-data 349:d=4 hl=2 l= 29 cons: SEQUENCE 351:d=5 hl=2 l= 9 prim: OBJECT :aes-256-cbc 362:d=5 hl=2 l= 16 prim: OCTET STRING [HEX DUMP]:<SNIP IV> 380:d=4 hl=4 l=3600 prim: cont [ 0 ] >From the RFC an EnvelopedData should start with { Version, (Optional)Originator Info, SET of Recipient Info, ... }. So I guess here the version number is 0? This is weird, since I'm using a Subject Key Identifier: this condition "must" set the RecipientInfo's version field to 2 (which is happening), which in turn should make the EnvelopedData's version also 2 (which is not happening). ... Weird. > (Parsing ASN.1 stuff) Yup, I think I got the idea of how ASN.1 works, and I'm starting to be pretty familiar with the CMS RFC (and its various updates). The thing is, I'm having trouble finding a decent parser ; I've tried compiling the CMS ASN.1 spec into C classes with e.g. asn1c, but all the ASN.1 compilers I've tried choke on the syntax as it is written in the RFC (I also tried the spec from RFC 6268 but the programs still can't compile it). Should I bother using OpenSSL's ASN.1 API or should I continue trying to get an ASN.1 compiler to work with the CMS spec? ----- Original Message ----- From: "Tom Francis" <fr...@tcsaf.com> To: openssl-users@openssl.org Sent: Friday, May 2, 2014 2:11:25 PM Subject: Re: [1.0.1] Nested CMS structures 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 ______________________________________________________________________ OpenSSL Project http://www.openssl.org User Support Mailing List openssl-users@openssl.org Automated List Manager majord...@openssl.org