aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGES.txt1
-rw-r--r--pyasn1_modules/rfc2985.py7
-rw-r--r--pyasn1_modules/rfc5751.py124
-rw-r--r--tests/__main__.py1
-rw-r--r--tests/test_rfc2985.py8
-rw-r--r--tests/test_rfc5751.py109
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())