From e33d2ac356fa13ac1f5c5622d4c7bd00202161d2 Mon Sep 17 00:00:00 2001 From: Russ Housley Date: Tue, 8 Oct 2019 16:28:53 -0400 Subject: Add support for RFC 7914 (#77) --- CHANGES.txt | 1 + pyasn1_modules/rfc7914.py | 49 ++++++++++++++++++++++++ tests/__main__.py | 1 + tests/test_rfc7914.py | 95 +++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 146 insertions(+) create mode 100644 pyasn1_modules/rfc7914.py create mode 100644 tests/test_rfc7914.py diff --git a/CHANGES.txt b/CHANGES.txt index 4dbe1fa..9797e24 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -44,6 +44,7 @@ Revision 0.2.7, released XX-09-2019 - Added RFC8358 providing Digital Signatures on Internet-Draft Documents - Added RFC8209 providing BGPsec Router PKI Profile - Added RFC8017 providing PKCS #1 Version 2.2 +- Added RFC7914 providing scrypt Password-Based Key Derivation Function Revision 0.2.6, released 31-07-2019 ----------------------------------- diff --git a/pyasn1_modules/rfc7914.py b/pyasn1_modules/rfc7914.py new file mode 100644 index 0000000..99e9551 --- /dev/null +++ b/pyasn1_modules/rfc7914.py @@ -0,0 +1,49 @@ +# +# 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 +# +#The scrypt Password-Based Key Derivation Function +# +# ASN.1 source from: +# https://www.rfc-editor.org/rfc/rfc8520.txt +# https://www.rfc-editor.org/errata/eid5871 +# + +from pyasn1.type import constraint +from pyasn1.type import namedtype +from pyasn1.type import univ + +from pyasn1_modules import rfc5280 + +MAX = float('inf') + + +id_scrypt = univ.ObjectIdentifier('1.3.6.1.4.1.11591.4.11') + + +class Scrypt_params(univ.Sequence): + componentType = namedtype.NamedTypes( + namedtype.NamedType('salt', + univ.OctetString()), + namedtype.NamedType('costParameter', + univ.Integer().subtype(subtypeSpec=constraint.ValueRangeConstraint(1, MAX))), + namedtype.NamedType('blockSize', + univ.Integer().subtype(subtypeSpec=constraint.ValueRangeConstraint(1, MAX))), + namedtype.NamedType('parallelizationParameter', + univ.Integer().subtype(subtypeSpec=constraint.ValueRangeConstraint(1, MAX))), + namedtype.OptionalNamedType('keyLength', + univ.Integer().subtype(subtypeSpec=constraint.ValueRangeConstraint(1, MAX))) + ) + + +# Update the Algorithm Identifier map in rfc5280.py + +_algorithmIdentifierMapUpdate = { + id_scrypt: Scrypt_params(), +} + +rfc5280.algorithmIdentifierMap.update(_algorithmIdentifierMapUpdate) diff --git a/tests/__main__.py b/tests/__main__.py index 36290c6..b79752f 100644 --- a/tests/__main__.py +++ b/tests/__main__.py @@ -60,6 +60,7 @@ suite = unittest.TestLoader().loadTestsFromNames( 'tests.test_rfc7296.suite', 'tests.test_rfc7894.suite', 'tests.test_rfc7906.suite', + 'tests.test_rfc7914.suite', 'tests.test_rfc8017.suite', 'tests.test_rfc8018.suite', 'tests.test_rfc8103.suite', diff --git a/tests/test_rfc7914.py b/tests/test_rfc7914.py new file mode 100644 index 0000000..1ab8b8f --- /dev/null +++ b/tests/test_rfc7914.py @@ -0,0 +1,95 @@ +# +# 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_modules import pem +from pyasn1_modules import rfc5280 +from pyasn1_modules import rfc5958 +from pyasn1_modules import rfc7914 +from pyasn1_modules import rfc8018 + +try: + import unittest2 as unittest + +except ImportError: + import unittest + + +# From RFC 7914, Section 13 + +class MultiprimeRSAPrivateKeyTestCase(unittest.TestCase): + pem_text = """\ +MIHiME0GCSqGSIb3DQEFDTBAMB8GCSsGAQQB2kcECzASBAVNb3VzZQIDEAAAAgEI +AgEBMB0GCWCGSAFlAwQBKgQQyYmguHMsOwzGMPoyObk/JgSBkJb47EWd5iAqJlyy ++ni5ftd6gZgOPaLQClL7mEZc2KQay0VhjZm/7MbBUNbqOAXNM6OGebXxVp6sHUAL +iBGY/Dls7B1TsWeGObE0sS1MXEpuREuloZjcsNVcNXWPlLdZtkSH6uwWzR0PyG/Z ++ZXfNodZtd/voKlvLOw5B3opGIFaLkbtLZQwMiGtl42AS89lZg== +""" + + def setUp(self): + self.asn1Spec = rfc5958.EncryptedPrivateKeyInfo() + + def testDerCodec(self): + substrate = pem.readBase64fromText(self.pem_text) + asn1Object, rest = der_decode(substrate, asn1Spec=self.asn1Spec) + assert not rest + assert asn1Object.prettyPrint() + assert der_encode(asn1Object) == substrate + + ea = asn1Object['encryptionAlgorithm'] + assert ea['algorithm'] == rfc8018.id_PBES2 + assert ea['algorithm'] in rfc5280.algorithmIdentifierMap.keys() + + params, rest = der_decode(ea['parameters'], + asn1Spec=rfc5280.algorithmIdentifierMap[ea['algorithm']]) + assert not rest + assert params.prettyPrint() + assert der_encode(params) == ea['parameters'] + + kdf = params['keyDerivationFunc'] + assert kdf['algorithm'] == rfc7914.id_scrypt + assert kdf['algorithm'] in rfc5280.algorithmIdentifierMap.keys() + + kdfp, rest = der_decode(kdf['parameters'], + asn1Spec=rfc5280.algorithmIdentifierMap[kdf['algorithm']]) + assert not rest + assert kdfp.prettyPrint() + assert der_encode(kdfp) == kdf['parameters'] + + assert kdfp['costParameter'] == 1048576 + + def testOpenTypes(self): + 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 + + ea = asn1Object['encryptionAlgorithm'] + assert ea['algorithm'] == rfc8018.id_PBES2 + + params = asn1Object['encryptionAlgorithm']['parameters'] + assert params['keyDerivationFunc']['algorithm'] == rfc7914.id_scrypt + + kdfp = params['keyDerivationFunc']['parameters'] + assert kdfp['costParameter'] == 1048576 + + +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