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