*and by "ghetto", I mean "extremely applied". Basically, it's just interpretive labor on RFC 5652: Cryptographic Message Syntax and RFC 5280: X.509.
P.S. CMS is way more technically elegant than PGP. I dare you to read the RFC and compare.
Firstly: the "root object" that you'll be shitting nakedly into a binary (or ASCII-armored) file that sits in the filesystem or gets otherwise transferred is a ContentInfo
data stream. This is an ASN.1 SEQUENCE
with 2 elements:
OBJECT IDENTIFIER
giving "context" and defining what the following element should contain[0] EXPLICIT OCTET STRING OPTIONAL
, containing whatever the OID in the first element says to
Before we go on, just take note that AlgorithmIdentifier ::= SEQUENCE { algorithm OBJECT IDENTIFIER, parameters ANY DEFINED BY algorithm OPTIONAL }
; I didn't want to define terms that aren't directly in a critical chain of logic, but this one is used all the frickin time here so I figured I'd define it for convenience.
We'll generally want the EnvelopedData
content type (1.2.840.113549.1.7.3
) at the first level; it is a SEQUENCE
with 3 to 5 elements:
version
(an integer)[0] IMPLICIT OriginatorInfo OPTIONAL
SET OF CHOICE { KeyTransRecipentInfo, [1] KeyAgreeRecipientInfo, [2] KEKRecipientInfo, [3] PasswordRecipientInfo, [4] OtherRecipientInfo }
EncryptedContentInfo
[1] IMPLICIT UnprotectedAttributes SET OF AlgorithmIdentifier OPTIONAL
- A kinda-comprehensive list of choices can be found here
- Abandon all hope, ye who enter here
- You probably should include at least
id-aa-contentHint
(1.2.840.113549.1.9.16.2.4
) to specify thecontentType
of your plaintext data - if this field is present, then the
version
field must be 2
- A kinda-comprehensive list of choices can be found here
Currently, we'll focus on PasswordRecipientInfo
, which was defined in RFC 3211 as a SEQUENCE
with 3 to 4 elements:
version
(always equal to 0)keyDerivationAlgorithm [0] AlgorithmIdentifier OPTIONAL
- PBKDF2 (
1.2.840.113549.1.5.12
) is a good choice here PBKDF2-params ::= SEQUENCE { salt CHOICE { specified OCTET STRING, otherSource AlgorithmIdentifier }, iterationCount INTEGER, keyLength INTEGER OPTIONAL, prf AlgorithmIdentifier DEFAULT hMAC-SHA1 }
- PBKDF2 (
keyEncryptionAlgorithm AlgorithmIdentifier
- officially prescribed is this convoluted algorithm or these mediocre options. I think we can do better than either, these days.
encryptedKey OCTET STRING
EncryptedContentInfo
is a SEQUENCE
with 2 to 3 elements:
contentType OBJECT IDENTIFIER
contentEncryptionAlgorithm AlgorithmIdentifier
- Nonces generally go here, but it's entirely up to whoever defined the selected
algorithm
- Nonces generally go here, but it's entirely up to whoever defined the selected
encryptedContent [0] IMPLICIT OPTIONAL OCTET STRING DEFINED BY contentEncryptionAlgorithm
- “
encryptedContent
is the result of encrypting the content. The field is optional, [but] if [it] is not present, its intended value must be supplied by other means.”
- “
TODO: SignedData
, which is a complicated composite doodad that supports most other use-cases as a subset