aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIlya Etingof <etingof@gmail.com>2019-01-26 18:20:17 +0100
committerGitHub <noreply@github.com>2019-01-26 18:20:17 +0100
commit456d3f2987b901b3cd9dbb7774926b9d362c2f59 (patch)
tree0a62eb0dcd39f24502ab3b9307c64a07be9653ca
parentee7f9f20a2464bf52b3895efe5f6c5ab999520eb (diff)
downloadpyasn1-modules-456d3f2987b901b3cd9dbb7774926b9d362c2f59.tar.gz
Add RFC8226 (#21)
Implement RFC8226 Implements JWT Claim Constraints and TN Authorization List for X.509 certificate extensions. Also fixes bug in `rfc5280.AlgorithmIdentifier` ANY type definition.
-rw-r--r--pyasn1_modules/rfc5084.py23
-rw-r--r--pyasn1_modules/rfc5280.py2
-rw-r--r--pyasn1_modules/rfc8103.py38
-rw-r--r--pyasn1_modules/rfc8226.py123
-rw-r--r--tests/test_rfc8103.py49
-rw-r--r--tests/test_rfc8226.py54
6 files changed, 276 insertions, 13 deletions
diff --git a/pyasn1_modules/rfc5084.py b/pyasn1_modules/rfc5084.py
index d541df7..99ce60d 100644
--- a/pyasn1_modules/rfc5084.py
+++ b/pyasn1_modules/rfc5084.py
@@ -1,15 +1,16 @@
-# This file is being contributed to of pyasn1-modules software.
+# This file is being contributed to pyasn1-modules software.
#
-# Created by Russ Housley with assistance from the asn1ate tool, with
-# a few comments added afterwards
-# Copyright (c) 2018, Vigil Security, LLC
+# Created by Russ Housley with assistance from the asn1ate tool, with manual
+# changes to AES_CCM_ICVlen.subtypeSpec and added comments
+#
+# Copyright (c) 2018-2019, Vigil Security, LLC
# License: http://snmplabs.com/pyasn1/license.html
#
# AES-CCM and AES-GCM Algorithms fo use with the Authenticated-Enveloped-Data
# protecting content type for the Cryptographic Message Syntax (CMS)
#
# ASN.1 source from:
-# https://www.rfc-editor.org/rfc/rfc5083.txt
+# https://www.rfc-editor.org/rfc/rfc5084.txt
from pyasn1.type import univ, char, namedtype, namedval, tag, constraint, useful
@@ -34,14 +35,14 @@ class AES_GCM_ICVlen(univ.Integer):
pass
-class CCMParameters(univ.Sequence):
- pass
+AES_CCM_ICVlen.subtypeSpec = constraint.SingleValueConstraint(4, 6, 8, 10, 12, 14, 16)
-AES_CCM_ICVlen.subtypeSpec = constraint.ValueRangeConstraint(4, 16)
+AES_GCM_ICVlen.subtypeSpec = constraint.ValueRangeConstraint(12, 16)
-AES_GCM_ICVlen.subtypeSpec = constraint.ValueRangeConstraint(12, 16)
+class CCMParameters(univ.Sequence):
+ pass
CCMParameters.componentType = namedtype.NamedTypes(
@@ -49,8 +50,6 @@ CCMParameters.componentType = namedtype.NamedTypes(
# The aes-nonce parameter contains 15-L octets, where L is the size of the length field. L=8 is RECOMMENDED.
# Within the scope of any content-authenticated-encryption key, the nonce value MUST be unique.
namedtype.DefaultedNamedType('aes-ICVlen', AES_CCM_ICVlen().subtype(value=12))
- # The programmer must make sure that the AES_CCM_ICVlen is 4, 6, 8, 10, 12, 14, or 16.
- # The contraint above will only limit it to 4..16, but will not prohibit the odd numbers.
)
@@ -60,7 +59,7 @@ class GCMParameters(univ.Sequence):
GCMParameters.componentType = namedtype.NamedTypes(
namedtype.NamedType('aes-nonce', univ.OctetString()),
- # The aes-nonce may have any number of bits between 8 and 2^64, but it must be a multiple of 8 bits.
+ # The aes-nonce may have any number of bits between 8 and 2^64, but it MUST be a multiple of 8 bits.
# Within the scope of any content-authenticated-encryption key, the nonce value MUST be unique.
# A nonce value of 12 octets can be processed more efficiently, so that length is RECOMMENDED.
namedtype.DefaultedNamedType('aes-ICVlen', AES_GCM_ICVlen().subtype(value=12))
diff --git a/pyasn1_modules/rfc5280.py b/pyasn1_modules/rfc5280.py
index df4b1fc..80bded5 100644
--- a/pyasn1_modules/rfc5280.py
+++ b/pyasn1_modules/rfc5280.py
@@ -283,7 +283,7 @@ class CertificateSerialNumber(univ.Integer):
class AlgorithmIdentifier(univ.Sequence):
componentType = namedtype.NamedTypes(
namedtype.NamedType('algorithm', univ.ObjectIdentifier()),
- namedtype.OptionalNamedType('parameters', univ.Any())
+ namedtype.OptionalNamedType('parameters', univ.Any(), openType=opentype.OpenType)
)
diff --git a/pyasn1_modules/rfc8103.py b/pyasn1_modules/rfc8103.py
new file mode 100644
index 0000000..5e2d787
--- /dev/null
+++ b/pyasn1_modules/rfc8103.py
@@ -0,0 +1,38 @@
+# This file is being contributed to pyasn1-modules software.
+#
+# Created by Russ Housley with assistance from the asn1ate tool.
+# Auto-generated by asn1ate v.0.6.0 from rfc8103.asn.
+#
+# Copyright (c) 2019, Vigil Security, LLC
+# License: http://snmplabs.com/pyasn1/license.html
+#
+# ChaCha20Poly1305 algorithm fo use with the Authenticated-Enveloped-Data
+# protecting content type for the Cryptographic Message Syntax (CMS)
+#
+# ASN.1 source from:
+# https://www.rfc-editor.org/rfc/rfc8103.txt
+
+from pyasn1.type import univ, char, namedtype, namedval, tag, constraint, useful
+
+
+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)
+
+
+class AEADChaCha20Poly1305Nonce(univ.OctetString):
+ pass
+
+
+AEADChaCha20Poly1305Nonce.subtypeSpec = constraint.ValueSizeConstraint(12, 12)
+
+
+id_alg_AEADChaCha20Poly1305 = _OID(1, 2, 840, 113549, 1, 9, 16, 3, 18)
+
+
diff --git a/pyasn1_modules/rfc8226.py b/pyasn1_modules/rfc8226.py
new file mode 100644
index 0000000..cd9bfd1
--- /dev/null
+++ b/pyasn1_modules/rfc8226.py
@@ -0,0 +1,123 @@
+# This file is being contributed to pyasn1-modules software.
+#
+# Created by Russ Housley with assistance from the asn1ate tool, with manual
+# changes to implement appropriate constraints and added comments
+#
+# Copyright (c) 2019, Vigil Security, LLC
+# License: http://snmplabs.com/pyasn1/license.html
+#
+# JWT Claim Constraints and TN Authorization List for certificate extensions.
+#
+# ASN.1 source from:
+# https://www.rfc-editor.org/rfc/rfc8226.txt (with errata corrected)
+
+from pyasn1.type import univ, char, namedtype, namedval, tag, constraint, useful
+
+
+MAX = float('inf')
+
+
+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)
+
+
+class JWTClaimName(char.IA5String):
+ pass
+
+
+class JWTClaimNames(univ.SequenceOf):
+ pass
+
+
+JWTClaimNames.componentType = JWTClaimName()
+JWTClaimNames.subtypeSpec=constraint.ValueSizeConstraint(1, MAX)
+
+
+class JWTClaimPermittedValues(univ.Sequence):
+ pass
+
+
+JWTClaimPermittedValues.componentType = namedtype.NamedTypes(
+ namedtype.NamedType('claim', JWTClaimName()),
+ namedtype.NamedType('permitted', univ.SequenceOf(componentType=char.UTF8String()).subtype(subtypeSpec=constraint.ValueSizeConstraint(1, MAX)))
+)
+
+
+class JWTClaimPermittedValuesList(univ.SequenceOf):
+ pass
+
+
+JWTClaimPermittedValuesList.componentType = JWTClaimPermittedValues()
+JWTClaimPermittedValuesList.subtypeSpec=constraint.ValueSizeConstraint(1, MAX)
+
+
+class JWTClaimConstraints(univ.Sequence):
+ pass
+
+
+JWTClaimConstraints.componentType = namedtype.NamedTypes(
+ namedtype.OptionalNamedType('mustInclude', JWTClaimNames().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))),
+ namedtype.OptionalNamedType('permittedValues', JWTClaimPermittedValuesList().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1)))
+)
+
+
+JWTClaimConstraints.sizeSpec = univ.Sequence.sizeSpec + constraint.ValueSizeConstraint(1, 2)
+
+
+id_pe_JWTClaimConstraints = _OID(1, 3, 6, 1, 5, 5, 7, 1, 27)
+
+
+class ServiceProviderCode(char.IA5String):
+ pass
+
+
+class TelephoneNumber(char.IA5String):
+ pass
+
+
+TelephoneNumber.subtypeSpec = constraint.ConstraintsIntersection(
+ constraint.ValueSizeConstraint(1, 15),
+ constraint.PermittedAlphabetConstraint('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '#', '*')
+)
+
+
+class TelephoneNumberRange(univ.Sequence):
+ pass
+
+
+TelephoneNumberRange.componentType = namedtype.NamedTypes(
+ namedtype.NamedType('start', TelephoneNumber()),
+ namedtype.NamedType('count', univ.Integer().subtype(subtypeSpec=constraint.ValueRangeConstraint(2, MAX)))
+)
+
+
+class TNEntry(univ.Choice):
+ pass
+
+
+TNEntry.componentType = namedtype.NamedTypes(
+ namedtype.NamedType('spc', ServiceProviderCode().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))),
+ namedtype.NamedType('range', TelephoneNumberRange().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 1))),
+ namedtype.NamedType('one', TelephoneNumber().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 2)))
+)
+
+
+class TNAuthorizationList(univ.SequenceOf):
+ pass
+
+
+TNAuthorizationList.componentType = TNEntry()
+TNAuthorizationList.subtypeSpec=constraint.ValueSizeConstraint(1, MAX)
+
+
+id_pe_TNAuthList = _OID(1, 3, 6, 1, 5, 5, 7, 1, 26)
+
+
+id_ad_stirTNList = _OID(1, 3, 6, 1, 5, 5, 7, 48, 14)
diff --git a/tests/test_rfc8103.py b/tests/test_rfc8103.py
new file mode 100644
index 0000000..56a96fb
--- /dev/null
+++ b/tests/test_rfc8103.py
@@ -0,0 +1,49 @@
+#
+# 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 import decoder as der_decoder
+from pyasn1.codec.der import encoder as der_encoder
+
+from pyasn1_modules import pem
+from pyasn1_modules import rfc5280
+from pyasn1_modules import rfc8103
+
+
+try:
+ import unittest2 as unittest
+except ImportError:
+ import unittest
+
+
+class CAEADChaCha20Poly1305TestCase(unittest.TestCase):
+ alg_id_pem_text = "MBsGCyqGSIb3DQEJEAMSBAzK/rq++s7brd7K+Ig="
+
+ def setUp(self):
+ self.asn1Spec = rfc5280.AlgorithmIdentifier()
+
+ def testDerCodec(self):
+ substrate = pem.readBase64fromText(self.alg_id_pem_text)
+ asn1Object, rest = der_decoder.decode(substrate, asn1Spec=self.asn1Spec)
+ assert not rest
+ assert asn1Object.prettyPrint()
+ assert asn1Object[0] == rfc8103.id_alg_AEADChaCha20Poly1305
+ param, rest = der_decoder.decode(asn1Object[1], rfc8103.AEADChaCha20Poly1305Nonce())
+ assert not rest
+ assert param.prettyPrint()
+ assert param == rfc8103.AEADChaCha20Poly1305Nonce(value='\xca\xfe\xba\xbe\xfa\xce\xdb\xad\xde\xca\xf8\x88')
+ assert der_encoder.encode(asn1Object) == substrate
+
+
+suite = unittest.TestLoader().loadTestsFromModule(sys.modules[__name__])
+
+if __name__ == '__main__':
+ unittest.TextTestRunner(verbosity=2).run(suite)
+
+
diff --git a/tests/test_rfc8226.py b/tests/test_rfc8226.py
new file mode 100644
index 0000000..a7dc036
--- /dev/null
+++ b/tests/test_rfc8226.py
@@ -0,0 +1,54 @@
+#
+# 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 import decoder as der_decoder
+from pyasn1.codec.der import encoder as der_encoder
+
+from pyasn1_modules import pem
+from pyasn1_modules import rfc8226
+
+try:
+ import unittest2 as unittest
+except ImportError:
+ import unittest
+
+
+class JWTClaimConstraintsTestCase(unittest.TestCase):
+ jwtcc_pem_text = "MD2gBzAFFgNmb2+hMjAwMBkWA2ZvbzASDARmb28xDARmb28yDARmb28zMBMWA2JhcjAMDARiYXIxDARiYXIy"
+
+ def setUp(self):
+ self.asn1Spec = rfc8226.JWTClaimConstraints()
+
+ def testDerCodec(self):
+ substrate = pem.readBase64fromText(self.jwtcc_pem_text)
+ asn1Object, rest = der_decoder.decode(substrate, asn1Spec=self.asn1Spec)
+ assert not rest
+ assert asn1Object.prettyPrint()
+ assert der_encoder.encode(asn1Object) == substrate
+
+
+class TNAuthorizationListTestCase(unittest.TestCase):
+ tnal_pem_text = "MCugBxYFYm9ndXOhEjAQFgo1NzE1NTUxMjEyAgIDFKIMFgo3MDM1NTUxMjEy"
+
+ def setUp(self):
+ self.asn1Spec = rfc8226.TNAuthorizationList()
+
+ def testDerCodec(self):
+ substrate = pem.readBase64fromText(self.tnal_pem_text)
+ asn1Object, rest = der_decoder.decode(substrate, asn1Spec=self.asn1Spec)
+ assert not rest
+ assert asn1Object.prettyPrint()
+ assert der_encoder.encode(asn1Object) == substrate
+
+
+suite = unittest.TestLoader().loadTestsFromModule(sys.modules[__name__])
+
+if __name__ == '__main__':
+ unittest.TextTestRunner(verbosity=2).run(suite)