From e6aca617f808e2ae1ddcc0c1221a41eef73644b4 Mon Sep 17 00:00:00 2001 From: Russ Housley Date: Tue, 22 Oct 2019 16:50:27 -0400 Subject: Add support for RFC 6486 (#90) --- CHANGES.txt | 1 + pyasn1_modules/rfc6486.py | 68 +++++++++++++++++++++++++ tests/__main__.py | 1 + tests/test_rfc6486.py | 123 ++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 193 insertions(+) create mode 100644 pyasn1_modules/rfc6486.py create mode 100644 tests/test_rfc6486.py diff --git a/CHANGES.txt b/CHANGES.txt index 2b942e6..006a913 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -10,6 +10,7 @@ Revision 0.2.8, released XX-XX-2019 - Added RFC6960 providing Online Certificate Status Protocol (OCSP) - Added RFC6955 providing Diffie-Hellman Proof-of-Possession Algorithms - Updated the handling of maps for use with openType for RFC 3279 +- Added RFC6486 providing RPKI Manifests Revision 0.2.7, released 09-10-2019 ----------------------------------- diff --git a/pyasn1_modules/rfc6486.py b/pyasn1_modules/rfc6486.py new file mode 100644 index 0000000..31c936a --- /dev/null +++ b/pyasn1_modules/rfc6486.py @@ -0,0 +1,68 @@ +# +# This file is part of 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 +# +# RPKI Manifests +# +# ASN.1 source from: +# https://www.rfc-editor.org/rfc/rfc6486.txt +# + +from pyasn1.type import char +from pyasn1.type import constraint +from pyasn1.type import namedtype +from pyasn1.type import tag +from pyasn1.type import useful +from pyasn1.type import univ + +from pyasn1_modules import rfc5652 + +MAX = float('inf') + + +id_smime = univ.ObjectIdentifier('1.2.840.113549.1.9.16') + +id_ct = id_smime + (1, ) + +id_ct_rpkiManifest = id_ct + (26, ) + + +class FileAndHash(univ.Sequence): + componentType = namedtype.NamedTypes( + namedtype.NamedType('file', char.IA5String()), + namedtype.NamedType('hash', univ.BitString()) + ) + + +class Manifest(univ.Sequence): + componentType = namedtype.NamedTypes( + namedtype.DefaultedNamedType('version', + univ.Integer().subtype(explicitTag=tag.Tag( + tag.tagClassContext, tag.tagFormatSimple, 0)).subtype(value=0)), + namedtype.NamedType('manifestNumber', + univ.Integer().subtype( + subtypeSpec=constraint.ValueRangeConstraint(0, MAX))), + namedtype.NamedType('thisUpdate', + useful.GeneralizedTime()), + namedtype.NamedType('nextUpdate', + useful.GeneralizedTime()), + namedtype.NamedType('fileHashAlg', + univ.ObjectIdentifier()), + namedtype.NamedType('fileList', + univ.SequenceOf(componentType=FileAndHash()).subtype( + subtypeSpec=constraint.ValueSizeConstraint(0, MAX))) + ) + + +# Map of Content Type OIDs to Content Types added to the +# ones that are in rfc5652.py + +_cmsContentTypesMapUpdate = { + id_ct_rpkiManifest: Manifest(), +} + +rfc5652.cmsContentTypesMap.update(_cmsContentTypesMapUpdate) diff --git a/tests/__main__.py b/tests/__main__.py index c852b56..48e674d 100644 --- a/tests/__main__.py +++ b/tests/__main__.py @@ -59,6 +59,7 @@ suite = unittest.TestLoader().loadTestsFromNames( 'tests.test_rfc6032.suite', 'tests.test_rfc6210.suite', 'tests.test_rfc6211.suite', + 'tests.test_rfc6486.suite', 'tests.test_rfc6955.suite', 'tests.test_rfc6960.suite', 'tests.test_rfc7030.suite', diff --git a/tests/test_rfc6486.py b/tests/test_rfc6486.py new file mode 100644 index 0000000..70e9160 --- /dev/null +++ b/tests/test_rfc6486.py @@ -0,0 +1,123 @@ +# +# This file is part of pyasn1-modules software. +# +# 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_modules import pem +from pyasn1_modules import rfc5652 +from pyasn1_modules import rfc6486 + +try: + import unittest2 as unittest +except ImportError: + import unittest + + +class SignedManifestTestCase(unittest.TestCase): + manifest_pem_text = """\ +MIIHVAYJKoZIhvcNAQcCoIIHRTCCB0ECAQMxDTALBglghkgBZQMEAgEwgYwGCyqGSIb3DQEJ +EAEaoH0EezB5AgIK5xgPMjAxMjEwMjMyMjI2MDNaGA8yMDEyMTAyNTIyMjYwM1oGCWCGSAFl +AwQCATBGMEQWH1pYU0dCREJrTDgyVEZHSHVFNFZPWXRKUC1FNC5jcmwDIQCzTdC3GsuONsRq +RFnYf8+AJ2NnCIgmnc3O8PyfGvn18aCCBO4wggTqMIID0qADAgECAgIK5zANBgkqhkiG9w0B +AQsFADATMREwDwYDVQQDEwhBOTE5OTg4NTAeFw0xMjEwMjMyMjI2MDNaFw0xMjEwMjUyMjI2 +MDNaMBgxFjAUBgNVBAMTDTUwODcxOTdjLTIwZjcwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw +ggEKAoIBAQDEl4R4LiCs6zyR/IAeaRCfz0O0mXXAUKt8bmG6DXzaDYNG8dnBjbrsM1L05sb4 +2Ti4TyE1UXtwFFEwatsFQ2uRBn9gsKmDGOjW8TH1AYObmZW+hZlEN7OLSz2bmPLtxIMwiCq/ +vqmBJlMWPyCSym4iPnjzwWbJechqHSiTMOYGICF1QSW5xjJDAhRfeZG3nRY7TqfW8R2KJXeN +cKSYSGNKzv79B8GCswmwU8J8kcuryIiqb7WtcK2B6VBsROIQHGXM0UV4Zbnvv9m9Fl0SjvZJ +XyrzRjGzV2C00hM0f4jAplD9nJhAJ7nOTe8OnadrFABRga+Ge1HooeDQJGmTekLXAgMBAAGj +ggJBMIICPTAdBgNVHQ4EFgQUbcbOyNBHkRXXDaMq51jC7vOSHFUwHwYDVR0jBBgwFoAUZXSG +BDBkL82TFGHuE4VOYtJP+E4wDgYDVR0PAQH/BAQDAgeAMIGDBgNVHR8EfDB6MHigdqB0hnJy +c3luYzovL3Jwa2kuYXBuaWMubmV0L21lbWJlcl9yZXBvc2l0b3J5L0E5MTk5ODg1LzY1RkQ0 +M0FBNUJFRjExREZBQjYxQjNFNzU1QUZFN0NGL1pYU0dCREJrTDgyVEZHSHVFNFZPWXRKUC1F +NC5jcmwwfgYIKwYBBQUHAQEEcjBwMG4GCCsGAQUFBzAChmJyc3luYzovL3Jwa2kuYXBuaWMu +bmV0L3JlcG9zaXRvcnkvQTNDMzhBMjRENjAzMTFEQ0FCMDhGMzE5NzlCREJFMzkvWlhTR0JE +QmtMODJURkdIdUU0Vk9ZdEpQLUU0LmNlcjAYBgNVHSABAf8EDjAMMAoGCCsGAQUFBw4CMIGQ +BggrBgEFBQcBCwSBgzCBgDB+BggrBgEFBQcwC4ZycnN5bmM6Ly9ycGtpLmFwbmljLm5ldC9t +ZW1iZXJfcmVwb3NpdG9yeS9BOTE5OTg4NS82NUZENDNBQTVCRUYxMURGQUI2MUIzRTc1NUFG +RTdDRi9aWFNHQkRCa0w4MlRGR0h1RTRWT1l0SlAtRTQubWZ0MBUGCCsGAQUFBwEIAQH/BAYw +BKACBQAwIQYIKwYBBQUHAQcBAf8EEjAQMAYEAgABBQAwBgQCAAIFADANBgkqhkiG9w0BAQsF +AAOCAQEAyBl1J+ql1O3d6JiaQEG2UAjDSKHSMVau++QcB6/yd4RuWv2KpQxk1cp+awf4Ttoh +GYakbUZQl7lJaXzbluG5siRSv6AowEWxf99iLhDx+pE1htklRfmmTE9oFpKnITAYZAUjarNC +sYGCZ00vSwRu27OdpSQbZQ7WdyDAhyHS0Sun0pkImVSqPO11gqyKV9ZCwCJUa5U/zsWDMNrj +MSZl1I3VoPs2rx997rLoiQiMqwGeoqfl7snpsL9OR/CazPmepuq3SyZNWcCrUGcGRhRdGScj +Tm2EHne1GiRHapn46HWQ3am8jumEKv5u0gLT4Mi9CyZwkDyhotGTJZmdAmN7zzGCAaowggGm +AgEDgBRtxs7I0EeRFdcNoyrnWMLu85IcVTALBglghkgBZQMEAgGgazAaBgkqhkiG9w0BCQMx +DQYLKoZIhvcNAQkQARowHAYJKoZIhvcNAQkFMQ8XDTEyMTAyMzIyMjYwNFowLwYJKoZIhvcN +AQkEMSIEIIu2XV8dT+rqQy5Cbpm3Tv5I1dwkLK8n2GesMGOr6/pEMA0GCSqGSIb3DQEBAQUA +BIIBAFsd0zkl4dIHrqZts441T+w/5/ekymDLFwftk6W+Mi35Htjvm2IHOthnKHQsK5h6dnEh +6DfNfc6tACmzLnM+UG7ve+uAhfpA+CUJIoVhpQvDH7Ntql0cD1X3d9ng484jpkVoHhbUIYNR +TyxvV4DV5EBbLYpx2HYf6wWa8TCobxUXNtw53OVA24ceavS+KvuDa0JQPFpbYUCS0UPMt/Im +mtKrWTmRUr8sYWdIQn+SStUh8iAR5rmSVr+Pe7aFbe2ju2FPf08gnIjH/SdCrJuFK8q7Z5MT +C9ijmXiajracUe+7eCluqgXRE8yRtnscWoA/9fVFz1lPwgEeNHLoaK7Sqew= +""" + + def testDerCodec(self): + substrate = pem.readBase64fromText(self.manifest_pem_text) + + layers = { } + layers.update(rfc5652.cmsContentTypesMap) + + getNextLayer = { + rfc5652.id_ct_contentInfo: lambda x: x['contentType'], + rfc5652.id_signedData: lambda x: x['encapContentInfo']['eContentType'], + rfc6486.id_ct_rpkiManifest: lambda x: None + } + + getNextSubstrate = { + rfc5652.id_ct_contentInfo: lambda x: x['content'], + rfc5652.id_signedData: lambda x: x['encapContentInfo']['eContent'], + rfc6486.id_ct_rpkiManifest: lambda x: None + } + + next_layer = rfc5652.id_ct_contentInfo + while next_layer: + asn1Object, rest = der_decode(substrate, asn1Spec=layers[next_layer]) + assert not rest + assert asn1Object.prettyPrint() + assert der_encode(asn1Object) == substrate + + substrate = getNextSubstrate[next_layer](asn1Object) + next_layer = getNextLayer[next_layer](asn1Object) + + assert asn1Object['version'] == 0 + for f in asn1Object['fileList']: + assert f['file'] == 'ZXSGBDBkL82TFGHuE4VOYtJP-E4.crl' + + def testOpenTypes(self): + substrate = pem.readBase64fromText(self.manifest_pem_text) + asn1Object, rest = der_decode(substrate, + asn1Spec=rfc5652.ContentInfo(), + decodeOpenTypes=True) + assert not rest + assert asn1Object.prettyPrint() + assert der_encode(asn1Object) == substrate + + oid = asn1Object['content']['encapContentInfo']['eContentType'] + substrate = asn1Object['content']['encapContentInfo']['eContent'] + assert oid in rfc5652.cmsContentTypesMap.keys() + asn1Object, rest = der_decode(substrate, + asn1Spec=rfc5652.cmsContentTypesMap[oid], + decodeOpenTypes=True) + assert not rest + assert asn1Object.prettyPrint() + assert der_encode(asn1Object) == substrate + + assert asn1Object['version'] == 0 + for f in asn1Object['fileList']: + assert f['file'] == 'ZXSGBDBkL82TFGHuE4VOYtJP-E4.crl' + + +suite = unittest.TestLoader().loadTestsFromModule(sys.modules[__name__]) + +if __name__ == '__main__': + import sys + + result = unittest.TextTestRunner(verbosity=2).run(suite) + sys.exit(not result.wasSuccessful()) -- cgit v1.2.3