diff options
-rw-r--r-- | CHANGES.txt | 1 | ||||
-rw-r--r-- | pyasn1_modules/rfc2985.py | 7 | ||||
-rw-r--r-- | pyasn1_modules/rfc5751.py | 124 | ||||
-rw-r--r-- | tests/__main__.py | 1 | ||||
-rw-r--r-- | tests/test_rfc2985.py | 8 | ||||
-rw-r--r-- | tests/test_rfc5751.py | 109 |
6 files changed, 247 insertions, 3 deletions
diff --git a/CHANGES.txt b/CHANGES.txt index bf7e8ad..1e37a2b 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -30,6 +30,7 @@ Revision 0.2.7, released XX-09-2019 - Fixed malformed `rfc4210.RevRepContent` data structure layout - Added RFC5934 providing Trust Anchor Management Protocol (TAMP) - Added RFC6210 providing Experiment for Hash Functions with Parameters +- Added RFC5751 providing S/MIME Version 3.2 Message Specification Revision 0.2.6, released 31-07-2019 ----------------------------------- diff --git a/pyasn1_modules/rfc2985.py b/pyasn1_modules/rfc2985.py index c8ebb0c..75bccf0 100644 --- a/pyasn1_modules/rfc2985.py +++ b/pyasn1_modules/rfc2985.py @@ -565,6 +565,11 @@ rfc5280.certificateAttributesMap.update(_certificateAttributesMapUpdate) # CMS Attribute Map +# Note: pkcs_9_at_smimeCapabilities is not included in the map because +# the definition in RFC 5751 is preferred, which produces the same +# encoding, but it allows different parameters for SMIMECapability +# and AlgorithmIdentifier. + _cmsAttributesMapUpdate = { # Attribute types for use in PKCS #7 data (a.k.a. CMS) pkcs_9_at_contentType: ContentType(), @@ -577,7 +582,7 @@ _cmsAttributesMapUpdate = { pkcs_9_at_friendlyName: FriendlyName(), pkcs_9_at_localKeyId: univ.OctetString(), pkcs_9_at_signingDescription: DirectoryString(), - pkcs_9_at_smimeCapabilities: SMIMECapabilities(), + # pkcs_9_at_smimeCapabilities: SMIMECapabilities(), } rfc5652.cmsAttributesMap.update(_cmsAttributesMapUpdate) diff --git a/pyasn1_modules/rfc5751.py b/pyasn1_modules/rfc5751.py new file mode 100644 index 0000000..7e20001 --- /dev/null +++ b/pyasn1_modules/rfc5751.py @@ -0,0 +1,124 @@ +# This file is being contributed to pyasn1-modules software. +# +# Created by Russ Housley with assistance from asn1ate v.0.6.0. +# +# Copyright (c) 2019, Vigil Security, LLC +# License: http://snmplabs.com/pyasn1/license.html +# +# S/MIME Version 3.2 Message Specification +# +# ASN.1 source from: +# https://www.rfc-editor.org/rfc/rfc5751.txt + +from pyasn1.type import namedtype +from pyasn1.type import opentype +from pyasn1.type import tag +from pyasn1.type import univ + +from pyasn1_modules import rfc5652 +from pyasn1_modules import rfc8018 + + +def _OID(*components): + output = [] + for x in tuple(components): + if isinstance(x, univ.ObjectIdentifier): + output.extend(list(x)) + else: + output.append(int(x)) + return univ.ObjectIdentifier(output) + + +# Imports from RFC 5652 and RFC 8018 + +IssuerAndSerialNumber = rfc5652.IssuerAndSerialNumber + +RecipientKeyIdentifier = rfc5652.RecipientKeyIdentifier + +SubjectKeyIdentifier = rfc5652.SubjectKeyIdentifier + +rc2CBC = rfc8018.rc2CBC + + +# S/MIME Capabilities Attribute + +smimeCapabilities = univ.ObjectIdentifier('1.2.840.113549.1.9.15') + + +smimeCapabilityMap = { } + + +class SMIMECapability(univ.Sequence): + pass + +SMIMECapability.componentType = namedtype.NamedTypes( + namedtype.NamedType('capabilityID', univ.ObjectIdentifier()), + namedtype.OptionalNamedType('parameters', univ.Any(), + openType=opentype.OpenType('capabilityID', smimeCapabilityMap)) +) + + +class SMIMECapabilities(univ.SequenceOf): + pass + +SMIMECapabilities.componentType = SMIMECapability() + + +class SMIMECapabilitiesParametersForRC2CBC(univ.Integer): + # which carries the RC2 Key Length (number of bits) + pass + + +# S/MIME Encryption Key Preference Attribute + +id_smime = univ.ObjectIdentifier('1.2.840.113549.1.9.16') + +id_aa = _OID(id_smime, 2) + +id_aa_encrypKeyPref = _OID(id_aa, 11) + + +class SMIMEEncryptionKeyPreference(univ.Choice): + pass + +SMIMEEncryptionKeyPreference.componentType = namedtype.NamedTypes( + namedtype.NamedType('issuerAndSerialNumber', + IssuerAndSerialNumber().subtype(implicitTag=tag.Tag( + tag.tagClassContext, tag.tagFormatSimple, 0))), + namedtype.NamedType('receipentKeyId', + # Yes, 'receipentKeyId' is spelled incorrectly, but kept + # this way for alignment with the ASN.1 module in the RFC. + RecipientKeyIdentifier().subtype(implicitTag=tag.Tag( + tag.tagClassContext, tag.tagFormatSimple, 1))), + namedtype.NamedType('subjectAltKeyIdentifier', + SubjectKeyIdentifier().subtype(implicitTag=tag.Tag( + tag.tagClassContext, tag.tagFormatSimple, 2))) +) + + +# The Prefer Binary Inside SMIMECapabilities attribute + +id_cap = _OID(id_smime, 11) + +id_cap_preferBinaryInside = _OID(id_cap, 1) + + +# CMS Attribute Map + +_cmsAttributesMapUpdate = { + smimeCapabilities: SMIMECapabilities(), + id_aa_encrypKeyPref: SMIMEEncryptionKeyPreference(), +} + +rfc5652.cmsAttributesMap.update(_cmsAttributesMapUpdate) + + +# SMIMECapabilities Attribute Map +# +# Do not include OIDs in the dictionary when the parameters are absent. + +_smimeCapabilityMapUpdate = { + rc2CBC: SMIMECapabilitiesParametersForRC2CBC(), +} + +smimeCapabilityMap.update(_smimeCapabilityMapUpdate) diff --git a/tests/__main__.py b/tests/__main__.py index 7ee3fb7..56c2fd4 100644 --- a/tests/__main__.py +++ b/tests/__main__.py @@ -40,6 +40,7 @@ suite = unittest.TestLoader().loadTestsFromNames( 'tests.test_rfc5480.suite', 'tests.test_rfc5649.suite', 'tests.test_rfc5652.suite', + 'tests.test_rfc5751.suite', 'tests.test_rfc5914.suite', 'tests.test_rfc5915.suite', 'tests.test_rfc5934.suite', diff --git a/tests/test_rfc2985.py b/tests/test_rfc2985.py index 466d50b..f95ede8 100644 --- a/tests/test_rfc2985.py +++ b/tests/test_rfc2985.py @@ -129,7 +129,9 @@ HktMK+isIjxOTk4yJTOOAgIH0A== assert asn1Object.prettyPrint() assert der_encode(asn1Object) == substrate - openTypesMap = { } + openTypesMap = { + rfc2985.pkcs_9_at_smimeCapabilities: rfc2985.SMIMECapabilities(), + } openTypesMap.update(rfc5280.certificateAttributesMap) openTypesMap.update(rfc5652.cmsAttributesMap) @@ -218,7 +220,9 @@ HktMK+isIjxOTk4yJTOOAgIH0A== assert nv == 'alice@example.com' def testOpenTypes(self): - openTypesMap = { } + openTypesMap = { + rfc2985.pkcs_9_at_smimeCapabilities: rfc2985.SMIMECapabilities(), + } openTypesMap.update(rfc5280.certificateAttributesMap) openTypesMap.update(rfc5652.cmsAttributesMap) diff --git a/tests/test_rfc5751.py b/tests/test_rfc5751.py new file mode 100644 index 0000000..a89457a --- /dev/null +++ b/tests/test_rfc5751.py @@ -0,0 +1,109 @@ +# +# This file is part of pyasn1-modules software. +# +# Created by Russ Housley +# Copyright (c) 2019, Vigil Security, LLC +# License: http://snmplabs.com/pyasn1/license.html +# + +import sys + +from pyasn1.codec.der.decoder import decode as der_decode +from pyasn1.codec.der.encoder import encode as der_encode + +from pyasn1.type import univ + +from pyasn1_modules import pem +from pyasn1_modules import rfc5652 +from pyasn1_modules import rfc5751 + +try: + import unittest2 as unittest +except ImportError: + import unittest + + +class SignedMessageTestCase(unittest.TestCase): + pem_text = """\ +MIIGigYJKoZIhvcNAQcCoIIGezCCBncCAQExCTAHBgUrDgMCGjArBgkqhkiG9w0B +BwGgHgQcVGhpcyBpcyBzb21lIHNhbXBsZSBjb250ZW50LqCCAuAwggLcMIICm6AD +AgECAgIAyDAJBgcqhkjOOAQDMBIxEDAOBgNVBAMTB0NhcmxEU1MwHhcNOTkwODE3 +MDExMDQ5WhcNMzkxMjMxMjM1OTU5WjATMREwDwYDVQQDEwhBbGljZURTUzCCAbYw +ggErBgcqhkjOOAQBMIIBHgKBgQCBjc3tg+oKnjk+wkgoo+RHk90O16gO7FPFq4QI +T/+U4XNIfgzW80RI0f6fr6ShiS/h2TDINt4/m7+3TNxfaYrkddA3DJEIlZvep175 +/PSfL91DqItU8T+wBwhHTV2Iw8O1s+NVCHXVOXYQxHi9/52whJc38uRRG7XkCZZc +835b2wIVAOJHphpFZrgTxtqPuDchK2KL95PNAoGAJjjQFIkyqjn7Pm3ZS1lqTHYj +OQQCNVzyyxowwx5QXd2bWeLNqgU9WMB7oja4bgevfYpCJaf0dc9KCF5LPpD4beqc +ySGKO3YU6c4uXaMHzSOFuC8wAXxtSYkRiTZEvfjIlUpTVrXi+XPsGmE2HxF/wr3t +0VD/mHTC0YFKYDm6NjkDgYQAAoGAXOO5WnUUlgupet3jP6nsrF7cvbcTETSmFoko +ESPZNIZndXUTEj1DW2/lUb/6ifKiGz4kfT0HjVtjyLtFpaBK44XWzgaAP+gjfhry +JKtTGrgnDR7vCL9mFIBcYqxl+hWL8bs01NKWN/ZhR7LEMoTwfkFA/UanY04z8qXi +9PKD5bijgYEwfzAMBgNVHRMBAf8EAjAAMA4GA1UdDwEB/wQEAwIGwDAfBgNVHSME +GDAWgBRwRD6CLm+H3krTdeM9ILxDK5PxHzAdBgNVHQ4EFgQUvmyhs+PB9+1DcKTO +EwHi/eOX/s0wHwYDVR0RBBgwFoEUQWxpY2VEU1NAZXhhbXBsZS5jb20wCQYHKoZI +zjgEAwMwADAtAhRVDKQZH0IriXEiM42DarU9Z2u/RQIVAJ9hU1JUC1yy3drndh3i +EFJbQ169MYIDVDCCA1ACAQEwGDASMRAwDgYDVQQDEwdDYXJsRFNTAgIAyDAHBgUr +DgMCGqCCAuowGAYJKoZIhvcNAQkDMQsGCSqGSIb3DQEHATAjBgkqhkiG9w0BCQQx +FgQUQGrsCFJ5um4WAi2eBinAIpaH3UgwOAYDKqszMTEEL1RoaXMgaXMgYSB0ZXN0 +IEdlbmVyYWwgQVNOIEF0dHJpYnV0ZSwgbnVtYmVyIDEuMD4GCyqGSIb3DQEJEAIE +MS8wLQwgQ29udGVudCBIaW50cyBEZXNjcmlwdGlvbiBCdWZmZXIGCSqGSIb3DQEH +ATBKBgkqhkiG9w0BCQ8xPTA7MAcGBSoDBAUGMDAGBioDBAUGTQQmU21pbWUgQ2Fw +YWJpbGl0aWVzIHBhcmFtZXRlcnMgYnVmZmVyIDIwbwYLKoZIhvcNAQkQAgoxYDBe +BgUqAwQFBgQrQ29udGVudCBSZWZlcmVuY2UgQ29udGVudCBJZGVudGlmaWVyIEJ1 +ZmZlcgQoQ29udGVudCBSZWZlcmVuY2UgU2lnbmF0dXJlIFZhbHVlIEJ1ZmZlcjBz +BgsqhkiG9w0BCRACCzFkoGIwWjELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDVVTIEdv +dmVybm1lbnQxETAPBgNVBAsTCFZEQSBTaXRlMQwwCgYDVQQLEwNWREExEjAQBgNV +BAMTCURhaXN5IFJTQQIEClVEMzCB/AYLKoZIhvcNAQkQAgMxgewwgekwgeYEBzU3 +MzgyOTkYDzE5OTkwMzExMTA0NDMzWqGByTCBxqRhMF8xCzAJBgNVBAYTAlVTMRYw +FAYDVQQKEw1VUyBHb3Zlcm5tZW50MREwDwYDVQQLEwhWREEgU2l0ZTEMMAoGA1UE +CxMDVkRBMRcwFQYDVQQDEw5CdWdzIEJ1bm55IERTQaRhMF8xCzAJBgNVBAYTAlVT +MRYwFAYDVQQKEw1VUyBHb3Zlcm5tZW50MREwDwYDVQQLEwhWREEgU2l0ZTEMMAoG +A1UECxMDVkRBMRcwFQYDVQQDEw5FbG1lciBGdWRkIERTQTAJBgcqhkjOOAQDBC8w +LQIVALwzN2XE93BcF0kTqkyFyrtSkUhZAhRjlqIUi89X3rBIX2xk3YQESV8cyg== +""" + + def setUp(self): + self.asn1Spec = rfc5652.ContentInfo() + + def testDerCodec(self): + smimeCapMap = { + univ.ObjectIdentifier('1.2.3.4.5.6.77'): univ.OctetString(), + } + smimeCapMap.update(rfc5751.smimeCapabilityMap) + + substrate = pem.readBase64fromText(self.pem_text) + asn1Object, rest = der_decode (substrate, + asn1Spec=self.asn1Spec, + decodeOpenTypes=True) + assert not rest + assert asn1Object.prettyPrint() + assert der_encode(asn1Object) == substrate + + assert asn1Object['contentType'] == rfc5652.id_signedData + assert asn1Object['content']['version'] == 1 + + for si in asn1Object['content']['signerInfos']: + assert si['version'] == 1 + for attr in si['signedAttrs']: + + if attr['attrType'] == rfc5751.smimeCapabilities: + for scap in attr['attrValues'][0]: + if scap['capabilityID'] in smimeCapMap.keys(): + scap_p, rest = der_decode(scap['parameters'], + asn1Spec=smimeCapMap[scap['capabilityID']]) + assert not rest + assert der_encode(scap_p) == scap['parameters'] + assert 'parameters' in scap_p.prettyPrint() + + if attr['attrType'] == rfc5751.id_aa_encrypKeyPref: + ekp_issuer_serial = attr['attrValues'][0]['issuerAndSerialNumber'] + assert ekp_issuer_serial['serialNumber'] == 173360179 + + +suite = unittest.TestLoader().loadTestsFromModule(sys.modules[__name__]) + +if __name__ == '__main__': + import sys + + result = unittest.TextTestRunner(verbosity=2).run(suite) + sys.exit(not result.wasSuccessful()) |