Import asn1crypto_0.24.0.orig.tar.gz
[dgit import orig asn1crypto_0.24.0.orig.tar.gz]
This commit is contained in:
commit
f60d0400ab
|
@ -0,0 +1,19 @@
|
|||
Copyright (c) 2015-2017 Will Bond <will@wbond.net>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do
|
||||
so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -0,0 +1,3 @@
|
|||
include LICENSE
|
||||
include readme.md changelog.md
|
||||
recursive-include docs *.md
|
|
@ -0,0 +1,24 @@
|
|||
Metadata-Version: 1.1
|
||||
Name: asn1crypto
|
||||
Version: 0.24.0
|
||||
Summary: Fast ASN.1 parser and serializer with definitions for private keys, public keys, certificates, CRL, OCSP, CMS, PKCS#3, PKCS#7, PKCS#8, PKCS#12, PKCS#5, X.509 and TSP
|
||||
Home-page: https://github.com/wbond/asn1crypto
|
||||
Author: wbond
|
||||
Author-email: will@wbond.net
|
||||
License: MIT
|
||||
Description-Content-Type: UNKNOWN
|
||||
Description: Docs for this project are maintained at https://github.com/wbond/asn1crypto#readme.
|
||||
Keywords: asn1 crypto pki x509 certificate rsa dsa ec dh
|
||||
Platform: UNKNOWN
|
||||
Classifier: Development Status :: 4 - Beta
|
||||
Classifier: Intended Audience :: Developers
|
||||
Classifier: License :: OSI Approved :: MIT License
|
||||
Classifier: Programming Language :: Python :: 2.6
|
||||
Classifier: Programming Language :: Python :: 2.7
|
||||
Classifier: Programming Language :: Python :: 3.2
|
||||
Classifier: Programming Language :: Python :: 3.3
|
||||
Classifier: Programming Language :: Python :: 3.4
|
||||
Classifier: Programming Language :: Python :: 3.5
|
||||
Classifier: Programming Language :: Python :: 3.6
|
||||
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
||||
Classifier: Topic :: Security :: Cryptography
|
|
@ -0,0 +1,24 @@
|
|||
Metadata-Version: 1.1
|
||||
Name: asn1crypto
|
||||
Version: 0.24.0
|
||||
Summary: Fast ASN.1 parser and serializer with definitions for private keys, public keys, certificates, CRL, OCSP, CMS, PKCS#3, PKCS#7, PKCS#8, PKCS#12, PKCS#5, X.509 and TSP
|
||||
Home-page: https://github.com/wbond/asn1crypto
|
||||
Author: wbond
|
||||
Author-email: will@wbond.net
|
||||
License: MIT
|
||||
Description-Content-Type: UNKNOWN
|
||||
Description: Docs for this project are maintained at https://github.com/wbond/asn1crypto#readme.
|
||||
Keywords: asn1 crypto pki x509 certificate rsa dsa ec dh
|
||||
Platform: UNKNOWN
|
||||
Classifier: Development Status :: 4 - Beta
|
||||
Classifier: Intended Audience :: Developers
|
||||
Classifier: License :: OSI Approved :: MIT License
|
||||
Classifier: Programming Language :: Python :: 2.6
|
||||
Classifier: Programming Language :: Python :: 2.7
|
||||
Classifier: Programming Language :: Python :: 3.2
|
||||
Classifier: Programming Language :: Python :: 3.3
|
||||
Classifier: Programming Language :: Python :: 3.4
|
||||
Classifier: Programming Language :: Python :: 3.5
|
||||
Classifier: Programming Language :: Python :: 3.6
|
||||
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
||||
Classifier: Topic :: Security :: Cryptography
|
|
@ -0,0 +1,39 @@
|
|||
LICENSE
|
||||
MANIFEST.in
|
||||
changelog.md
|
||||
readme.md
|
||||
setup.py
|
||||
asn1crypto/__init__.py
|
||||
asn1crypto/_elliptic_curve.py
|
||||
asn1crypto/_errors.py
|
||||
asn1crypto/_ffi.py
|
||||
asn1crypto/_inet.py
|
||||
asn1crypto/_int.py
|
||||
asn1crypto/_iri.py
|
||||
asn1crypto/_ordereddict.py
|
||||
asn1crypto/_teletex_codec.py
|
||||
asn1crypto/_types.py
|
||||
asn1crypto/algos.py
|
||||
asn1crypto/cms.py
|
||||
asn1crypto/core.py
|
||||
asn1crypto/crl.py
|
||||
asn1crypto/csr.py
|
||||
asn1crypto/keys.py
|
||||
asn1crypto/ocsp.py
|
||||
asn1crypto/parser.py
|
||||
asn1crypto/pdf.py
|
||||
asn1crypto/pem.py
|
||||
asn1crypto/pkcs12.py
|
||||
asn1crypto/tsp.py
|
||||
asn1crypto/util.py
|
||||
asn1crypto/version.py
|
||||
asn1crypto/x509.py
|
||||
asn1crypto.egg-info/PKG-INFO
|
||||
asn1crypto.egg-info/SOURCES.txt
|
||||
asn1crypto.egg-info/dependency_links.txt
|
||||
asn1crypto.egg-info/top_level.txt
|
||||
asn1crypto/_perf/__init__.py
|
||||
asn1crypto/_perf/_big_num_ctypes.py
|
||||
docs/pem.md
|
||||
docs/readme.md
|
||||
docs/universal_types.md
|
|
@ -0,0 +1 @@
|
|||
|
|
@ -0,0 +1 @@
|
|||
asn1crypto
|
|
@ -0,0 +1,9 @@
|
|||
# coding: utf-8
|
||||
from __future__ import unicode_literals, division, absolute_import, print_function
|
||||
|
||||
from .version import __version__, __version_info__
|
||||
|
||||
__all__ = [
|
||||
'__version__',
|
||||
'__version_info__',
|
||||
]
|
|
@ -0,0 +1,314 @@
|
|||
# coding: utf-8
|
||||
|
||||
"""
|
||||
Classes and objects to represent prime-field elliptic curves and points on them.
|
||||
Exports the following items:
|
||||
|
||||
- PrimeCurve()
|
||||
- PrimePoint()
|
||||
- SECP192R1_CURVE
|
||||
- SECP192R1_BASE_POINT
|
||||
- SECP224R1_CURVE
|
||||
- SECP224R1_BASE_POINT
|
||||
- SECP256R1_CURVE
|
||||
- SECP256R1_BASE_POINT
|
||||
- SECP384R1_CURVE
|
||||
- SECP384R1_BASE_POINT
|
||||
- SECP521R1_CURVE
|
||||
- SECP521R1_BASE_POINT
|
||||
|
||||
The curve constants are all PrimeCurve() objects and the base point constants
|
||||
are all PrimePoint() objects.
|
||||
|
||||
Some of the following source code is derived from
|
||||
http://webpages.charter.net/curryfans/peter/downloads.html, but has been heavily
|
||||
modified to fit into this projects lint settings. The original project license
|
||||
is listed below:
|
||||
|
||||
Copyright (c) 2014 Peter Pearson
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
"""
|
||||
|
||||
from __future__ import unicode_literals, division, absolute_import, print_function
|
||||
|
||||
from ._int import inverse_mod
|
||||
|
||||
|
||||
class PrimeCurve():
|
||||
"""
|
||||
Elliptic curve over a prime field. Characteristic two field curves are not
|
||||
supported.
|
||||
"""
|
||||
|
||||
def __init__(self, p, a, b):
|
||||
"""
|
||||
The curve of points satisfying y^2 = x^3 + a*x + b (mod p)
|
||||
|
||||
:param p:
|
||||
The prime number as an integer
|
||||
|
||||
:param a:
|
||||
The component a as an integer
|
||||
|
||||
:param b:
|
||||
The component b as an integer
|
||||
"""
|
||||
|
||||
self.p = p
|
||||
self.a = a
|
||||
self.b = b
|
||||
|
||||
def contains(self, point):
|
||||
"""
|
||||
:param point:
|
||||
A Point object
|
||||
|
||||
:return:
|
||||
Boolean if the point is on this curve
|
||||
"""
|
||||
|
||||
y2 = point.y * point.y
|
||||
x3 = point.x * point.x * point.x
|
||||
return (y2 - (x3 + self.a * point.x + self.b)) % self.p == 0
|
||||
|
||||
|
||||
class PrimePoint():
|
||||
"""
|
||||
A point on a prime-field elliptic curve
|
||||
"""
|
||||
|
||||
def __init__(self, curve, x, y, order=None):
|
||||
"""
|
||||
:param curve:
|
||||
A PrimeCurve object
|
||||
|
||||
:param x:
|
||||
The x coordinate of the point as an integer
|
||||
|
||||
:param y:
|
||||
The y coordinate of the point as an integer
|
||||
|
||||
:param order:
|
||||
The order of the point, as an integer - optional
|
||||
"""
|
||||
|
||||
self.curve = curve
|
||||
self.x = x
|
||||
self.y = y
|
||||
self.order = order
|
||||
|
||||
# self.curve is allowed to be None only for INFINITY:
|
||||
if self.curve:
|
||||
if not self.curve.contains(self):
|
||||
raise ValueError('Invalid EC point')
|
||||
|
||||
if self.order:
|
||||
if self * self.order != INFINITY:
|
||||
raise ValueError('Invalid EC point')
|
||||
|
||||
def __cmp__(self, other):
|
||||
"""
|
||||
:param other:
|
||||
A PrimePoint object
|
||||
|
||||
:return:
|
||||
0 if identical, 1 otherwise
|
||||
"""
|
||||
if self.curve == other.curve and self.x == other.x and self.y == other.y:
|
||||
return 0
|
||||
else:
|
||||
return 1
|
||||
|
||||
def __add__(self, other):
|
||||
"""
|
||||
:param other:
|
||||
A PrimePoint object
|
||||
|
||||
:return:
|
||||
A PrimePoint object
|
||||
"""
|
||||
|
||||
# X9.62 B.3:
|
||||
|
||||
if other == INFINITY:
|
||||
return self
|
||||
if self == INFINITY:
|
||||
return other
|
||||
assert self.curve == other.curve
|
||||
if self.x == other.x:
|
||||
if (self.y + other.y) % self.curve.p == 0:
|
||||
return INFINITY
|
||||
else:
|
||||
return self.double()
|
||||
|
||||
p = self.curve.p
|
||||
|
||||
l_ = ((other.y - self.y) * inverse_mod(other.x - self.x, p)) % p
|
||||
|
||||
x3 = (l_ * l_ - self.x - other.x) % p
|
||||
y3 = (l_ * (self.x - x3) - self.y) % p
|
||||
|
||||
return PrimePoint(self.curve, x3, y3)
|
||||
|
||||
def __mul__(self, other):
|
||||
"""
|
||||
:param other:
|
||||
An integer to multiple the Point by
|
||||
|
||||
:return:
|
||||
A PrimePoint object
|
||||
"""
|
||||
|
||||
def leftmost_bit(x):
|
||||
assert x > 0
|
||||
result = 1
|
||||
while result <= x:
|
||||
result = 2 * result
|
||||
return result // 2
|
||||
|
||||
e = other
|
||||
if self.order:
|
||||
e = e % self.order
|
||||
if e == 0:
|
||||
return INFINITY
|
||||
if self == INFINITY:
|
||||
return INFINITY
|
||||
assert e > 0
|
||||
|
||||
# From X9.62 D.3.2:
|
||||
|
||||
e3 = 3 * e
|
||||
negative_self = PrimePoint(self.curve, self.x, -self.y, self.order)
|
||||
i = leftmost_bit(e3) // 2
|
||||
result = self
|
||||
# print "Multiplying %s by %d (e3 = %d):" % ( self, other, e3 )
|
||||
while i > 1:
|
||||
result = result.double()
|
||||
if (e3 & i) != 0 and (e & i) == 0:
|
||||
result = result + self
|
||||
if (e3 & i) == 0 and (e & i) != 0:
|
||||
result = result + negative_self
|
||||
# print ". . . i = %d, result = %s" % ( i, result )
|
||||
i = i // 2
|
||||
|
||||
return result
|
||||
|
||||
def __rmul__(self, other):
|
||||
"""
|
||||
:param other:
|
||||
An integer to multiple the Point by
|
||||
|
||||
:return:
|
||||
A PrimePoint object
|
||||
"""
|
||||
|
||||
return self * other
|
||||
|
||||
def double(self):
|
||||
"""
|
||||
:return:
|
||||
A PrimePoint object that is twice this point
|
||||
"""
|
||||
|
||||
# X9.62 B.3:
|
||||
|
||||
p = self.curve.p
|
||||
a = self.curve.a
|
||||
|
||||
l_ = ((3 * self.x * self.x + a) * inverse_mod(2 * self.y, p)) % p
|
||||
|
||||
x3 = (l_ * l_ - 2 * self.x) % p
|
||||
y3 = (l_ * (self.x - x3) - self.y) % p
|
||||
|
||||
return PrimePoint(self.curve, x3, y3)
|
||||
|
||||
|
||||
# This one point is the Point At Infinity for all purposes:
|
||||
INFINITY = PrimePoint(None, None, None)
|
||||
|
||||
|
||||
# NIST Curve P-192:
|
||||
SECP192R1_CURVE = PrimeCurve(
|
||||
6277101735386680763835789423207666416083908700390324961279,
|
||||
-3,
|
||||
0x64210519e59c80e70fa7e9ab72243049feb8deecc146b9b1
|
||||
)
|
||||
SECP192R1_BASE_POINT = PrimePoint(
|
||||
SECP192R1_CURVE,
|
||||
0x188da80eb03090f67cbf20eb43a18800f4ff0afd82ff1012,
|
||||
0x07192b95ffc8da78631011ed6b24cdd573f977a11e794811,
|
||||
6277101735386680763835789423176059013767194773182842284081
|
||||
)
|
||||
|
||||
|
||||
# NIST Curve P-224:
|
||||
SECP224R1_CURVE = PrimeCurve(
|
||||
26959946667150639794667015087019630673557916260026308143510066298881,
|
||||
-3,
|
||||
0xb4050a850c04b3abf54132565044b0b7d7bfd8ba270b39432355ffb4
|
||||
)
|
||||
SECP224R1_BASE_POINT = PrimePoint(
|
||||
SECP224R1_CURVE,
|
||||
0xb70e0cbd6bb4bf7f321390b94a03c1d356c21122343280d6115c1d21,
|
||||
0xbd376388b5f723fb4c22dfe6cd4375a05a07476444d5819985007e34,
|
||||
26959946667150639794667015087019625940457807714424391721682722368061
|
||||
)
|
||||
|
||||
|
||||
# NIST Curve P-256:
|
||||
SECP256R1_CURVE = PrimeCurve(
|
||||
115792089210356248762697446949407573530086143415290314195533631308867097853951,
|
||||
-3,
|
||||
0x5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b
|
||||
)
|
||||
SECP256R1_BASE_POINT = PrimePoint(
|
||||
SECP256R1_CURVE,
|
||||
0x6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296,
|
||||
0x4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5,
|
||||
115792089210356248762697446949407573529996955224135760342422259061068512044369
|
||||
)
|
||||
|
||||
|
||||
# NIST Curve P-384:
|
||||
SECP384R1_CURVE = PrimeCurve(
|
||||
39402006196394479212279040100143613805079739270465446667948293404245721771496870329047266088258938001861606973112319, # noqa
|
||||
-3,
|
||||
0xb3312fa7e23ee7e4988e056be3f82d19181d9c6efe8141120314088f5013875ac656398d8a2ed19d2a85c8edd3ec2aef
|
||||
)
|
||||
SECP384R1_BASE_POINT = PrimePoint(
|
||||
SECP384R1_CURVE,
|
||||
0xaa87ca22be8b05378eb1c71ef320ad746e1d3b628ba79b9859f741e082542a385502f25dbf55296c3a545e3872760ab7,
|
||||
0x3617de4a96262c6f5d9e98bf9292dc29f8f41dbd289a147ce9da3113b5f0b8c00a60b1ce1d7e819d7a431d7c90ea0e5f,
|
||||
39402006196394479212279040100143613805079739270465446667946905279627659399113263569398956308152294913554433653942643
|
||||
)
|
||||
|
||||
|
||||
# NIST Curve P-521:
|
||||
SECP521R1_CURVE = PrimeCurve(
|
||||
6864797660130609714981900799081393217269435300143305409394463459185543183397656052122559640661454554977296311391480858037121987999716643812574028291115057151, # noqa
|
||||
-3,
|
||||
0x051953eb9618e1c9a1f929a21a0b68540eea2da725b99b315f3b8b489918ef109e156193951ec7e937b1652c0bd3bb1bf073573df883d2c34f1ef451fd46b503f00 # noqa
|
||||
)
|
||||
SECP521R1_BASE_POINT = PrimePoint(
|
||||
SECP521R1_CURVE,
|
||||
0xc6858e06b70404e9cd9e3ecb662395b4429c648139053fb521f828af606b4d3dbaa14b5e77efe75928fe1dc127a2ffa8de3348b3c1856a429bf97e7e31c2e5bd66, # noqa
|
||||
0x11839296a789a3bc0045c8a5fb42c7d1bd998f54449579b446817afbd17273e662c97ee72995ef42640c550b9013fad0761353c7086a272c24088be94769fd16650, # noqa
|
||||
6864797660130609714981900799081393217269435300143305409394463459185543183397655394245057746333217197532963996371363321113864768612440380340372808892707005449 # noqa
|
||||
)
|
|
@ -0,0 +1,45 @@
|
|||
# coding: utf-8
|
||||
|
||||
"""
|
||||
Helper for formatting exception messages. Exports the following items:
|
||||
|
||||
- unwrap()
|
||||
"""
|
||||
|
||||
from __future__ import unicode_literals, division, absolute_import, print_function
|
||||
|
||||
import re
|
||||
import textwrap
|
||||
|
||||
|
||||
def unwrap(string, *params):
|
||||
"""
|
||||
Takes a multi-line string and does the following:
|
||||
|
||||
- dedents
|
||||
- converts newlines with text before and after into a single line
|
||||
- strips leading and trailing whitespace
|
||||
|
||||
:param string:
|
||||
The string to format
|
||||
|
||||
:param *params:
|
||||
Params to interpolate into the string
|
||||
|
||||
:return:
|
||||
The formatted string
|
||||
"""
|
||||
|
||||
output = textwrap.dedent(string)
|
||||
|
||||
# Unwrap lines, taking into account bulleted lists, ordered lists and
|
||||
# underlines consisting of = signs
|
||||
if output.find('\n') != -1:
|
||||
output = re.sub('(?<=\\S)\n(?=[^ \n\t\\d\\*\\-=])', ' ', output)
|
||||
|
||||
if params:
|
||||
output = output % params
|
||||
|
||||
output = output.strip()
|
||||
|
||||
return output
|
|
@ -0,0 +1,45 @@
|
|||
# coding: utf-8
|
||||
|
||||
"""
|
||||
FFI helper compatibility functions. Exports the following items:
|
||||
|
||||
- LibraryNotFoundError
|
||||
- FFIEngineError
|
||||
- bytes_from_buffer()
|
||||
- buffer_from_bytes()
|
||||
- null()
|
||||
"""
|
||||
|
||||
from __future__ import unicode_literals, division, absolute_import, print_function
|
||||
|
||||
from ctypes import create_string_buffer
|
||||
|
||||
|
||||
def buffer_from_bytes(initializer):
|
||||
return create_string_buffer(initializer)
|
||||
|
||||
|
||||
def bytes_from_buffer(buffer, maxlen=None):
|
||||
return buffer.raw
|
||||
|
||||
|
||||
def null():
|
||||
return None
|
||||
|
||||
|
||||
class LibraryNotFoundError(Exception):
|
||||
|
||||
"""
|
||||
An exception when trying to find a shared library
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class FFIEngineError(Exception):
|
||||
|
||||
"""
|
||||
An exception when trying to instantiate ctypes or cffi
|
||||
"""
|
||||
|
||||
pass
|
|
@ -0,0 +1,170 @@
|
|||
# coding: utf-8
|
||||
from __future__ import unicode_literals, division, absolute_import, print_function
|
||||
|
||||
import socket
|
||||
import struct
|
||||
|
||||
from ._errors import unwrap
|
||||
from ._types import byte_cls, bytes_to_list, str_cls, type_name
|
||||
|
||||
|
||||
def inet_ntop(address_family, packed_ip):
|
||||
"""
|
||||
Windows compatibility shim for socket.inet_ntop().
|
||||
|
||||
:param address_family:
|
||||
socket.AF_INET for IPv4 or socket.AF_INET6 for IPv6
|
||||
|
||||
:param packed_ip:
|
||||
A byte string of the network form of an IP address
|
||||
|
||||
:return:
|
||||
A unicode string of the IP address
|
||||
"""
|
||||
|
||||
if address_family not in set([socket.AF_INET, socket.AF_INET6]):
|
||||
raise ValueError(unwrap(
|
||||
'''
|
||||
address_family must be socket.AF_INET (%s) or socket.AF_INET6 (%s),
|
||||
not %s
|
||||
''',
|
||||
repr(socket.AF_INET),
|
||||
repr(socket.AF_INET6),
|
||||
repr(address_family)
|
||||
))
|
||||
|
||||
if not isinstance(packed_ip, byte_cls):
|
||||
raise TypeError(unwrap(
|
||||
'''
|
||||
packed_ip must be a byte string, not %s
|
||||
''',
|
||||
type_name(packed_ip)
|
||||
))
|
||||
|
||||
required_len = 4 if address_family == socket.AF_INET else 16
|
||||
if len(packed_ip) != required_len:
|
||||
raise ValueError(unwrap(
|
||||
'''
|
||||
packed_ip must be %d bytes long - is %d
|
||||
''',
|
||||
required_len,
|
||||
len(packed_ip)
|
||||
))
|
||||
|
||||
if address_family == socket.AF_INET:
|
||||
return '%d.%d.%d.%d' % tuple(bytes_to_list(packed_ip))
|
||||
|
||||
octets = struct.unpack(b'!HHHHHHHH', packed_ip)
|
||||
|
||||
runs_of_zero = {}
|
||||
longest_run = 0
|
||||
zero_index = None
|
||||
for i, octet in enumerate(octets + (-1,)):
|
||||
if octet != 0:
|
||||
if zero_index is not None:
|
||||
length = i - zero_index
|
||||
if length not in runs_of_zero:
|
||||
runs_of_zero[length] = zero_index
|
||||
longest_run = max(longest_run, length)
|
||||
zero_index = None
|
||||
elif zero_index is None:
|
||||
zero_index = i
|
||||
|
||||
hexed = [hex(o)[2:] for o in octets]
|
||||
|
||||
if longest_run < 2:
|
||||
return ':'.join(hexed)
|
||||
|
||||
zero_start = runs_of_zero[longest_run]
|
||||
zero_end = zero_start + longest_run
|
||||
|
||||
return ':'.join(hexed[:zero_start]) + '::' + ':'.join(hexed[zero_end:])
|
||||
|
||||
|
||||
def inet_pton(address_family, ip_string):
|
||||
"""
|
||||
Windows compatibility shim for socket.inet_ntop().
|
||||
|
||||
:param address_family:
|
||||
socket.AF_INET for IPv4 or socket.AF_INET6 for IPv6
|
||||
|
||||
:param ip_string:
|
||||
A unicode string of an IP address
|
||||
|
||||
:return:
|
||||
A byte string of the network form of the IP address
|
||||
"""
|
||||
|
||||
if address_family not in set([socket.AF_INET, socket.AF_INET6]):
|
||||
raise ValueError(unwrap(
|
||||
'''
|
||||
address_family must be socket.AF_INET (%s) or socket.AF_INET6 (%s),
|
||||
not %s
|
||||
''',
|
||||
repr(socket.AF_INET),
|
||||
repr(socket.AF_INET6),
|
||||
repr(address_family)
|
||||
))
|
||||
|
||||
if not isinstance(ip_string, str_cls):
|
||||
raise TypeError(unwrap(
|
||||
'''
|
||||
ip_string must be a unicode string, not %s
|
||||
''',
|
||||
type_name(ip_string)
|
||||
))
|
||||
|
||||
if address_family == socket.AF_INET:
|
||||
octets = ip_string.split('.')
|
||||
error = len(octets) != 4
|
||||
if not error:
|
||||
ints = []
|
||||
for o in octets:
|
||||
o = int(o)
|
||||
if o > 255 or o < 0:
|
||||
error = True
|
||||
break
|
||||
ints.append(o)
|
||||
|
||||
if error:
|
||||
raise ValueError(unwrap(
|
||||
'''
|
||||
ip_string must be a dotted string with four integers in the
|
||||
range of 0 to 255, got %s
|
||||
''',
|
||||
repr(ip_string)
|
||||
))
|
||||
|
||||
return struct.pack(b'!BBBB', *ints)
|
||||
|
||||
error = False
|
||||
omitted = ip_string.count('::')
|
||||
if omitted > 1:
|
||||
error = True
|
||||
elif omitted == 0:
|
||||
octets = ip_string.split(':')
|
||||
error = len(octets) != 8
|
||||
else:
|
||||
begin, end = ip_string.split('::')
|
||||
begin_octets = begin.split(':')
|
||||
end_octets = end.split(':')
|
||||
missing = 8 - len(begin_octets) - len(end_octets)
|
||||
octets = begin_octets + (['0'] * missing) + end_octets
|
||||
|
||||
if not error:
|
||||
ints = []
|
||||
for o in octets:
|
||||
o = int(o, 16)
|
||||
if o > 65535 or o < 0:
|
||||
error = True
|
||||
break
|
||||
ints.append(o)
|
||||
|
||||
return struct.pack(b'!HHHHHHHH', *ints)
|
||||
|
||||
raise ValueError(unwrap(
|
||||
'''
|
||||
ip_string must be a valid ipv6 string, got %s
|
||||
''',
|
||||
repr(ip_string)
|
||||
))
|
|
@ -0,0 +1,159 @@
|
|||
# coding: utf-8
|
||||
|
||||
"""
|
||||
Function for calculating the modular inverse. Exports the following items:
|
||||
|
||||
- inverse_mod()
|
||||
|
||||
Source code is derived from
|
||||
http://webpages.charter.net/curryfans/peter/downloads.html, but has been heavily
|
||||
modified to fit into this projects lint settings. The original project license
|
||||
is listed below:
|
||||
|
||||
Copyright (c) 2014 Peter Pearson
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
"""
|
||||
|
||||
from __future__ import unicode_literals, division, absolute_import, print_function
|
||||
|
||||
import math
|
||||
import platform
|
||||
|
||||
from .util import int_to_bytes, int_from_bytes
|
||||
|
||||
# First try to use ctypes with OpenSSL for better performance
|
||||
try:
|
||||
from ._ffi import (
|
||||
buffer_from_bytes,
|
||||
bytes_from_buffer,
|
||||
FFIEngineError,
|
||||
LibraryNotFoundError,
|
||||
null,
|
||||
)
|
||||
|
||||
# Some versions of PyPy have segfault issues, so we just punt on PyPy
|
||||
if platform.python_implementation() == 'PyPy':
|
||||
raise EnvironmentError()
|
||||
|
||||
try:
|
||||
from ._perf._big_num_ctypes import libcrypto
|
||||
|
||||
def inverse_mod(a, p):
|
||||
"""
|
||||
Compute the modular inverse of a (mod p)
|
||||
|
||||
:param a:
|
||||
An integer
|
||||
|
||||
:param p:
|
||||
An integer
|
||||
|
||||
:return:
|
||||
An integer
|
||||
"""
|
||||
|
||||
ctx = libcrypto.BN_CTX_new()
|
||||
|
||||
a_bytes = int_to_bytes(abs(a))
|
||||
p_bytes = int_to_bytes(abs(p))
|
||||
|
||||
a_buf = buffer_from_bytes(a_bytes)
|
||||
a_bn = libcrypto.BN_bin2bn(a_buf, len(a_bytes), null())
|
||||
if a < 0:
|
||||
libcrypto.BN_set_negative(a_bn, 1)
|
||||
|
||||
p_buf = buffer_from_bytes(p_bytes)
|
||||
p_bn = libcrypto.BN_bin2bn(p_buf, len(p_bytes), null())
|
||||
if p < 0:
|
||||
libcrypto.BN_set_negative(p_bn, 1)
|
||||
|
||||
r_bn = libcrypto.BN_mod_inverse(null(), a_bn, p_bn, ctx)
|
||||
r_len_bits = libcrypto.BN_num_bits(r_bn)
|
||||
r_len = int(math.ceil(r_len_bits / 8))
|
||||
r_buf = buffer_from_bytes(r_len)
|
||||
libcrypto.BN_bn2bin(r_bn, r_buf)
|
||||
r_bytes = bytes_from_buffer(r_buf, r_len)
|
||||
result = int_from_bytes(r_bytes)
|
||||
|
||||
libcrypto.BN_free(a_bn)
|
||||
libcrypto.BN_free(p_bn)
|
||||
libcrypto.BN_free(r_bn)
|
||||
libcrypto.BN_CTX_free(ctx)
|
||||
|
||||
return result
|
||||
except (LibraryNotFoundError, FFIEngineError):
|
||||
raise EnvironmentError()
|
||||
|
||||
# If there was an issue using ctypes or OpenSSL, we fall back to pure python
|
||||
except (EnvironmentError, ImportError):
|
||||
|
||||
def inverse_mod(a, p):
|
||||
"""
|
||||
Compute the modular inverse of a (mod p)
|
||||
|
||||
:param a:
|
||||
An integer
|
||||
|
||||
:param p:
|
||||
An integer
|
||||
|
||||
:return:
|
||||
An integer
|
||||
"""
|
||||
|
||||
if a < 0 or p <= a:
|
||||
a = a % p
|
||||
|
||||
# From Ferguson and Schneier, roughly:
|
||||
|
||||
c, d = a, p
|
||||
uc, vc, ud, vd = 1, 0, 0, 1
|
||||
while c != 0:
|
||||
q, c, d = divmod(d, c) + (c,)
|
||||
uc, vc, ud, vd = ud - q * uc, vd - q * vc, uc, vc
|
||||
|
||||
# At this point, d is the GCD, and ud*a+vd*p = d.
|
||||
# If d == 1, this means that ud is a inverse.
|
||||
|
||||
assert d == 1
|
||||
if ud > 0:
|
||||
return ud
|
||||
else:
|
||||
return ud + p
|
||||
|
||||
|
||||
def fill_width(bytes_, width):
|
||||
"""
|
||||
Ensure a byte string representing a positive integer is a specific width
|
||||
(in bytes)
|
||||
|
||||
:param bytes_:
|
||||
The integer byte string
|
||||
|
||||
:param width:
|
||||
The desired width as an integer
|
||||
|
||||
:return:
|
||||
A byte string of the width specified
|
||||
"""
|
||||
|
||||
while len(bytes_) < width:
|
||||
bytes_ = b'\x00' + bytes_
|
||||
return bytes_
|
|
@ -0,0 +1,288 @@
|
|||
# coding: utf-8
|
||||
|
||||
"""
|
||||
Functions to convert unicode IRIs into ASCII byte string URIs and back. Exports
|
||||
the following items:
|
||||
|
||||
- iri_to_uri()
|
||||
- uri_to_iri()
|
||||
"""
|
||||
|
||||
from __future__ import unicode_literals, division, absolute_import, print_function
|
||||
|
||||
from encodings import idna # noqa
|
||||
import codecs
|
||||
import re
|
||||
import sys
|
||||
|
||||
from ._errors import unwrap
|
||||
from ._types import byte_cls, str_cls, type_name, bytes_to_list, int_types
|
||||
|
||||
if sys.version_info < (3,):
|
||||
from urlparse import urlsplit, urlunsplit
|
||||
from urllib import (
|
||||
quote as urlquote,
|
||||
unquote as unquote_to_bytes,
|
||||
)
|
||||
|
||||
else:
|
||||
from urllib.parse import (
|
||||
quote as urlquote,
|
||||
unquote_to_bytes,
|
||||
urlsplit,
|
||||
urlunsplit,
|
||||
)
|
||||
|
||||
|
||||
def iri_to_uri(value):
|
||||
"""
|
||||
Normalizes and encodes a unicode IRI into an ASCII byte string URI
|
||||
|
||||
:param value:
|
||||
A unicode string of an IRI
|
||||
|
||||
:return:
|
||||
A byte string of the ASCII-encoded URI
|
||||
"""
|
||||
|
||||
if not isinstance(value, str_cls):
|
||||
raise TypeError(unwrap(
|
||||
'''
|
||||
value must be a unicode string, not %s
|
||||
''',
|
||||
type_name(value)
|
||||
))
|
||||
|
||||
scheme = None
|
||||
# Python 2.6 doesn't split properly is the URL doesn't start with http:// or https://
|
||||
if sys.version_info < (2, 7) and not value.startswith('http://') and not value.startswith('https://'):
|
||||
real_prefix = None
|
||||
prefix_match = re.match('^[^:]*://', value)
|
||||
if prefix_match:
|
||||
real_prefix = prefix_match.group(0)
|
||||
value = 'http://' + value[len(real_prefix):]
|
||||
parsed = urlsplit(value)
|
||||
if real_prefix:
|
||||
value = real_prefix + value[7:]
|
||||
scheme = _urlquote(real_prefix[:-3])
|
||||
else:
|
||||
parsed = urlsplit(value)
|
||||
|
||||
if scheme is None:
|
||||
scheme = _urlquote(parsed.scheme)
|
||||
hostname = parsed.hostname
|
||||
if hostname is not None:
|
||||
hostname = hostname.encode('idna')
|
||||
# RFC 3986 allows userinfo to contain sub-delims
|
||||
username = _urlquote(parsed.username, safe='!$&\'()*+,;=')
|
||||
password = _urlquote(parsed.password, safe='!$&\'()*+,;=')
|
||||
port = parsed.port
|
||||
if port is not None:
|
||||
port = str_cls(port).encode('ascii')
|
||||
|
||||
netloc = b''
|
||||
if username is not None:
|
||||
netloc += username
|
||||
if password:
|
||||
netloc += b':' + password
|
||||
netloc += b'@'
|
||||
if hostname is not None:
|
||||
netloc += hostname
|
||||
if port is not None:
|
||||
default_http = scheme == b'http' and port == b'80'
|
||||
default_https = scheme == b'https' and port == b'443'
|
||||
if not default_http and not default_https:
|
||||
netloc += b':' + port
|
||||
|
||||
# RFC 3986 allows a path to contain sub-delims, plus "@" and ":"
|
||||
path = _urlquote(parsed.path, safe='/!$&\'()*+,;=@:')
|
||||
# RFC 3986 allows the query to contain sub-delims, plus "@", ":" , "/" and "?"
|
||||
query = _urlquote(parsed.query, safe='/?!$&\'()*+,;=@:')
|
||||
# RFC 3986 allows the fragment to contain sub-delims, plus "@", ":" , "/" and "?"
|
||||
fragment = _urlquote(parsed.fragment, safe='/?!$&\'()*+,;=@:')
|
||||
|
||||
if query is None and fragment is None and path == b'/':
|
||||
path = None
|
||||
|
||||
# Python 2.7 compat
|
||||
if path is None:
|
||||
path = ''
|
||||
|
||||
output = urlunsplit((scheme, netloc, path, query, fragment))
|
||||
if isinstance(output, str_cls):
|
||||
output = output.encode('latin1')
|
||||
return output
|
||||
|
||||
|
||||
def uri_to_iri(value):
|
||||
"""
|
||||
Converts an ASCII URI byte string into a unicode IRI
|
||||
|
||||
:param value:
|
||||
An ASCII-encoded byte string of the URI
|
||||
|
||||
:return:
|
||||
A unicode string of the IRI
|
||||
"""
|
||||
|
||||
if not isinstance(value, byte_cls):
|
||||
raise TypeError(unwrap(
|
||||
'''
|
||||
value must be a byte string, not %s
|
||||
''',
|
||||
type_name(value)
|
||||
))
|
||||
|
||||
parsed = urlsplit(value)
|
||||
|
||||
scheme = parsed.scheme
|
||||
if scheme is not None:
|
||||
scheme = scheme.decode('ascii')
|
||||
|
||||
username = _urlunquote(parsed.username, remap=[':', '@'])
|
||||
password = _urlunquote(parsed.password, remap=[':', '@'])
|
||||
hostname = parsed.hostname
|
||||
if hostname:
|
||||
hostname = hostname.decode('idna')
|
||||
port = parsed.port
|
||||
if port and not isinstance(port, int_types):
|
||||
port = port.decode('ascii')
|
||||
|
||||
netloc = ''
|
||||
if username is not None:
|
||||
netloc += username
|
||||
if password:
|
||||
netloc += ':' + password
|
||||
netloc += '@'
|
||||
if hostname is not None:
|
||||
netloc += hostname
|
||||
if port is not None:
|
||||
netloc += ':' + str_cls(port)
|
||||
|
||||
path = _urlunquote(parsed.path, remap=['/'], preserve=True)
|
||||
query = _urlunquote(parsed.query, remap=['&', '='], preserve=True)
|
||||
fragment = _urlunquote(parsed.fragment)
|
||||
|
||||
return urlunsplit((scheme, netloc, path, query, fragment))
|
||||
|
||||
|
||||
def _iri_utf8_errors_handler(exc):
|
||||
"""
|
||||
Error handler for decoding UTF-8 parts of a URI into an IRI. Leaves byte
|
||||
sequences encoded in %XX format, but as part of a unicode string.
|
||||
|
||||
:param exc:
|
||||
The UnicodeDecodeError exception
|
||||
|
||||
:return:
|
||||
A 2-element tuple of (replacement unicode string, integer index to
|
||||
resume at)
|
||||
"""
|
||||
|
||||
bytes_as_ints = bytes_to_list(exc.object[exc.start:exc.end])
|
||||
replacements = ['%%%02x' % num for num in bytes_as_ints]
|
||||
return (''.join(replacements), exc.end)
|
||||
|
||||
|
||||
codecs.register_error('iriutf8', _iri_utf8_errors_handler)
|
||||
|
||||
|
||||
def _urlquote(string, safe=''):
|
||||
"""
|
||||
Quotes a unicode string for use in a URL
|
||||
|
||||
:param string:
|
||||
A unicode string
|
||||
|
||||
:param safe:
|
||||
A unicode string of character to not encode
|
||||
|
||||
:return:
|
||||
None (if string is None) or an ASCII byte string of the quoted string
|
||||
"""
|
||||
|
||||
if string is None or string == '':
|
||||
return None
|
||||
|
||||
# Anything already hex quoted is pulled out of the URL and unquoted if
|
||||
# possible
|
||||
escapes = []
|
||||
if re.search('%[0-9a-fA-F]{2}', string):
|
||||
# Try to unquote any percent values, restoring them if they are not
|
||||
# valid UTF-8. Also, requote any safe chars since encoded versions of
|
||||
# those are functionally different than the unquoted ones.
|
||||
def _try_unescape(match):
|
||||
byte_string = unquote_to_bytes(match.group(0))
|
||||
unicode_string = byte_string.decode('utf-8', 'iriutf8')
|
||||
for safe_char in list(safe):
|
||||
unicode_string = unicode_string.replace(safe_char, '%%%02x' % ord(safe_char))
|
||||
return unicode_string
|
||||
string = re.sub('(?:%[0-9a-fA-F]{2})+', _try_unescape, string)
|
||||
|
||||
# Once we have the minimal set of hex quoted values, removed them from
|
||||
# the string so that they are not double quoted
|
||||
def _extract_escape(match):
|
||||
escapes.append(match.group(0).encode('ascii'))
|
||||
return '\x00'
|
||||
string = re.sub('%[0-9a-fA-F]{2}', _extract_escape, string)
|
||||
|
||||
output = urlquote(string.encode('utf-8'), safe=safe.encode('utf-8'))
|
||||
if not isinstance(output, byte_cls):
|
||||
output = output.encode('ascii')
|
||||
|
||||
# Restore the existing quoted values that we extracted
|
||||
if len(escapes) > 0:
|
||||
def _return_escape(_):
|
||||
return escapes.pop(0)
|
||||
output = re.sub(b'%00', _return_escape, output)
|
||||
|
||||
return output
|
||||
|
||||
|
||||
def _urlunquote(byte_string, remap=None, preserve=None):
|
||||
"""
|
||||
Unquotes a URI portion from a byte string into unicode using UTF-8
|
||||
|
||||
:param byte_string:
|
||||
A byte string of the data to unquote
|
||||
|
||||
:param remap:
|
||||
A list of characters (as unicode) that should be re-mapped to a
|
||||
%XX encoding. This is used when characters are not valid in part of a
|
||||
URL.
|
||||
|
||||
:param preserve:
|
||||
A bool - indicates that the chars to be remapped if they occur in
|
||||
non-hex form, should be preserved. E.g. / for URL path.
|
||||
|
||||
:return:
|
||||
A unicode string
|
||||
"""
|
||||
|
||||
if byte_string is None:
|
||||
return byte_string
|
||||
|
||||
if byte_string == b'':
|
||||
return ''
|
||||
|
||||
if preserve:
|
||||
replacements = ['\x1A', '\x1C', '\x1D', '\x1E', '\x1F']
|
||||
preserve_unmap = {}
|
||||
for char in remap:
|
||||
replacement = replacements.pop(0)
|
||||
preserve_unmap[replacement] = char
|
||||
byte_string = byte_string.replace(char.encode('ascii'), replacement.encode('ascii'))
|
||||
|
||||
byte_string = unquote_to_bytes(byte_string)
|
||||
|
||||
if remap:
|
||||
for char in remap:
|
||||
byte_string = byte_string.replace(char.encode('ascii'), ('%%%02x' % ord(char)).encode('ascii'))
|
||||
|
||||
output = byte_string.decode('utf-8', 'iriutf8')
|
||||
|
||||
if preserve:
|
||||
for replacement, original in preserve_unmap.items():
|
||||
output = output.replace(replacement, original)
|
||||
|
||||
return output
|
|
@ -0,0 +1,135 @@
|
|||
# Copyright (c) 2009 Raymond Hettinger
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person
|
||||
# obtaining a copy of this software and associated documentation files
|
||||
# (the "Software"), to deal in the Software without restriction,
|
||||
# including without limitation the rights to use, copy, modify, merge,
|
||||
# publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
# and to permit persons to whom the Software is furnished to do so,
|
||||
# subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
# OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
import sys
|
||||
|
||||
if not sys.version_info < (2, 7):
|
||||
|
||||
from collections import OrderedDict
|
||||
|
||||
else:
|
||||
|
||||
from UserDict import DictMixin
|
||||
|
||||
class OrderedDict(dict, DictMixin):
|
||||
|
||||
def __init__(self, *args, **kwds):
|
||||
if len(args) > 1:
|
||||
raise TypeError('expected at most 1 arguments, got %d' % len(args))
|
||||
try:
|
||||
self.__end
|
||||
except AttributeError:
|
||||
self.clear()
|
||||
self.update(*args, **kwds)
|
||||
|
||||
def clear(self):
|
||||
self.__end = end = []
|
||||
end += [None, end, end] # sentinel node for doubly linked list
|
||||
self.__map = {} # key --> [key, prev, next]
|
||||
dict.clear(self)
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
if key not in self:
|
||||
end = self.__end
|
||||
curr = end[1]
|
||||
curr[2] = end[1] = self.__map[key] = [key, curr, end]
|
||||
dict.__setitem__(self, key, value)
|
||||
|
||||
def __delitem__(self, key):
|
||||
dict.__delitem__(self, key)
|
||||
key, prev, next_ = self.__map.pop(key)
|
||||
prev[2] = next_
|
||||
next_[1] = prev
|
||||
|
||||
def __iter__(self):
|
||||
end = self.__end
|
||||
curr = end[2]
|
||||
while curr is not end:
|
||||
yield curr[0]
|
||||
curr = curr[2]
|
||||
|
||||
def __reversed__(self):
|
||||
end = self.__end
|
||||
curr = end[1]
|
||||
while curr is not end:
|
||||
yield curr[0]
|
||||
curr = curr[1]
|
||||
|
||||
def popitem(self, last=True):
|
||||
if not self:
|
||||
raise KeyError('dictionary is empty')
|
||||
if last:
|
||||
key = reversed(self).next()
|
||||
else:
|
||||
key = iter(self).next()
|
||||
value = self.pop(key)
|
||||
return key, value
|
||||
|
||||
def __reduce__(self):
|
||||
items = [[k, self[k]] for k in self]
|
||||
tmp = self.__map, self.__end
|
||||
del self.__map, self.__end
|
||||
inst_dict = vars(self).copy()
|
||||
self.__map, self.__end = tmp
|
||||
if inst_dict:
|
||||
return (self.__class__, (items,), inst_dict)
|
||||
return self.__class__, (items,)
|
||||
|
||||
def keys(self):
|
||||
return list(self)
|
||||
|
||||
setdefault = DictMixin.setdefault
|
||||
update = DictMixin.update
|
||||
pop = DictMixin.pop
|
||||
values = DictMixin.values
|
||||
items = DictMixin.items
|
||||
iterkeys = DictMixin.iterkeys
|
||||
itervalues = DictMixin.itervalues
|
||||
iteritems = DictMixin.iteritems
|
||||
|
||||
def __repr__(self):
|
||||
if not self:
|
||||
return '%s()' % (self.__class__.__name__,)
|
||||
return '%s(%r)' % (self.__class__.__name__, self.items())
|
||||
|
||||
def copy(self):
|
||||
return self.__class__(self)
|
||||
|
||||
@classmethod
|
||||
def fromkeys(cls, iterable, value=None):
|
||||
d = cls()
|
||||
for key in iterable:
|
||||
d[key] = value
|
||||
return d
|
||||
|
||||
def __eq__(self, other):
|
||||
if isinstance(other, OrderedDict):
|
||||
if len(self) != len(other):
|
||||
return False
|
||||
for p, q in zip(self.items(), other.items()):
|
||||
if p != q:
|
||||
return False
|
||||
return True
|
||||
return dict.__eq__(self, other)
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self == other
|
|
@ -0,0 +1,69 @@
|
|||
# coding: utf-8
|
||||
|
||||
"""
|
||||
ctypes interface for BN_mod_inverse() function from OpenSSL. Exports the
|
||||
following items:
|
||||
|
||||
- libcrypto
|
||||
- BN_bn2bin()
|
||||
- BN_CTX_free()
|
||||
- BN_CTX_new()
|
||||
- BN_free()
|
||||
- BN_mod_inverse()
|
||||
- BN_new()
|
||||
- BN_num_bits()
|
||||
- BN_set_negative()
|
||||
|
||||
Will raise asn1crypto._ffi.LibraryNotFoundError() if libcrypto can not be
|
||||
found. Will raise asn1crypto._ffi.FFIEngineError() if there is an error
|
||||
interfacing with libcrypto.
|
||||
"""
|
||||
|
||||
from __future__ import unicode_literals, division, absolute_import, print_function
|
||||
|
||||
import sys
|
||||
|
||||
from ctypes import CDLL, c_int, c_char_p, c_void_p
|
||||
from ctypes.util import find_library
|
||||
|
||||
from .._ffi import LibraryNotFoundError, FFIEngineError
|
||||
|
||||
|
||||
try:
|
||||
# On Python 2, the unicode string here may raise a UnicodeDecodeError as it
|
||||
# tries to join a bytestring path to the unicode name "crypto"
|
||||
libcrypto_path = find_library(b'crypto' if sys.version_info < (3,) else 'crypto')
|
||||
if not libcrypto_path:
|
||||
raise LibraryNotFoundError('The library libcrypto could not be found')
|
||||
|
||||
libcrypto = CDLL(libcrypto_path)
|
||||
|
||||
libcrypto.BN_new.argtypes = []
|
||||
libcrypto.BN_new.restype = c_void_p
|
||||
|
||||
libcrypto.BN_bin2bn.argtypes = [c_char_p, c_int, c_void_p]
|
||||
libcrypto.BN_bin2bn.restype = c_void_p
|
||||
|
||||
libcrypto.BN_bn2bin.argtypes = [c_void_p, c_char_p]
|
||||
libcrypto.BN_bn2bin.restype = c_int
|
||||
|
||||
libcrypto.BN_set_negative.argtypes = [c_void_p, c_int]
|
||||
libcrypto.BN_set_negative.restype = None
|
||||
|
||||
libcrypto.BN_num_bits.argtypes = [c_void_p]
|
||||
libcrypto.BN_num_bits.restype = c_int
|
||||
|
||||
libcrypto.BN_free.argtypes = [c_void_p]
|
||||
libcrypto.BN_free.restype = None
|
||||
|
||||
libcrypto.BN_CTX_new.argtypes = []
|
||||
libcrypto.BN_CTX_new.restype = c_void_p
|
||||
|
||||
libcrypto.BN_CTX_free.argtypes = [c_void_p]
|
||||
libcrypto.BN_CTX_free.restype = None
|
||||
|
||||
libcrypto.BN_mod_inverse.argtypes = [c_void_p, c_void_p, c_void_p, c_void_p]
|
||||
libcrypto.BN_mod_inverse.restype = c_void_p
|
||||
|
||||
except (AttributeError):
|
||||
raise FFIEngineError('Error initializing ctypes')
|
|
@ -0,0 +1,331 @@
|
|||
# coding: utf-8
|
||||
|
||||
"""
|
||||
Implementation of the teletex T.61 codec. Exports the following items:
|
||||
|
||||
- register()
|
||||
"""
|
||||
|
||||
from __future__ import unicode_literals, division, absolute_import, print_function
|
||||
|
||||
import codecs
|
||||
|
||||
|
||||
class TeletexCodec(codecs.Codec):
|
||||
|
||||
def encode(self, input_, errors='strict'):
|
||||
return codecs.charmap_encode(input_, errors, ENCODING_TABLE)
|
||||
|
||||
def decode(self, input_, errors='strict'):
|
||||
return codecs.charmap_decode(input_, errors, DECODING_TABLE)
|
||||
|
||||
|
||||
class TeletexIncrementalEncoder(codecs.IncrementalEncoder):
|
||||
|
||||
def encode(self, input_, final=False):
|
||||
return codecs.charmap_encode(input_, self.errors, ENCODING_TABLE)[0]
|
||||
|
||||
|
||||
class TeletexIncrementalDecoder(codecs.IncrementalDecoder):
|
||||
|
||||
def decode(self, input_, final=False):
|
||||
return codecs.charmap_decode(input_, self.errors, DECODING_TABLE)[0]
|
||||
|
||||
|
||||
class TeletexStreamWriter(TeletexCodec, codecs.StreamWriter):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class TeletexStreamReader(TeletexCodec, codecs.StreamReader):
|
||||
|
||||
pass
|
||||
|
||||
|
||||
def teletex_search_function(name):
|
||||
"""
|
||||
Search function for teletex codec that is passed to codecs.register()
|
||||
"""
|
||||
|
||||
if name != 'teletex':
|
||||
return None
|
||||
|
||||
return codecs.CodecInfo(
|
||||
name='teletex',
|
||||
encode=TeletexCodec().encode,
|
||||
decode=TeletexCodec().decode,
|
||||
incrementalencoder=TeletexIncrementalEncoder,
|
||||
incrementaldecoder=TeletexIncrementalDecoder,
|
||||
streamreader=TeletexStreamReader,
|
||||
streamwriter=TeletexStreamWriter,
|
||||
)
|
||||
|
||||
|
||||
def register():
|
||||
"""
|
||||
Registers the teletex codec
|
||||
"""
|
||||
|
||||
codecs.register(teletex_search_function)
|
||||
|
||||
|
||||
# http://en.wikipedia.org/wiki/ITU_T.61
|
||||
DECODING_TABLE = (
|
||||
'\u0000'
|
||||
'\u0001'
|
||||
'\u0002'
|
||||
'\u0003'
|
||||
'\u0004'
|
||||
'\u0005'
|
||||
'\u0006'
|
||||
'\u0007'
|
||||
'\u0008'
|
||||
'\u0009'
|
||||
'\u000A'
|
||||
'\u000B'
|
||||
'\u000C'
|
||||
'\u000D'
|
||||
'\u000E'
|
||||
'\u000F'
|
||||
'\u0010'
|
||||
'\u0011'
|
||||
'\u0012'
|
||||
'\u0013'
|
||||
'\u0014'
|
||||
'\u0015'
|
||||
'\u0016'
|
||||
'\u0017'
|
||||
'\u0018'
|
||||
'\u0019'
|
||||
'\u001A'
|
||||
'\u001B'
|
||||
'\u001C'
|
||||
'\u001D'
|
||||
'\u001E'
|
||||
'\u001F'
|
||||
'\u0020'
|
||||
'\u0021'
|
||||
'\u0022'
|
||||
'\ufffe'
|
||||
'\ufffe'
|
||||
'\u0025'
|
||||
'\u0026'
|
||||
'\u0027'
|
||||
'\u0028'
|
||||
'\u0029'
|
||||
'\u002A'
|
||||
'\u002B'
|
||||
'\u002C'
|
||||
'\u002D'
|
||||
'\u002E'
|
||||
'\u002F'
|
||||
'\u0030'
|
||||
'\u0031'
|
||||
'\u0032'
|
||||
'\u0033'
|
||||
'\u0034'
|
||||
'\u0035'
|
||||
'\u0036'
|
||||
'\u0037'
|
||||
'\u0038'
|
||||
'\u0039'
|
||||
'\u003A'
|
||||
'\u003B'
|
||||
'\u003C'
|
||||
'\u003D'
|
||||
'\u003E'
|
||||
'\u003F'
|
||||
'\u0040'
|
||||
'\u0041'
|
||||
'\u0042'
|
||||
'\u0043'
|
||||
'\u0044'
|
||||
'\u0045'
|
||||
'\u0046'
|
||||
'\u0047'
|
||||
'\u0048'
|
||||
'\u0049'
|
||||
'\u004A'
|
||||
'\u004B'
|
||||
'\u004C'
|
||||
'\u004D'
|
||||
'\u004E'
|
||||
'\u004F'
|
||||
'\u0050'
|
||||
'\u0051'
|
||||
'\u0052'
|
||||
'\u0053'
|
||||
'\u0054'
|
||||
'\u0055'
|
||||
'\u0056'
|
||||
'\u0057'
|
||||
'\u0058'
|
||||
'\u0059'
|
||||
'\u005A'
|
||||
'\u005B'
|
||||
'\ufffe'
|
||||
'\u005D'
|
||||
'\ufffe'
|
||||
'\u005F'
|
||||
'\ufffe'
|
||||
'\u0061'
|
||||
'\u0062'
|
||||
'\u0063'
|
||||
'\u0064'
|
||||
'\u0065'
|
||||
'\u0066'
|
||||
'\u0067'
|
||||
'\u0068'
|
||||
'\u0069'
|
||||
'\u006A'
|
||||
'\u006B'
|
||||
'\u006C'
|
||||
'\u006D'
|
||||
'\u006E'
|
||||
'\u006F'
|
||||
'\u0070'
|
||||
'\u0071'
|
||||
'\u0072'
|
||||
'\u0073'
|
||||
'\u0074'
|
||||
'\u0075'
|
||||
'\u0076'
|
||||
'\u0077'
|
||||
'\u0078'
|
||||
'\u0079'
|
||||
'\u007A'
|
||||
'\ufffe'
|
||||
'\u007C'
|
||||
'\ufffe'
|
||||
'\ufffe'
|
||||
'\u007F'
|
||||
'\u0080'
|
||||
'\u0081'
|
||||
'\u0082'
|
||||
'\u0083'
|
||||
'\u0084'
|
||||
'\u0085'
|
||||
'\u0086'
|
||||
'\u0087'
|
||||
'\u0088'
|
||||
'\u0089'
|
||||
'\u008A'
|
||||
'\u008B'
|
||||
'\u008C'
|
||||
'\u008D'
|
||||
'\u008E'
|
||||
'\u008F'
|
||||
'\u0090'
|
||||
'\u0091'
|
||||
'\u0092'
|
||||
'\u0093'
|
||||
'\u0094'
|
||||
'\u0095'
|
||||
'\u0096'
|
||||
'\u0097'
|
||||
'\u0098'
|
||||
'\u0099'
|
||||
'\u009A'
|
||||
'\u009B'
|
||||
'\u009C'
|
||||
'\u009D'
|
||||
'\u009E'
|
||||
'\u009F'
|
||||
'\u00A0'
|
||||
'\u00A1'
|
||||
'\u00A2'
|
||||
'\u00A3'
|
||||
'\u0024'
|
||||
'\u00A5'
|
||||
'\u0023'
|
||||
'\u00A7'
|
||||
'\u00A4'
|
||||
'\ufffe'
|
||||
'\ufffe'
|
||||
'\u00AB'
|
||||
'\ufffe'
|
||||
'\ufffe'
|
||||
'\ufffe'
|
||||
'\ufffe'
|
||||
'\u00B0'
|
||||
'\u00B1'
|
||||
'\u00B2'
|
||||
'\u00B3'
|
||||
'\u00D7'
|
||||
'\u00B5'
|
||||
'\u00B6'
|
||||
'\u00B7'
|
||||
'\u00F7'
|
||||
'\ufffe'
|
||||
'\ufffe'
|
||||
'\u00BB'
|
||||
'\u00BC'
|
||||
'\u00BD'
|
||||
'\u00BE'
|
||||
'\u00BF'
|
||||
'\ufffe'
|
||||
'\u0300'
|
||||
'\u0301'
|
||||
'\u0302'
|
||||
'\u0303'
|
||||
'\u0304'
|
||||
'\u0306'
|
||||
'\u0307'
|
||||
'\u0308'
|
||||
'\ufffe'
|
||||
'\u030A'
|
||||
'\u0327'
|
||||
'\u0332'
|
||||
'\u030B'
|
||||
'\u0328'
|
||||
'\u030C'
|
||||
'\ufffe'
|
||||
'\ufffe'
|
||||
'\ufffe'
|
||||
'\ufffe'
|
||||
'\ufffe'
|
||||
'\ufffe'
|
||||
'\ufffe'
|
||||
'\ufffe'
|
||||
'\ufffe'
|
||||
'\ufffe'
|
||||
'\ufffe'
|
||||
'\ufffe'
|
||||
'\ufffe'
|
||||
'\ufffe'
|
||||
'\ufffe'
|
||||
'\ufffe'
|
||||
'\u2126'
|
||||
'\u00C6'
|
||||
'\u00D0'
|
||||
'\u00AA'
|
||||
'\u0126'
|
||||
'\ufffe'
|
||||
'\u0132'
|
||||
'\u013F'
|
||||
'\u0141'
|
||||
'\u00D8'
|
||||
'\u0152'
|
||||
'\u00BA'
|
||||
'\u00DE'
|
||||
'\u0166'
|
||||
'\u014A'
|
||||
'\u0149'
|
||||
'\u0138'
|
||||
'\u00E6'
|
||||
'\u0111'
|
||||
'\u00F0'
|
||||
'\u0127'
|
||||
'\u0131'
|
||||
'\u0133'
|
||||
'\u0140'
|
||||
'\u0142'
|
||||
'\u00F8'
|
||||
'\u0153'
|
||||
'\u00DF'
|
||||
'\u00FE'
|
||||
'\u0167'
|
||||
'\u014B'
|
||||
'\ufffe'
|
||||
)
|
||||
ENCODING_TABLE = codecs.charmap_build(DECODING_TABLE)
|
|
@ -0,0 +1,46 @@
|
|||
# coding: utf-8
|
||||
from __future__ import unicode_literals, division, absolute_import, print_function
|
||||
|
||||
import inspect
|
||||
import sys
|
||||
|
||||
|
||||
if sys.version_info < (3,):
|
||||
str_cls = unicode # noqa
|
||||
byte_cls = str
|
||||
int_types = (int, long) # noqa
|
||||
|
||||
def bytes_to_list(byte_string):
|
||||
return [ord(b) for b in byte_string]
|
||||
|
||||
chr_cls = chr
|
||||
|
||||
else:
|
||||
str_cls = str
|
||||
byte_cls = bytes
|
||||
int_types = int
|
||||
|
||||
bytes_to_list = list
|
||||
|
||||
def chr_cls(num):
|
||||
return bytes([num])
|
||||
|
||||
|
||||
def type_name(value):
|
||||
"""
|
||||
Returns a user-readable name for the type of an object
|
||||
|
||||
:param value:
|
||||
A value to get the type name of
|
||||
|
||||
:return:
|
||||
A unicode string of the object's type name
|
||||
"""
|
||||
|
||||
if inspect.isclass(value):
|
||||
cls = value
|
||||
else:
|
||||
cls = value.__class__
|
||||
if cls.__module__ in set(['builtins', '__builtin__']):
|
||||
return cls.__name__
|
||||
return '%s.%s' % (cls.__module__, cls.__name__)
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,932 @@
|
|||
# coding: utf-8
|
||||
|
||||
"""
|
||||
ASN.1 type classes for cryptographic message syntax (CMS). Structures are also
|
||||
compatible with PKCS#7. Exports the following items:
|
||||
|
||||
- AuthenticatedData()
|
||||
- AuthEnvelopedData()
|
||||
- CompressedData()
|
||||
- ContentInfo()
|
||||
- DigestedData()
|
||||
- EncryptedData()
|
||||
- EnvelopedData()
|
||||
- SignedAndEnvelopedData()
|
||||
- SignedData()
|
||||
|
||||
Other type classes are defined that help compose the types listed above.
|
||||
|
||||
Most CMS structures in the wild are formatted as ContentInfo encapsulating one of the other types.
|
||||
"""
|
||||
|
||||
from __future__ import unicode_literals, division, absolute_import, print_function
|
||||
|
||||
try:
|
||||
import zlib
|
||||
except (ImportError):
|
||||
zlib = None
|
||||
|
||||
from .algos import (
|
||||
_ForceNullParameters,
|
||||
DigestAlgorithm,
|
||||
EncryptionAlgorithm,
|
||||
HmacAlgorithm,
|
||||
KdfAlgorithm,
|
||||
SignedDigestAlgorithm,
|
||||
)
|
||||
from .core import (
|
||||
Any,
|
||||
BitString,
|
||||
Choice,
|
||||
Enumerated,
|
||||
GeneralizedTime,
|
||||
Integer,
|
||||
ObjectIdentifier,
|
||||
OctetBitString,
|
||||
OctetString,
|
||||
ParsableOctetString,
|
||||
Sequence,
|
||||
SequenceOf,
|
||||
SetOf,
|
||||
UTCTime,
|
||||
UTF8String,
|
||||
)
|
||||
from .crl import CertificateList
|
||||
from .keys import PublicKeyInfo
|
||||
from .ocsp import OCSPResponse
|
||||
from .x509 import Attributes, Certificate, Extensions, GeneralName, GeneralNames, Name
|
||||
|
||||
|
||||
# These structures are taken from
|
||||
# ftp://ftp.rsasecurity.com/pub/pkcs/ascii/pkcs-6.asc
|
||||
|
||||
class ExtendedCertificateInfo(Sequence):
|
||||
_fields = [
|
||||
('version', Integer),
|
||||
('certificate', Certificate),
|
||||
('attributes', Attributes),
|
||||
]
|
||||
|
||||
|
||||
class ExtendedCertificate(Sequence):
|
||||
_fields = [
|
||||
('extended_certificate_info', ExtendedCertificateInfo),
|
||||
('signature_algorithm', SignedDigestAlgorithm),
|
||||
('signature', OctetBitString),
|
||||
]
|
||||
|
||||
|
||||
# These structures are taken from https://tools.ietf.org/html/rfc5652,
|
||||
# https://tools.ietf.org/html/rfc5083, http://tools.ietf.org/html/rfc2315,
|
||||
# https://tools.ietf.org/html/rfc5940, https://tools.ietf.org/html/rfc3274,
|
||||
# https://tools.ietf.org/html/rfc3281
|
||||
|
||||
|
||||
class CMSVersion(Integer):
|
||||
_map = {
|
||||
0: 'v0',
|
||||
1: 'v1',
|
||||
2: 'v2',
|
||||
3: 'v3',
|
||||
4: 'v4',
|
||||
5: 'v5',
|
||||
}
|
||||
|
||||
|
||||
class CMSAttributeType(ObjectIdentifier):
|
||||
_map = {
|
||||
'1.2.840.113549.1.9.3': 'content_type',
|
||||
'1.2.840.113549.1.9.4': 'message_digest',
|
||||
'1.2.840.113549.1.9.5': 'signing_time',
|
||||
'1.2.840.113549.1.9.6': 'counter_signature',
|
||||
# https://tools.ietf.org/html/rfc3161#page-20
|
||||
'1.2.840.113549.1.9.16.2.14': 'signature_time_stamp_token',
|
||||
# https://tools.ietf.org/html/rfc6211#page-5
|
||||
'1.2.840.113549.1.9.52': 'cms_algorithm_protection',
|
||||
}
|
||||
|
||||
|
||||
class Time(Choice):
|
||||
_alternatives = [
|
||||
('utc_time', UTCTime),
|
||||
('generalized_time', GeneralizedTime),
|
||||
]
|
||||
|
||||
|
||||
class ContentType(ObjectIdentifier):
|
||||
_map = {
|
||||
'1.2.840.113549.1.7.1': 'data',
|
||||
'1.2.840.113549.1.7.2': 'signed_data',
|
||||
'1.2.840.113549.1.7.3': 'enveloped_data',
|
||||
'1.2.840.113549.1.7.4': 'signed_and_enveloped_data',
|
||||
'1.2.840.113549.1.7.5': 'digested_data',
|
||||
'1.2.840.113549.1.7.6': 'encrypted_data',
|
||||
'1.2.840.113549.1.9.16.1.2': 'authenticated_data',
|
||||
'1.2.840.113549.1.9.16.1.9': 'compressed_data',
|
||||
'1.2.840.113549.1.9.16.1.23': 'authenticated_enveloped_data',
|
||||
}
|
||||
|
||||
|
||||
class CMSAlgorithmProtection(Sequence):
|
||||
_fields = [
|
||||
('digest_algorithm', DigestAlgorithm),
|
||||
('signature_algorithm', SignedDigestAlgorithm, {'implicit': 1, 'optional': True}),
|
||||
('mac_algorithm', HmacAlgorithm, {'implicit': 2, 'optional': True}),
|
||||
]
|
||||
|
||||
|
||||
class SetOfContentType(SetOf):
|
||||
_child_spec = ContentType
|
||||
|
||||
|
||||
class SetOfOctetString(SetOf):
|
||||
_child_spec = OctetString
|
||||
|
||||
|
||||
class SetOfTime(SetOf):
|
||||
_child_spec = Time
|
||||
|
||||
|
||||
class SetOfAny(SetOf):
|
||||
_child_spec = Any
|
||||
|
||||
|
||||
class SetOfCMSAlgorithmProtection(SetOf):
|
||||
_child_spec = CMSAlgorithmProtection
|
||||
|
||||
|
||||
class CMSAttribute(Sequence):
|
||||
_fields = [
|
||||
('type', CMSAttributeType),
|
||||
('values', None),
|
||||
]
|
||||
|
||||
_oid_specs = {}
|
||||
|
||||
def _values_spec(self):
|
||||
return self._oid_specs.get(self['type'].native, SetOfAny)
|
||||
|
||||
_spec_callbacks = {
|
||||
'values': _values_spec
|
||||
}
|
||||
|
||||
|
||||
class CMSAttributes(SetOf):
|
||||
_child_spec = CMSAttribute
|
||||
|
||||
|
||||
class IssuerSerial(Sequence):
|
||||
_fields = [
|
||||
('issuer', GeneralNames),
|
||||
('serial', Integer),
|
||||
('issuer_uid', OctetBitString, {'optional': True}),
|
||||
]
|
||||
|
||||
|
||||
class AttCertVersion(Integer):
|
||||
_map = {
|
||||
0: 'v1',
|
||||
1: 'v2',
|
||||
}
|
||||
|
||||
|
||||
class AttCertSubject(Choice):
|
||||
_alternatives = [
|
||||
('base_certificate_id', IssuerSerial, {'explicit': 0}),
|
||||
('subject_name', GeneralNames, {'explicit': 1}),
|
||||
]
|
||||
|
||||
|
||||
class AttCertValidityPeriod(Sequence):
|
||||
_fields = [
|
||||
('not_before_time', GeneralizedTime),
|
||||
('not_after_time', GeneralizedTime),
|
||||
]
|
||||
|
||||
|
||||
class AttributeCertificateInfoV1(Sequence):
|
||||
_fields = [
|
||||
('version', AttCertVersion, {'default': 'v1'}),
|
||||
('subject', AttCertSubject),
|
||||
('issuer', GeneralNames),
|
||||
('signature', SignedDigestAlgorithm),
|
||||
('serial_number', Integer),
|
||||
('att_cert_validity_period', AttCertValidityPeriod),
|
||||
('attributes', Attributes),
|
||||
('issuer_unique_id', OctetBitString, {'optional': True}),
|
||||
('extensions', Extensions, {'optional': True}),
|
||||
]
|
||||
|
||||
|
||||
class AttributeCertificateV1(Sequence):
|
||||
_fields = [
|
||||
('ac_info', AttributeCertificateInfoV1),
|
||||
('signature_algorithm', SignedDigestAlgorithm),
|
||||
('signature', OctetBitString),
|
||||
]
|
||||
|
||||
|
||||
class DigestedObjectType(Enumerated):
|
||||
_map = {
|
||||
0: 'public_key',
|
||||
1: 'public_key_cert',
|
||||
2: 'other_objy_types',
|
||||
}
|
||||
|
||||
|
||||
class ObjectDigestInfo(Sequence):
|
||||
_fields = [
|
||||
('digested_object_type', DigestedObjectType),
|
||||
('other_object_type_id', ObjectIdentifier, {'optional': True}),
|
||||
('digest_algorithm', DigestAlgorithm),
|
||||
('object_digest', OctetBitString),
|
||||
]
|
||||
|
||||
|
||||
class Holder(Sequence):
|
||||
_fields = [
|
||||
('base_certificate_id', IssuerSerial, {'implicit': 0, 'optional': True}),
|
||||
('entity_name', GeneralNames, {'implicit': 1, 'optional': True}),
|
||||
('object_digest_info', ObjectDigestInfo, {'implicit': 2, 'optional': True}),
|
||||
]
|
||||
|
||||
|
||||
class V2Form(Sequence):
|
||||
_fields = [
|
||||
('issuer_name', GeneralNames, {'optional': True}),
|
||||
('base_certificate_id', IssuerSerial, {'explicit': 0, 'optional': True}),
|
||||
('object_digest_info', ObjectDigestInfo, {'explicit': 1, 'optional': True}),
|
||||
]
|
||||
|
||||
|
||||
class AttCertIssuer(Choice):
|
||||
_alternatives = [
|
||||
('v1_form', GeneralNames),
|
||||
('v2_form', V2Form, {'explicit': 0}),
|
||||
]
|
||||
|
||||
|
||||
class IetfAttrValue(Choice):
|
||||
_alternatives = [
|
||||
('octets', OctetString),
|
||||
('oid', ObjectIdentifier),
|
||||
('string', UTF8String),
|
||||
]
|
||||
|
||||
|
||||
class IetfAttrValues(SequenceOf):
|
||||
_child_spec = IetfAttrValue
|
||||
|
||||
|
||||
class IetfAttrSyntax(Sequence):
|
||||
_fields = [
|
||||
('policy_authority', GeneralNames, {'implicit': 0, 'optional': True}),
|
||||
('values', IetfAttrValues),
|
||||
]
|
||||
|
||||
|
||||
class SetOfIetfAttrSyntax(SetOf):
|
||||
_child_spec = IetfAttrSyntax
|
||||
|
||||
|
||||
class SvceAuthInfo(Sequence):
|
||||
_fields = [
|
||||
('service', GeneralName),
|
||||
('ident', GeneralName),
|
||||
('auth_info', OctetString, {'optional': True}),
|
||||
]
|
||||
|
||||
|
||||
class SetOfSvceAuthInfo(SetOf):
|
||||
_child_spec = SvceAuthInfo
|
||||
|
||||
|
||||
class RoleSyntax(Sequence):
|
||||
_fields = [
|
||||
('role_authority', GeneralNames, {'implicit': 0, 'optional': True}),
|
||||
('role_name', GeneralName, {'implicit': 1}),
|
||||
]
|
||||
|
||||
|
||||
class SetOfRoleSyntax(SetOf):
|
||||
_child_spec = RoleSyntax
|
||||
|
||||
|
||||
class ClassList(BitString):
|
||||
_map = {
|
||||
0: 'unmarked',
|
||||
1: 'unclassified',
|
||||
2: 'restricted',
|
||||
3: 'confidential',
|
||||
4: 'secret',
|
||||
5: 'top_secret',
|
||||
}
|
||||
|
||||
|
||||
class SecurityCategory(Sequence):
|
||||
_fields = [
|
||||
('type', ObjectIdentifier, {'implicit': 0}),
|
||||
('value', Any, {'implicit': 1}),
|
||||
]
|
||||
|
||||
|
||||
class SetOfSecurityCategory(SetOf):
|
||||
_child_spec = SecurityCategory
|
||||
|
||||
|
||||
class Clearance(Sequence):
|
||||
_fields = [
|
||||
('policy_id', ObjectIdentifier, {'implicit': 0}),
|
||||
('class_list', ClassList, {'implicit': 1, 'default': 'unclassified'}),
|
||||
('security_categories', SetOfSecurityCategory, {'implicit': 2, 'optional': True}),
|
||||
]
|
||||
|
||||
|
||||
class SetOfClearance(SetOf):
|
||||
_child_spec = Clearance
|
||||
|
||||
|
||||
class BigTime(Sequence):
|
||||
_fields = [
|
||||
('major', Integer),
|
||||
('fractional_seconds', Integer),
|
||||
('sign', Integer, {'optional': True}),
|
||||
]
|
||||
|
||||
|
||||
class LeapData(Sequence):
|
||||
_fields = [
|
||||
('leap_time', BigTime),
|
||||
('action', Integer),
|
||||
]
|
||||
|
||||
|
||||
class SetOfLeapData(SetOf):
|
||||
_child_spec = LeapData
|
||||
|
||||
|
||||
class TimingMetrics(Sequence):
|
||||
_fields = [
|
||||
('ntp_time', BigTime),
|
||||
('offset', BigTime),
|
||||
('delay', BigTime),
|
||||
('expiration', BigTime),
|
||||
('leap_event', SetOfLeapData, {'optional': True}),
|
||||
]
|
||||
|
||||
|
||||
class SetOfTimingMetrics(SetOf):
|
||||
_child_spec = TimingMetrics
|
||||
|
||||
|
||||
class TimingPolicy(Sequence):
|
||||
_fields = [
|
||||
('policy_id', SequenceOf, {'spec': ObjectIdentifier}),
|
||||
('max_offset', BigTime, {'explicit': 0, 'optional': True}),
|
||||
('max_delay', BigTime, {'explicit': 1, 'optional': True}),
|
||||
]
|
||||
|
||||
|
||||
class SetOfTimingPolicy(SetOf):
|
||||
_child_spec = TimingPolicy
|
||||
|
||||
|
||||
class AttCertAttributeType(ObjectIdentifier):
|
||||
_map = {
|
||||
'1.3.6.1.5.5.7.10.1': 'authentication_info',
|
||||
'1.3.6.1.5.5.7.10.2': 'access_identity',
|
||||
'1.3.6.1.5.5.7.10.3': 'charging_identity',
|
||||
'1.3.6.1.5.5.7.10.4': 'group',
|
||||
'2.5.4.72': 'role',
|
||||
'2.5.4.55': 'clearance',
|
||||
'1.3.6.1.4.1.601.10.4.1': 'timing_metrics',
|
||||
'1.3.6.1.4.1.601.10.4.2': 'timing_policy',
|
||||
}
|
||||
|
||||
|
||||
class AttCertAttribute(Sequence):
|
||||
_fields = [
|
||||
('type', AttCertAttributeType),
|
||||
('values', None),
|
||||
]
|
||||
|
||||
_oid_specs = {
|
||||
'authentication_info': SetOfSvceAuthInfo,
|
||||
'access_identity': SetOfSvceAuthInfo,
|
||||
'charging_identity': SetOfIetfAttrSyntax,
|
||||
'group': SetOfIetfAttrSyntax,
|
||||
'role': SetOfRoleSyntax,
|
||||
'clearance': SetOfClearance,
|
||||
'timing_metrics': SetOfTimingMetrics,
|
||||
'timing_policy': SetOfTimingPolicy,
|
||||
}
|
||||
|
||||
def _values_spec(self):
|
||||
return self._oid_specs.get(self['type'].native, SetOfAny)
|
||||
|
||||
_spec_callbacks = {
|
||||
'values': _values_spec
|
||||
}
|
||||
|
||||
|
||||
class AttCertAttributes(SequenceOf):
|
||||
_child_spec = AttCertAttribute
|
||||
|
||||
|
||||
class AttributeCertificateInfoV2(Sequence):
|
||||
_fields = [
|
||||
('version', AttCertVersion),
|
||||
('holder', Holder),
|
||||
('issuer', AttCertIssuer),
|
||||
('signature', SignedDigestAlgorithm),
|
||||
('serial_number', Integer),
|
||||
('att_cert_validity_period', AttCertValidityPeriod),
|
||||
('attributes', AttCertAttributes),
|
||||
('issuer_unique_id', OctetBitString, {'optional': True}),
|
||||
('extensions', Extensions, {'optional': True}),
|
||||
]
|
||||
|
||||
|
||||
class AttributeCertificateV2(Sequence):
|
||||
# Handle the situation where a V2 cert is encoded as V1
|
||||
_bad_tag = 1
|
||||
|
||||
_fields = [
|
||||
('ac_info', AttributeCertificateInfoV2),
|
||||
('signature_algorithm', SignedDigestAlgorithm),
|
||||
('signature', OctetBitString),
|
||||
]
|
||||
|
||||
|
||||
class OtherCertificateFormat(Sequence):
|
||||
_fields = [
|
||||
('other_cert_format', ObjectIdentifier),
|
||||
('other_cert', Any),
|
||||
]
|
||||
|
||||
|
||||
class CertificateChoices(Choice):
|
||||
_alternatives = [
|
||||
('certificate', Certificate),
|
||||
('extended_certificate', ExtendedCertificate, {'implicit': 0}),
|
||||
('v1_attr_cert', AttributeCertificateV1, {'implicit': 1}),
|
||||
('v2_attr_cert', AttributeCertificateV2, {'implicit': 2}),
|
||||
('other', OtherCertificateFormat, {'implicit': 3}),
|
||||
]
|
||||
|
||||
def validate(self, class_, tag, contents):
|
||||
"""
|
||||
Ensures that the class and tag specified exist as an alternative. This
|
||||
custom version fixes parsing broken encodings there a V2 attribute
|
||||
# certificate is encoded as a V1
|
||||
|
||||
:param class_:
|
||||
The integer class_ from the encoded value header
|
||||
|
||||
:param tag:
|
||||
The integer tag from the encoded value header
|
||||
|
||||
:param contents:
|
||||
A byte string of the contents of the value - used when the object
|
||||
is explicitly tagged
|
||||
|
||||
:raises:
|
||||
ValueError - when value is not a valid alternative
|
||||
"""
|
||||
|
||||
super(CertificateChoices, self).validate(class_, tag, contents)
|
||||
if self._choice == 2:
|
||||
if AttCertVersion.load(Sequence.load(contents)[0].dump()).native == 'v2':
|
||||
self._choice = 3
|
||||
|
||||
|
||||
class CertificateSet(SetOf):
|
||||
_child_spec = CertificateChoices
|
||||
|
||||
|
||||
class ContentInfo(Sequence):
|
||||
_fields = [
|
||||
('content_type', ContentType),
|
||||
('content', Any, {'explicit': 0, 'optional': True}),
|
||||
]
|
||||
|
||||
_oid_pair = ('content_type', 'content')
|
||||
_oid_specs = {}
|
||||
|
||||
|
||||
class SetOfContentInfo(SetOf):
|
||||
_child_spec = ContentInfo
|
||||
|
||||
|
||||
class EncapsulatedContentInfo(Sequence):
|
||||
_fields = [
|
||||
('content_type', ContentType),
|
||||
('content', ParsableOctetString, {'explicit': 0, 'optional': True}),
|
||||
]
|
||||
|
||||
_oid_pair = ('content_type', 'content')
|
||||
_oid_specs = {}
|
||||
|
||||
|
||||
class IssuerAndSerialNumber(Sequence):
|
||||
_fields = [
|
||||
('issuer', Name),
|
||||
('serial_number', Integer),
|
||||
]
|
||||
|
||||
|
||||
class SignerIdentifier(Choice):
|
||||
_alternatives = [
|
||||
('issuer_and_serial_number', IssuerAndSerialNumber),
|
||||
('subject_key_identifier', OctetString, {'implicit': 0}),
|
||||
]
|
||||
|
||||
|
||||
class DigestAlgorithms(SetOf):
|
||||
_child_spec = DigestAlgorithm
|
||||
|
||||
|
||||
class CertificateRevocationLists(SetOf):
|
||||
_child_spec = CertificateList
|
||||
|
||||
|
||||
class SCVPReqRes(Sequence):
|
||||
_fields = [
|
||||
('request', ContentInfo, {'explicit': 0, 'optional': True}),
|
||||
('response', ContentInfo),
|
||||
]
|
||||
|
||||
|
||||
class OtherRevInfoFormatId(ObjectIdentifier):
|
||||
_map = {
|
||||
'1.3.6.1.5.5.7.16.2': 'ocsp_response',
|
||||
'1.3.6.1.5.5.7.16.4': 'scvp',
|
||||
}
|
||||
|
||||
|
||||
class OtherRevocationInfoFormat(Sequence):
|
||||
_fields = [
|
||||
('other_rev_info_format', OtherRevInfoFormatId),
|
||||
('other_rev_info', Any),
|
||||
]
|
||||
|
||||
_oid_pair = ('other_rev_info_format', 'other_rev_info')
|
||||
_oid_specs = {
|
||||
'ocsp_response': OCSPResponse,
|
||||
'scvp': SCVPReqRes,
|
||||
}
|
||||
|
||||
|
||||
class RevocationInfoChoice(Choice):
|
||||
_alternatives = [
|
||||
('crl', CertificateList),
|
||||
('other', OtherRevocationInfoFormat, {'implicit': 1}),
|
||||
]
|
||||
|
||||
|
||||
class RevocationInfoChoices(SetOf):
|
||||
_child_spec = RevocationInfoChoice
|
||||
|
||||
|
||||
class SignerInfo(Sequence):
|
||||
_fields = [
|
||||
('version', CMSVersion),
|
||||
('sid', SignerIdentifier),
|
||||
('digest_algorithm', DigestAlgorithm),
|
||||
('signed_attrs', CMSAttributes, {'implicit': 0, 'optional': True}),
|
||||
('signature_algorithm', SignedDigestAlgorithm),
|
||||
('signature', OctetString),
|
||||
('unsigned_attrs', CMSAttributes, {'implicit': 1, 'optional': True}),
|
||||
]
|
||||
|
||||
|
||||
class SignerInfos(SetOf):
|
||||
_child_spec = SignerInfo
|
||||
|
||||
|
||||
class SignedData(Sequence):
|
||||
_fields = [
|
||||
('version', CMSVersion),
|
||||
('digest_algorithms', DigestAlgorithms),
|
||||
('encap_content_info', None),
|
||||
('certificates', CertificateSet, {'implicit': 0, 'optional': True}),
|
||||
('crls', RevocationInfoChoices, {'implicit': 1, 'optional': True}),
|
||||
('signer_infos', SignerInfos),
|
||||
]
|
||||
|
||||
def _encap_content_info_spec(self):
|
||||
# If the encap_content_info is version v1, then this could be a PKCS#7
|
||||
# structure, or a CMS structure. CMS wraps the encoded value in an
|
||||
# Octet String tag.
|
||||
|
||||
# If the version is greater than 1, it is definite CMS
|
||||
if self['version'].native != 'v1':
|
||||
return EncapsulatedContentInfo
|
||||
|
||||
# Otherwise, the ContentInfo spec from PKCS#7 will be compatible with
|
||||
# CMS v1 (which only allows Data, an Octet String) and PKCS#7, which
|
||||
# allows Any
|
||||
return ContentInfo
|
||||
|
||||
_spec_callbacks = {
|
||||
'encap_content_info': _encap_content_info_spec
|
||||
}
|
||||
|
||||
|
||||
class OriginatorInfo(Sequence):
|
||||
_fields = [
|
||||
('certs', CertificateSet, {'implicit': 0, 'optional': True}),
|
||||
('crls', RevocationInfoChoices, {'implicit': 1, 'optional': True}),
|
||||
]
|
||||
|
||||
|
||||
class RecipientIdentifier(Choice):
|
||||
_alternatives = [
|
||||
('issuer_and_serial_number', IssuerAndSerialNumber),
|
||||
('subject_key_identifier', OctetString, {'implicit': 0}),
|
||||
]
|
||||
|
||||
|
||||
class KeyEncryptionAlgorithmId(ObjectIdentifier):
|
||||
_map = {
|
||||
'1.2.840.113549.1.1.1': 'rsa',
|
||||
'2.16.840.1.101.3.4.1.5': 'aes128_wrap',
|
||||
'2.16.840.1.101.3.4.1.8': 'aes128_wrap_pad',
|
||||
'2.16.840.1.101.3.4.1.25': 'aes192_wrap',
|
||||
'2.16.840.1.101.3.4.1.28': 'aes192_wrap_pad',
|
||||
'2.16.840.1.101.3.4.1.45': 'aes256_wrap',
|
||||
'2.16.840.1.101.3.4.1.48': 'aes256_wrap_pad',
|
||||
}
|
||||
|
||||
|
||||
class KeyEncryptionAlgorithm(_ForceNullParameters, Sequence):
|
||||
_fields = [
|
||||
('algorithm', KeyEncryptionAlgorithmId),
|
||||
('parameters', Any, {'optional': True}),
|
||||
]
|
||||
|
||||
|
||||
class KeyTransRecipientInfo(Sequence):
|
||||
_fields = [
|
||||
('version', CMSVersion),
|
||||
('rid', RecipientIdentifier),
|
||||
('key_encryption_algorithm', KeyEncryptionAlgorithm),
|
||||
('encrypted_key', OctetString),
|
||||
]
|
||||
|
||||
|
||||
class OriginatorIdentifierOrKey(Choice):
|
||||
_alternatives = [
|
||||
('issuer_and_serial_number', IssuerAndSerialNumber),
|
||||
('subject_key_identifier', OctetString, {'implicit': 0}),
|
||||
('originator_key', PublicKeyInfo, {'implicit': 1}),
|
||||
]
|
||||
|
||||
|
||||
class OtherKeyAttribute(Sequence):
|
||||
_fields = [
|
||||
('key_attr_id', ObjectIdentifier),
|
||||
('key_attr', Any),
|
||||
]
|
||||
|
||||
|
||||
class RecipientKeyIdentifier(Sequence):
|
||||
_fields = [
|
||||
('subject_key_identifier', OctetString),
|
||||
('date', GeneralizedTime, {'optional': True}),
|
||||
('other', OtherKeyAttribute, {'optional': True}),
|
||||
]
|
||||
|
||||
|
||||
class KeyAgreementRecipientIdentifier(Choice):
|
||||
_alternatives = [
|
||||
('issuer_and_serial_number', IssuerAndSerialNumber),
|
||||
('r_key_id', RecipientKeyIdentifier, {'implicit': 0}),
|
||||
]
|
||||
|
||||
|
||||
class RecipientEncryptedKey(Sequence):
|
||||
_fields = [
|
||||
('rid', KeyAgreementRecipientIdentifier),
|
||||
('encrypted_key', OctetString),
|
||||
]
|
||||
|
||||
|
||||
class RecipientEncryptedKeys(SequenceOf):
|
||||
_child_spec = RecipientEncryptedKey
|
||||
|
||||
|
||||
class KeyAgreeRecipientInfo(Sequence):
|
||||
_fields = [
|
||||
('version', CMSVersion),
|
||||
('originator', OriginatorIdentifierOrKey, {'explicit': 0}),
|
||||
('ukm', OctetString, {'explicit': 1, 'optional': True}),
|
||||
('key_encryption_algorithm', KeyEncryptionAlgorithm),
|
||||
('recipient_encrypted_keys', RecipientEncryptedKeys),
|
||||
]
|
||||
|
||||
|
||||
class KEKIdentifier(Sequence):
|
||||
_fields = [
|
||||
('key_identifier', OctetString),
|
||||
('date', GeneralizedTime, {'optional': True}),
|
||||
('other', OtherKeyAttribute, {'optional': True}),
|
||||
]
|
||||
|
||||
|
||||
class KEKRecipientInfo(Sequence):
|
||||
_fields = [
|
||||
('version', CMSVersion),
|
||||
('kekid', KEKIdentifier),
|
||||
('key_encryption_algorithm', KeyEncryptionAlgorithm),
|
||||
('encrypted_key', OctetString),
|
||||
]
|
||||
|
||||
|
||||
class PasswordRecipientInfo(Sequence):
|
||||
_fields = [
|
||||
('version', CMSVersion),
|
||||
('key_derivation_algorithm', KdfAlgorithm, {'implicit': 0, 'optional': True}),
|
||||
('key_encryption_algorithm', KeyEncryptionAlgorithm),
|
||||
('encrypted_key', OctetString),
|
||||
]
|
||||
|
||||
|
||||
class OtherRecipientInfo(Sequence):
|
||||
_fields = [
|
||||
('ori_type', ObjectIdentifier),
|
||||
('ori_value', Any),
|
||||
]
|
||||
|
||||
|
||||
class RecipientInfo(Choice):
|
||||
_alternatives = [
|
||||
('ktri', KeyTransRecipientInfo),
|
||||
('kari', KeyAgreeRecipientInfo, {'implicit': 1}),
|
||||
('kekri', KEKRecipientInfo, {'implicit': 2}),
|
||||
('pwri', PasswordRecipientInfo, {'implicit': 3}),
|
||||
('ori', OtherRecipientInfo, {'implicit': 4}),
|
||||
]
|
||||
|
||||
|
||||
class RecipientInfos(SetOf):
|
||||
_child_spec = RecipientInfo
|
||||
|
||||
|
||||
class EncryptedContentInfo(Sequence):
|
||||
_fields = [
|
||||
('content_type', ContentType),
|
||||
('content_encryption_algorithm', EncryptionAlgorithm),
|
||||
('encrypted_content', OctetString, {'implicit': 0, 'optional': True}),
|
||||
]
|
||||
|
||||
|
||||
class EnvelopedData(Sequence):
|
||||
_fields = [
|
||||
('version', CMSVersion),
|
||||
('originator_info', OriginatorInfo, {'implicit': 0, 'optional': True}),
|
||||
('recipient_infos', RecipientInfos),
|
||||
('encrypted_content_info', EncryptedContentInfo),
|
||||
('unprotected_attrs', CMSAttributes, {'implicit': 1, 'optional': True}),
|
||||
]
|
||||
|
||||
|
||||
class SignedAndEnvelopedData(Sequence):
|
||||
_fields = [
|
||||
('version', CMSVersion),
|
||||
('recipient_infos', RecipientInfos),
|
||||
('digest_algorithms', DigestAlgorithms),
|
||||
('encrypted_content_info', EncryptedContentInfo),
|
||||
('certificates', CertificateSet, {'implicit': 0, 'optional': True}),
|
||||
('crls', CertificateRevocationLists, {'implicit': 1, 'optional': True}),
|
||||
('signer_infos', SignerInfos),
|
||||
]
|
||||
|
||||
|
||||
class DigestedData(Sequence):
|
||||
_fields = [
|
||||
('version', CMSVersion),
|
||||
('digest_algorithm', DigestAlgorithm),
|
||||
('encap_content_info', None),
|
||||
('digest', OctetString),
|
||||
]
|
||||
|
||||
def _encap_content_info_spec(self):
|
||||
# If the encap_content_info is version v1, then this could be a PKCS#7
|
||||
# structure, or a CMS structure. CMS wraps the encoded value in an
|
||||
# Octet String tag.
|
||||
|
||||
# If the version is greater than 1, it is definite CMS
|
||||
if self['version'].native != 'v1':
|
||||
return EncapsulatedContentInfo
|
||||
|
||||
# Otherwise, the ContentInfo spec from PKCS#7 will be compatible with
|
||||
# CMS v1 (which only allows Data, an Octet String) and PKCS#7, which
|
||||
# allows Any
|
||||
return ContentInfo
|
||||
|
||||
_spec_callbacks = {
|
||||
'encap_content_info': _encap_content_info_spec
|
||||
}
|
||||
|
||||
|
||||
class EncryptedData(Sequence):
|
||||
_fields = [
|
||||
('version', CMSVersion),
|
||||
('encrypted_content_info', EncryptedContentInfo),
|
||||
('unprotected_attrs', CMSAttributes, {'implicit': 1, 'optional': True}),
|
||||
]
|
||||
|
||||
|
||||
class AuthenticatedData(Sequence):
|
||||
_fields = [
|
||||
('version', CMSVersion),
|
||||
('originator_info', OriginatorInfo, {'implicit': 0, 'optional': True}),
|
||||
('recipient_infos', RecipientInfos),
|
||||
('mac_algorithm', HmacAlgorithm),
|
||||
('digest_algorithm', DigestAlgorithm, {'implicit': 1, 'optional': True}),
|
||||
# This does not require the _spec_callbacks approach of SignedData and
|
||||
# DigestedData since AuthenticatedData was not part of PKCS#7
|
||||
('encap_content_info', EncapsulatedContentInfo),
|
||||
('auth_attrs', CMSAttributes, {'implicit': 2, 'optional': True}),
|
||||
('mac', OctetString),
|
||||
('unauth_attrs', CMSAttributes, {'implicit': 3, 'optional': True}),
|
||||
]
|
||||
|
||||
|
||||
class AuthEnvelopedData(Sequence):
|
||||
_fields = [
|
||||
('version', CMSVersion),
|
||||
('originator_info', OriginatorInfo, {'implicit': 0, 'optional': True}),
|
||||
('recipient_infos', RecipientInfos),
|
||||
('auth_encrypted_content_info', EncryptedContentInfo),
|
||||
('auth_attrs', CMSAttributes, {'implicit': 1, 'optional': True}),
|
||||
('mac', OctetString),
|
||||
('unauth_attrs', CMSAttributes, {'implicit': 2, 'optional': True}),
|
||||
]
|
||||
|
||||
|
||||
class CompressionAlgorithmId(ObjectIdentifier):
|
||||
_map = {
|
||||
'1.2.840.113549.1.9.16.3.8': 'zlib',
|
||||
}
|
||||
|
||||
|
||||
class CompressionAlgorithm(Sequence):
|
||||
_fields = [
|
||||
('algorithm', CompressionAlgorithmId),
|
||||
('parameters', Any, {'optional': True}),
|
||||
]
|
||||
|
||||
|
||||
class CompressedData(Sequence):
|
||||
_fields = [
|
||||
('version', CMSVersion),
|
||||
('compression_algorithm', CompressionAlgorithm),
|
||||
('encap_content_info', EncapsulatedContentInfo),
|
||||
]
|
||||
|
||||
_decompressed = None
|
||||
|
||||
@property
|
||||
def decompressed(self):
|
||||
if self._decompressed is None:
|
||||
if zlib is None:
|
||||
raise SystemError('The zlib module is not available')
|
||||
self._decompressed = zlib.decompress(self['encap_content_info']['content'].native)
|
||||
return self._decompressed
|
||||
|
||||
|
||||
ContentInfo._oid_specs = {
|
||||
'data': OctetString,
|
||||
'signed_data': SignedData,
|
||||
'enveloped_data': EnvelopedData,
|
||||
'signed_and_enveloped_data': SignedAndEnvelopedData,
|
||||
'digested_data': DigestedData,
|
||||
'encrypted_data': EncryptedData,
|
||||
'authenticated_data': AuthenticatedData,
|
||||
'compressed_data': CompressedData,
|
||||
'authenticated_enveloped_data': AuthEnvelopedData,
|
||||
}
|
||||
|
||||
|
||||
EncapsulatedContentInfo._oid_specs = {
|
||||
'signed_data': SignedData,
|
||||
'enveloped_data': EnvelopedData,
|
||||
'signed_and_enveloped_data': SignedAndEnvelopedData,
|
||||
'digested_data': DigestedData,
|
||||
'encrypted_data': EncryptedData,
|
||||
'authenticated_data': AuthenticatedData,
|
||||
'compressed_data': CompressedData,
|
||||
'authenticated_enveloped_data': AuthEnvelopedData,
|
||||
}
|
||||
|
||||
|
||||
CMSAttribute._oid_specs = {
|
||||
'content_type': SetOfContentType,
|
||||
'message_digest': SetOfOctetString,
|
||||
'signing_time': SetOfTime,
|
||||
'counter_signature': SignerInfos,
|
||||
'signature_time_stamp_token': SetOfContentInfo,
|
||||
'cms_algorithm_protection': SetOfCMSAlgorithmProtection,
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,536 @@
|
|||
# coding: utf-8
|
||||
|
||||
"""
|
||||
ASN.1 type classes for certificate revocation lists (CRL). Exports the
|
||||
following items:
|
||||
|
||||
- CertificateList()
|
||||
|
||||
Other type classes are defined that help compose the types listed above.
|
||||
"""
|
||||
|
||||
from __future__ import unicode_literals, division, absolute_import, print_function
|
||||
|
||||
import hashlib
|
||||
|
||||
from .algos import SignedDigestAlgorithm
|
||||
from .core import (
|
||||
Boolean,
|
||||
Enumerated,
|
||||
GeneralizedTime,
|
||||
Integer,
|
||||
ObjectIdentifier,
|
||||
OctetBitString,
|
||||
ParsableOctetString,
|
||||
Sequence,
|
||||
SequenceOf,
|
||||
)
|
||||
from .x509 import (
|
||||
AuthorityInfoAccessSyntax,
|
||||
AuthorityKeyIdentifier,
|
||||
CRLDistributionPoints,
|
||||
DistributionPointName,
|
||||
GeneralNames,
|
||||
Name,
|
||||
ReasonFlags,
|
||||
Time,
|
||||
)
|
||||
|
||||
|
||||
# The structures in this file are taken from https://tools.ietf.org/html/rfc5280
|
||||
|
||||
|
||||
class Version(Integer):
|
||||
_map = {
|
||||
0: 'v1',
|
||||
1: 'v2',
|
||||
2: 'v3',
|
||||
}
|
||||
|
||||
|
||||
class IssuingDistributionPoint(Sequence):
|
||||
_fields = [
|
||||
('distribution_point', DistributionPointName, {'explicit': 0, 'optional': True}),
|
||||
('only_contains_user_certs', Boolean, {'implicit': 1, 'default': False}),
|
||||
('only_contains_ca_certs', Boolean, {'implicit': 2, 'default': False}),
|
||||
('only_some_reasons', ReasonFlags, {'implicit': 3, 'optional': True}),
|
||||
('indirect_crl', Boolean, {'implicit': 4, 'default': False}),
|
||||
('only_contains_attribute_certs', Boolean, {'implicit': 5, 'default': False}),
|
||||
]
|
||||
|
||||
|
||||
class TBSCertListExtensionId(ObjectIdentifier):
|
||||
_map = {
|
||||
'2.5.29.18': 'issuer_alt_name',
|
||||
'2.5.29.20': 'crl_number',
|
||||
'2.5.29.27': 'delta_crl_indicator',
|
||||
'2.5.29.28': 'issuing_distribution_point',
|
||||
'2.5.29.35': 'authority_key_identifier',
|
||||
'2.5.29.46': 'freshest_crl',
|
||||
'1.3.6.1.5.5.7.1.1': 'authority_information_access',
|
||||
}
|
||||
|
||||
|
||||
class TBSCertListExtension(Sequence):
|
||||
_fields = [
|
||||
('extn_id', TBSCertListExtensionId),
|
||||
('critical', Boolean, {'default': False}),
|
||||
('extn_value', ParsableOctetString),
|
||||
]
|
||||
|
||||
_oid_pair = ('extn_id', 'extn_value')
|
||||
_oid_specs = {
|
||||
'issuer_alt_name': GeneralNames,
|
||||
'crl_number': Integer,
|
||||
'delta_crl_indicator': Integer,
|
||||
'issuing_distribution_point': IssuingDistributionPoint,
|
||||
'authority_key_identifier': AuthorityKeyIdentifier,
|
||||
'freshest_crl': CRLDistributionPoints,
|
||||
'authority_information_access': AuthorityInfoAccessSyntax,
|
||||
}
|
||||
|
||||
|
||||
class TBSCertListExtensions(SequenceOf):
|
||||
_child_spec = TBSCertListExtension
|
||||
|
||||
|
||||
class CRLReason(Enumerated):
|
||||
_map = {
|
||||
0: 'unspecified',
|
||||
1: 'key_compromise',
|
||||
2: 'ca_compromise',
|
||||
3: 'affiliation_changed',
|
||||
4: 'superseded',
|
||||
5: 'cessation_of_operation',
|
||||
6: 'certificate_hold',
|
||||
8: 'remove_from_crl',
|
||||
9: 'privilege_withdrawn',
|
||||
10: 'aa_compromise',
|
||||
}
|
||||
|
||||
@property
|
||||
def human_friendly(self):
|
||||
"""
|
||||
:return:
|
||||
A unicode string with revocation description that is suitable to
|
||||
show to end-users. Starts with a lower case letter and phrased in
|
||||
such a way that it makes sense after the phrase "because of" or
|
||||
"due to".
|
||||
"""
|
||||
|
||||
return {
|
||||
'unspecified': 'an unspecified reason',
|
||||
'key_compromise': 'a compromised key',
|
||||
'ca_compromise': 'the CA being compromised',
|
||||
'affiliation_changed': 'an affiliation change',
|
||||
'superseded': 'certificate supersession',
|
||||
'cessation_of_operation': 'a cessation of operation',
|
||||
'certificate_hold': 'a certificate hold',
|
||||
'remove_from_crl': 'removal from the CRL',
|
||||
'privilege_withdrawn': 'privilege withdrawl',
|
||||
'aa_compromise': 'the AA being compromised',
|
||||
}[self.native]
|
||||
|
||||
|
||||
class CRLEntryExtensionId(ObjectIdentifier):
|
||||
_map = {
|
||||
'2.5.29.21': 'crl_reason',
|
||||
'2.5.29.23': 'hold_instruction_code',
|
||||
'2.5.29.24': 'invalidity_date',
|
||||
'2.5.29.29': 'certificate_issuer',
|
||||
}
|
||||
|
||||
|
||||
class CRLEntryExtension(Sequence):
|
||||
_fields = [
|
||||
('extn_id', CRLEntryExtensionId),
|
||||
('critical', Boolean, {'default': False}),
|
||||
('extn_value', ParsableOctetString),
|
||||
]
|
||||
|
||||
_oid_pair = ('extn_id', 'extn_value')
|
||||
_oid_specs = {
|
||||
'crl_reason': CRLReason,
|
||||
'hold_instruction_code': ObjectIdentifier,
|
||||
'invalidity_date': GeneralizedTime,
|
||||
'certificate_issuer': GeneralNames,
|
||||
}
|
||||
|
||||
|
||||
class CRLEntryExtensions(SequenceOf):
|
||||
_child_spec = CRLEntryExtension
|
||||
|
||||
|
||||
class RevokedCertificate(Sequence):
|
||||
_fields = [
|
||||
('user_certificate', Integer),
|
||||
('revocation_date', Time),
|
||||
('crl_entry_extensions', CRLEntryExtensions, {'optional': True}),
|
||||
]
|
||||
|
||||
_processed_extensions = False
|
||||
_critical_extensions = None
|
||||
_crl_reason_value = None
|
||||
_invalidity_date_value = None
|
||||
_certificate_issuer_value = None
|
||||
_issuer_name = False
|
||||
|
||||
def _set_extensions(self):
|
||||
"""
|
||||
Sets common named extensions to private attributes and creates a list
|
||||
of critical extensions
|
||||
"""
|
||||
|
||||
self._critical_extensions = set()
|
||||
|
||||
for extension in self['crl_entry_extensions']:
|
||||
name = extension['extn_id'].native
|
||||
attribute_name = '_%s_value' % name
|
||||
if hasattr(self, attribute_name):
|
||||
setattr(self, attribute_name, extension['extn_value'].parsed)
|
||||
if extension['critical'].native:
|
||||
self._critical_extensions.add(name)
|
||||
|
||||
self._processed_extensions = True
|
||||
|
||||
@property
|
||||
def critical_extensions(self):
|
||||
"""
|
||||
Returns a set of the names (or OID if not a known extension) of the
|
||||
extensions marked as critical
|
||||
|
||||
:return:
|
||||
A set of unicode strings
|
||||
"""
|
||||
|
||||
if not self._processed_extensions:
|
||||
self._set_extensions()
|
||||
return self._critical_extensions
|
||||
|
||||
@property
|
||||
def crl_reason_value(self):
|
||||
"""
|
||||
This extension indicates the reason that a certificate was revoked.
|
||||
|
||||
:return:
|
||||
None or a CRLReason object
|
||||
"""
|
||||
|
||||
if self._processed_extensions is False:
|
||||
self._set_extensions()
|
||||
return self._crl_reason_value
|
||||
|
||||
@property
|
||||
def invalidity_date_value(self):
|
||||
"""
|
||||
This extension indicates the suspected date/time the private key was
|
||||
compromised or the certificate became invalid. This would usually be
|
||||
before the revocation date, which is when the CA processed the
|
||||
revocation.
|
||||
|
||||
:return:
|
||||
None or a GeneralizedTime object
|
||||
"""
|
||||
|
||||
if self._processed_extensions is False:
|
||||
self._set_extensions()
|
||||
return self._invalidity_date_value
|
||||
|
||||
@property
|
||||
def certificate_issuer_value(self):
|
||||
"""
|
||||
This extension indicates the issuer of the certificate in question,
|
||||
and is used in indirect CRLs. CRL entries without this extension are
|
||||
for certificates issued from the last seen issuer.
|
||||
|
||||
:return:
|
||||
None or an x509.GeneralNames object
|
||||
"""
|
||||
|
||||
if self._processed_extensions is False:
|
||||
self._set_extensions()
|
||||
return self._certificate_issuer_value
|
||||
|
||||
@property
|
||||
def issuer_name(self):
|
||||
"""
|
||||
:return:
|
||||
None, or an asn1crypto.x509.Name object for the issuer of the cert
|
||||
"""
|
||||
|
||||
if self._issuer_name is False:
|
||||
self._issuer_name = None
|
||||
if self.certificate_issuer_value:
|
||||
for general_name in self.certificate_issuer_value:
|
||||
if general_name.name == 'directory_name':
|
||||
self._issuer_name = general_name.chosen
|
||||
break
|
||||
return self._issuer_name
|
||||
|
||||
|
||||
class RevokedCertificates(SequenceOf):
|
||||
_child_spec = RevokedCertificate
|
||||
|
||||
|
||||
class TbsCertList(Sequence):
|
||||
_fields = [
|
||||
('version', Version, {'optional': True}),
|
||||
('signature', SignedDigestAlgorithm),
|
||||
('issuer', Name),
|
||||
('this_update', Time),
|
||||
('next_update', Time, {'optional': True}),
|
||||
('revoked_certificates', RevokedCertificates, {'optional': True}),
|
||||
('crl_extensions', TBSCertListExtensions, {'explicit': 0, 'optional': True}),
|
||||
]
|
||||
|
||||
|
||||
class CertificateList(Sequence):
|
||||
_fields = [
|
||||
('tbs_cert_list', TbsCertList),
|
||||
('signature_algorithm', SignedDigestAlgorithm),
|
||||
('signature', OctetBitString),
|
||||
]
|
||||
|
||||
_processed_extensions = False
|
||||
_critical_extensions = None
|
||||
_issuer_alt_name_value = None
|
||||
_crl_number_value = None
|
||||
_delta_crl_indicator_value = None
|
||||
_issuing_distribution_point_value = None
|
||||
_authority_key_identifier_value = None
|
||||
_freshest_crl_value = None
|
||||
_authority_information_access_value = None
|
||||
_issuer_cert_urls = None
|
||||
_delta_crl_distribution_points = None
|
||||
_sha1 = None
|
||||
_sha256 = None
|
||||
|
||||
def _set_extensions(self):
|
||||
"""
|
||||
Sets common named extensions to private attributes and creates a list
|
||||
of critical extensions
|
||||
"""
|
||||
|
||||
self._critical_extensions = set()
|
||||
|
||||
for extension in self['tbs_cert_list']['crl_extensions']:
|
||||
name = extension['extn_id'].native
|
||||
attribute_name = '_%s_value' % name
|
||||
if hasattr(self, attribute_name):
|
||||
setattr(self, attribute_name, extension['extn_value'].parsed)
|
||||
if extension['critical'].native:
|
||||
self._critical_extensions.add(name)
|
||||
|
||||
self._processed_extensions = True
|
||||
|
||||
@property
|
||||
def critical_extensions(self):
|
||||
"""
|
||||
Returns a set of the names (or OID if not a known extension) of the
|
||||
extensions marked as critical
|
||||
|
||||
:return:
|
||||
A set of unicode strings
|
||||
"""
|
||||
|
||||
if not self._processed_extensions:
|
||||
self._set_extensions()
|
||||
return self._critical_extensions
|
||||
|
||||
@property
|
||||
def issuer_alt_name_value(self):
|
||||
"""
|
||||
This extension allows associating one or more alternative names with
|
||||
the issuer of the CRL.
|
||||
|
||||
:return:
|
||||
None or an x509.GeneralNames object
|
||||
"""
|
||||
|
||||
if self._processed_extensions is False:
|
||||
self._set_extensions()
|
||||
return self._issuer_alt_name_value
|
||||
|
||||
@property
|
||||
def crl_number_value(self):
|
||||
"""
|
||||
This extension adds a monotonically increasing number to the CRL and is
|
||||
used to distinguish different versions of the CRL.
|
||||
|
||||
:return:
|
||||
None or an Integer object
|
||||
"""
|
||||
|
||||
if self._processed_extensions is False:
|
||||
self._set_extensions()
|
||||
return self._crl_number_value
|
||||
|
||||
@property
|
||||
def delta_crl_indicator_value(self):
|
||||
"""
|
||||
This extension indicates a CRL is a delta CRL, and contains the CRL
|
||||
number of the base CRL that it is a delta from.
|
||||
|
||||
:return:
|
||||
None or an Integer object
|
||||
"""
|
||||
|
||||
if self._processed_extensions is False:
|
||||
self._set_extensions()
|
||||
return self._delta_crl_indicator_value
|
||||
|
||||
@property
|
||||
def issuing_distribution_point_value(self):
|
||||
"""
|
||||
This extension includes information about what types of revocations
|
||||
and certificates are part of the CRL.
|
||||
|
||||
:return:
|
||||
None or an IssuingDistributionPoint object
|
||||
"""
|
||||
|
||||
if self._processed_extensions is False:
|
||||
self._set_extensions()
|
||||
return self._issuing_distribution_point_value
|
||||
|
||||
@property
|
||||
def authority_key_identifier_value(self):
|
||||
"""
|
||||
This extension helps in identifying the public key with which to
|
||||
validate the authenticity of the CRL.
|
||||
|
||||
:return:
|
||||
None or an AuthorityKeyIdentifier object
|
||||
"""
|
||||
|
||||
if self._processed_extensions is False:
|
||||
self._set_extensions()
|
||||
return self._authority_key_identifier_value
|
||||
|
||||
@property
|
||||
def freshest_crl_value(self):
|
||||
"""
|
||||
This extension is used in complete CRLs to indicate where a delta CRL
|
||||
may be located.
|
||||
|
||||
:return:
|
||||
None or a CRLDistributionPoints object
|
||||
"""
|
||||
|
||||
if self._processed_extensions is False:
|
||||
self._set_extensions()
|
||||
return self._freshest_crl_value
|
||||
|
||||
@property
|
||||
def authority_information_access_value(self):
|
||||
"""
|
||||
This extension is used to provide a URL with which to download the
|
||||
certificate used to sign this CRL.
|
||||
|
||||
:return:
|
||||
None or an AuthorityInfoAccessSyntax object
|
||||
"""
|
||||
|
||||
if self._processed_extensions is False:
|
||||
self._set_extensions()
|
||||
return self._authority_information_access_value
|
||||
|
||||
@property
|
||||
def issuer(self):
|
||||
"""
|
||||
:return:
|
||||
An asn1crypto.x509.Name object for the issuer of the CRL
|
||||
"""
|
||||
|
||||
return self['tbs_cert_list']['issuer']
|
||||
|
||||
@property
|
||||
def authority_key_identifier(self):
|
||||
"""
|
||||
:return:
|
||||
None or a byte string of the key_identifier from the authority key
|
||||
identifier extension
|
||||
"""
|
||||
|
||||
if not self.authority_key_identifier_value:
|
||||
return None
|
||||
|
||||
return self.authority_key_identifier_value['key_identifier'].native
|
||||
|
||||
@property
|
||||
def issuer_cert_urls(self):
|
||||
"""
|
||||
:return:
|
||||
A list of unicode strings that are URLs that should contain either
|
||||
an individual DER-encoded X.509 certificate, or a DER-encoded CMS
|
||||
message containing multiple certificates
|
||||
"""
|
||||
|
||||
if self._issuer_cert_urls is None:
|
||||
self._issuer_cert_urls = []
|
||||
if self.authority_information_access_value:
|
||||
for entry in self.authority_information_access_value:
|
||||
if entry['access_method'].native == 'ca_issuers':
|
||||
location = entry['access_location']
|
||||
if location.name != 'uniform_resource_identifier':
|
||||
continue
|
||||
url = location.native
|
||||
if url.lower()[0:7] == 'http://':
|
||||
self._issuer_cert_urls.append(url)
|
||||
return self._issuer_cert_urls
|
||||
|
||||
@property
|
||||
def delta_crl_distribution_points(self):
|
||||
"""
|
||||
Returns delta CRL URLs - only applies to complete CRLs
|
||||
|
||||
:return:
|
||||
A list of zero or more DistributionPoint objects
|
||||
"""
|
||||
|
||||
if self._delta_crl_distribution_points is None:
|
||||
self._delta_crl_distribution_points = []
|
||||
|
||||
if self.freshest_crl_value is not None:
|
||||
for distribution_point in self.freshest_crl_value:
|
||||
distribution_point_name = distribution_point['distribution_point']
|
||||
# RFC 5280 indicates conforming CA should not use the relative form
|
||||
if distribution_point_name.name == 'name_relative_to_crl_issuer':
|
||||
continue
|
||||
# This library is currently only concerned with HTTP-based CRLs
|
||||
for general_name in distribution_point_name.chosen:
|
||||
if general_name.name == 'uniform_resource_identifier':
|
||||
self._delta_crl_distribution_points.append(distribution_point)
|
||||
|
||||
return self._delta_crl_distribution_points
|
||||
|
||||
@property
|
||||
def signature(self):
|
||||
"""
|
||||
:return:
|
||||
A byte string of the signature
|
||||
"""
|
||||
|
||||
return self['signature'].native
|
||||
|
||||
@property
|
||||
def sha1(self):
|
||||
"""
|
||||
:return:
|
||||
The SHA1 hash of the DER-encoded bytes of this certificate list
|
||||
"""
|
||||
|
||||
if self._sha1 is None:
|
||||
self._sha1 = hashlib.sha1(self.dump()).digest()
|
||||
return self._sha1
|
||||
|
||||
@property
|
||||
def sha256(self):
|
||||
"""
|
||||
:return:
|
||||
The SHA-256 hash of the DER-encoded bytes of this certificate list
|
||||
"""
|
||||
|
||||
if self._sha256 is None:
|
||||
self._sha256 = hashlib.sha256(self.dump()).digest()
|
||||
return self._sha256
|
|
@ -0,0 +1,96 @@
|
|||
# coding: utf-8
|
||||
|
||||
"""
|
||||
ASN.1 type classes for certificate signing requests (CSR). Exports the
|
||||
following items:
|
||||
|
||||
- CertificatationRequest()
|
||||
|
||||
Other type classes are defined that help compose the types listed above.
|
||||
"""
|
||||
|
||||
from __future__ import unicode_literals, division, absolute_import, print_function
|
||||
|
||||
from .algos import SignedDigestAlgorithm
|
||||
from .core import (
|
||||
Any,
|
||||
Integer,
|
||||
ObjectIdentifier,
|
||||
OctetBitString,
|
||||
Sequence,
|
||||
SetOf,
|
||||
)
|
||||
from .keys import PublicKeyInfo
|
||||
from .x509 import DirectoryString, Extensions, Name
|
||||
|
||||
|
||||
# The structures in this file are taken from https://tools.ietf.org/html/rfc2986
|
||||
# and https://tools.ietf.org/html/rfc2985
|
||||
|
||||
|
||||
class Version(Integer):
|
||||
_map = {
|
||||
0: 'v1',
|
||||
}
|
||||
|
||||
|
||||
class CSRAttributeType(ObjectIdentifier):
|
||||
_map = {
|
||||
'1.2.840.113549.1.9.7': 'challenge_password',
|
||||
'1.2.840.113549.1.9.9': 'extended_certificate_attributes',
|
||||
'1.2.840.113549.1.9.14': 'extension_request',
|
||||
}
|
||||
|
||||
|
||||
class SetOfDirectoryString(SetOf):
|
||||
_child_spec = DirectoryString
|
||||
|
||||
|
||||
class Attribute(Sequence):
|
||||
_fields = [
|
||||
('type', ObjectIdentifier),
|
||||
('values', SetOf, {'spec': Any}),
|
||||
]
|
||||
|
||||
|
||||
class SetOfAttributes(SetOf):
|
||||
_child_spec = Attribute
|
||||
|
||||
|
||||
class SetOfExtensions(SetOf):
|
||||
_child_spec = Extensions
|
||||
|
||||
|
||||
class CRIAttribute(Sequence):
|
||||
_fields = [
|
||||
('type', CSRAttributeType),
|
||||
('values', Any),
|
||||
]
|
||||
|
||||
_oid_pair = ('type', 'values')
|
||||
_oid_specs = {
|
||||
'challenge_password': SetOfDirectoryString,
|
||||
'extended_certificate_attributes': SetOfAttributes,
|
||||
'extension_request': SetOfExtensions,
|
||||
}
|
||||
|
||||
|
||||
class CRIAttributes(SetOf):
|
||||
_child_spec = CRIAttribute
|
||||
|
||||
|
||||
class CertificationRequestInfo(Sequence):
|
||||
_fields = [
|
||||
('version', Version),
|
||||
('subject', Name),
|
||||
('subject_pk_info', PublicKeyInfo),
|
||||
('attributes', CRIAttributes, {'implicit': 0, 'optional': True}),
|
||||
]
|
||||
|
||||
|
||||
class CertificationRequest(Sequence):
|
||||
_fields = [
|
||||
('certification_request_info', CertificationRequestInfo),
|
||||
('signature_algorithm', SignedDigestAlgorithm),
|
||||
('signature', OctetBitString),
|
||||
]
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,652 @@
|
|||
# coding: utf-8
|
||||
|
||||
"""
|
||||
ASN.1 type classes for the online certificate status protocol (OCSP). Exports
|
||||
the following items:
|
||||
|
||||
- OCSPRequest()
|
||||
- OCSPResponse()
|
||||
|
||||
Other type classes are defined that help compose the types listed above.
|
||||
"""
|
||||
|
||||
from __future__ import unicode_literals, division, absolute_import, print_function
|
||||
|
||||
from .algos import DigestAlgorithm, SignedDigestAlgorithm
|
||||
from .core import (
|
||||
Boolean,
|
||||
Choice,
|
||||
Enumerated,
|
||||
GeneralizedTime,
|
||||
IA5String,
|
||||
Integer,
|
||||
Null,
|
||||
ObjectIdentifier,
|
||||
OctetBitString,
|
||||
OctetString,
|
||||
ParsableOctetString,
|
||||
Sequence,
|
||||
SequenceOf,
|
||||
)
|
||||
from .crl import AuthorityInfoAccessSyntax, CRLReason
|
||||
from .keys import PublicKeyAlgorithm
|
||||
from .x509 import Certificate, GeneralName, GeneralNames, Name
|
||||
|
||||
|
||||
# The structures in this file are taken from https://tools.ietf.org/html/rfc6960
|
||||
|
||||
|
||||
class Version(Integer):
|
||||
_map = {
|
||||
0: 'v1'
|
||||
}
|
||||
|
||||
|
||||
class CertId(Sequence):
|
||||
_fields = [
|
||||
('hash_algorithm', DigestAlgorithm),
|
||||
('issuer_name_hash', OctetString),
|
||||
('issuer_key_hash', OctetString),
|
||||
('serial_number', Integer),
|
||||
]
|
||||
|
||||
|
||||
class ServiceLocator(Sequence):
|
||||
_fields = [
|
||||
('issuer', Name),
|
||||
('locator', AuthorityInfoAccessSyntax),
|
||||
]
|
||||
|
||||
|
||||
class RequestExtensionId(ObjectIdentifier):
|
||||
_map = {
|
||||
'1.3.6.1.5.5.7.48.1.7': 'service_locator',
|
||||
}
|
||||
|
||||
|
||||
class RequestExtension(Sequence):
|
||||
_fields = [
|
||||
('extn_id', RequestExtensionId),
|
||||
('critical', Boolean, {'default': False}),
|
||||
('extn_value', ParsableOctetString),
|
||||
]
|
||||
|
||||
_oid_pair = ('extn_id', 'extn_value')
|
||||
_oid_specs = {
|
||||
'service_locator': ServiceLocator,
|
||||
}
|
||||
|
||||
|
||||
class RequestExtensions(SequenceOf):
|
||||
_child_spec = RequestExtension
|
||||
|
||||
|
||||
class Request(Sequence):
|
||||
_fields = [
|
||||
('req_cert', CertId),
|
||||
('single_request_extensions', RequestExtensions, {'explicit': 0, 'optional': True}),
|
||||
]
|
||||
|
||||
_processed_extensions = False
|
||||
_critical_extensions = None
|
||||
_service_locator_value = None
|
||||
|
||||
def _set_extensions(self):
|
||||
"""
|
||||
Sets common named extensions to private attributes and creates a list
|
||||
of critical extensions
|
||||
"""
|
||||
|
||||
self._critical_extensions = set()
|
||||
|
||||
for extension in self['single_request_extensions']:
|
||||
name = extension['extn_id'].native
|
||||
attribute_name = '_%s_value' % name
|
||||
if hasattr(self, attribute_name):
|
||||
setattr(self, attribute_name, extension['extn_value'].parsed)
|
||||
if extension['critical'].native:
|
||||
self._critical_extensions.add(name)
|
||||
|
||||
self._processed_extensions = True
|
||||
|
||||
@property
|
||||
def critical_extensions(self):
|
||||
"""
|
||||
Returns a set of the names (or OID if not a known extension) of the
|
||||
extensions marked as critical
|
||||
|
||||
:return:
|
||||
A set of unicode strings
|
||||
"""
|
||||
|
||||
if not self._processed_extensions:
|
||||
self._set_extensions()
|
||||
return self._critical_extensions
|
||||
|
||||
@property
|
||||
def service_locator_value(self):
|
||||
"""
|
||||
This extension is used when communicating with an OCSP responder that
|
||||
acts as a proxy for OCSP requests
|
||||
|
||||
:return:
|
||||
None or a ServiceLocator object
|
||||
"""
|
||||
|
||||
if self._processed_extensions is False:
|
||||
self._set_extensions()
|
||||
return self._service_locator_value
|
||||
|
||||
|
||||
class Requests(SequenceOf):
|
||||
_child_spec = Request
|
||||
|
||||
|
||||
class ResponseType(ObjectIdentifier):
|
||||
_map = {
|
||||
'1.3.6.1.5.5.7.48.1.1': 'basic_ocsp_response',
|
||||
}
|
||||
|
||||
|
||||
class AcceptableResponses(SequenceOf):
|
||||
_child_spec = ResponseType
|
||||
|
||||
|
||||
class PreferredSignatureAlgorithm(Sequence):
|
||||
_fields = [
|
||||
('sig_identifier', SignedDigestAlgorithm),
|
||||
('cert_identifier', PublicKeyAlgorithm, {'optional': True}),
|
||||
]
|
||||
|
||||
|
||||
class PreferredSignatureAlgorithms(SequenceOf):
|
||||
_child_spec = PreferredSignatureAlgorithm
|
||||
|
||||
|
||||
class TBSRequestExtensionId(ObjectIdentifier):
|
||||
_map = {
|
||||
'1.3.6.1.5.5.7.48.1.2': 'nonce',
|
||||
'1.3.6.1.5.5.7.48.1.4': 'acceptable_responses',
|
||||
'1.3.6.1.5.5.7.48.1.8': 'preferred_signature_algorithms',
|
||||
}
|
||||
|
||||
|
||||
class TBSRequestExtension(Sequence):
|
||||
_fields = [
|
||||
('extn_id', TBSRequestExtensionId),
|
||||
('critical', Boolean, {'default': False}),
|
||||
('extn_value', ParsableOctetString),
|
||||
]
|
||||
|
||||
_oid_pair = ('extn_id', 'extn_value')
|
||||
_oid_specs = {
|
||||
'nonce': OctetString,
|
||||
'acceptable_responses': AcceptableResponses,
|
||||
'preferred_signature_algorithms': PreferredSignatureAlgorithms,
|
||||
}
|
||||
|
||||
|
||||
class TBSRequestExtensions(SequenceOf):
|
||||
_child_spec = TBSRequestExtension
|
||||
|
||||
|
||||
class TBSRequest(Sequence):
|
||||
_fields = [
|
||||
('version', Version, {'explicit': 0, 'default': 'v1'}),
|
||||
('requestor_name', GeneralName, {'explicit': 1, 'optional': True}),
|
||||
('request_list', Requests),
|
||||
('request_extensions', TBSRequestExtensions, {'explicit': 2, 'optional': True}),
|
||||
]
|
||||
|
||||
|
||||
class Certificates(SequenceOf):
|
||||
_child_spec = Certificate
|
||||
|
||||
|
||||
class Signature(Sequence):
|
||||
_fields = [
|
||||
('signature_algorithm', SignedDigestAlgorithm),
|
||||
('signature', OctetBitString),
|
||||
('certs', Certificates, {'explicit': 0, 'optional': True}),
|
||||
]
|
||||
|
||||
|
||||
class OCSPRequest(Sequence):
|
||||
_fields = [
|
||||
('tbs_request', TBSRequest),
|
||||
('optional_signature', Signature, {'explicit': 0, 'optional': True}),
|
||||
]
|
||||
|
||||
_processed_extensions = False
|
||||
_critical_extensions = None
|
||||
_nonce_value = None
|
||||
_acceptable_responses_value = None
|
||||
_preferred_signature_algorithms_value = None
|
||||
|
||||
def _set_extensions(self):
|
||||
"""
|
||||
Sets common named extensions to private attributes and creates a list
|
||||
of critical extensions
|
||||
"""
|
||||
|
||||
self._critical_extensions = set()
|
||||
|
||||
for extension in self['tbs_request']['request_extensions']:
|
||||
name = extension['extn_id'].native
|
||||
attribute_name = '_%s_value' % name
|
||||
if hasattr(self, attribute_name):
|
||||
setattr(self, attribute_name, extension['extn_value'].parsed)
|
||||
if extension['critical'].native:
|
||||
self._critical_extensions.add(name)
|
||||
|
||||
self._processed_extensions = True
|
||||
|
||||
@property
|
||||
def critical_extensions(self):
|
||||
"""
|
||||
Returns a set of the names (or OID if not a known extension) of the
|
||||
extensions marked as critical
|
||||
|
||||
:return:
|
||||
A set of unicode strings
|
||||
"""
|
||||
|
||||
if not self._processed_extensions:
|
||||
self._set_extensions()
|
||||
return self._critical_extensions
|
||||
|
||||
@property
|
||||
def nonce_value(self):
|
||||
"""
|
||||
This extension is used to prevent replay attacks by including a unique,
|
||||
random value with each request/response pair
|
||||
|
||||
:return:
|
||||
None or an OctetString object
|
||||
"""
|
||||
|
||||
if self._processed_extensions is False:
|
||||
self._set_extensions()
|
||||
return self._nonce_value
|
||||
|
||||
@property
|
||||
def acceptable_responses_value(self):
|
||||
"""
|
||||
This extension is used to allow the client and server to communicate
|
||||
with alternative response formats other than just basic_ocsp_response,
|
||||
although no other formats are defined in the standard.
|
||||
|
||||
:return:
|
||||
None or an AcceptableResponses object
|
||||
"""
|
||||
|
||||
if self._processed_extensions is False:
|
||||
self._set_extensions()
|
||||
return self._acceptable_responses_value
|
||||
|
||||
@property
|
||||
def preferred_signature_algorithms_value(self):
|
||||
"""
|
||||
This extension is used by the client to define what signature algorithms
|
||||
are preferred, including both the hash algorithm and the public key
|
||||
algorithm, with a level of detail down to even the public key algorithm
|
||||
parameters, such as curve name.
|
||||
|
||||
:return:
|
||||
None or a PreferredSignatureAlgorithms object
|
||||
"""
|
||||
|
||||
if self._processed_extensions is False:
|
||||
self._set_extensions()
|
||||
return self._preferred_signature_algorithms_value
|
||||
|
||||
|
||||
class OCSPResponseStatus(Enumerated):
|
||||
_map = {
|
||||
0: 'successful',
|
||||
1: 'malformed_request',
|
||||
2: 'internal_error',
|
||||
3: 'try_later',
|
||||
5: 'sign_required',
|
||||
6: 'unauthorized',
|
||||
}
|
||||
|
||||
|
||||
class ResponderId(Choice):
|
||||
_alternatives = [
|
||||
('by_name', Name, {'explicit': 1}),
|
||||
('by_key', OctetString, {'explicit': 2}),
|
||||
]
|
||||
|
||||
|
||||
class RevokedInfo(Sequence):
|
||||
_fields = [
|
||||
('revocation_time', GeneralizedTime),
|
||||
('revocation_reason', CRLReason, {'explicit': 0, 'optional': True}),
|
||||
]
|
||||
|
||||
|
||||
class CertStatus(Choice):
|
||||
_alternatives = [
|
||||
('good', Null, {'implicit': 0}),
|
||||
('revoked', RevokedInfo, {'implicit': 1}),
|
||||
('unknown', Null, {'implicit': 2}),
|
||||
]
|
||||
|
||||
|
||||
class CrlId(Sequence):
|
||||
_fields = [
|
||||
('crl_url', IA5String, {'explicit': 0, 'optional': True}),
|
||||
('crl_num', Integer, {'explicit': 1, 'optional': True}),
|
||||
('crl_time', GeneralizedTime, {'explicit': 2, 'optional': True}),
|
||||
]
|
||||
|
||||
|
||||
class SingleResponseExtensionId(ObjectIdentifier):
|
||||
_map = {
|
||||
'1.3.6.1.5.5.7.48.1.3': 'crl',
|
||||
'1.3.6.1.5.5.7.48.1.6': 'archive_cutoff',
|
||||
# These are CRLEntryExtension values from
|
||||
# https://tools.ietf.org/html/rfc5280
|
||||
'2.5.29.21': 'crl_reason',
|
||||
'2.5.29.24': 'invalidity_date',
|
||||
'2.5.29.29': 'certificate_issuer',
|
||||
# https://tools.ietf.org/html/rfc6962.html#page-13
|
||||
'1.3.6.1.4.1.11129.2.4.5': 'signed_certificate_timestamp_list',
|
||||
}
|
||||
|
||||
|
||||
class SingleResponseExtension(Sequence):
|
||||
_fields = [
|
||||
('extn_id', SingleResponseExtensionId),
|
||||
('critical', Boolean, {'default': False}),
|
||||
('extn_value', ParsableOctetString),
|
||||
]
|
||||
|
||||
_oid_pair = ('extn_id', 'extn_value')
|
||||
_oid_specs = {
|
||||
'crl': CrlId,
|
||||
'archive_cutoff': GeneralizedTime,
|
||||
'crl_reason': CRLReason,
|
||||
'invalidity_date': GeneralizedTime,
|
||||
'certificate_issuer': GeneralNames,
|
||||
'signed_certificate_timestamp_list': OctetString,
|
||||
}
|
||||
|
||||
|
||||
class SingleResponseExtensions(SequenceOf):
|
||||
_child_spec = SingleResponseExtension
|
||||
|
||||
|
||||
class SingleResponse(Sequence):
|
||||
_fields = [
|
||||
('cert_id', CertId),
|
||||
('cert_status', CertStatus),
|
||||
('this_update', GeneralizedTime),
|
||||
('next_update', GeneralizedTime, {'explicit': 0, 'optional': True}),
|
||||
('single_extensions', SingleResponseExtensions, {'explicit': 1, 'optional': True}),
|
||||
]
|
||||
|
||||
_processed_extensions = False
|
||||
_critical_extensions = None
|
||||
_crl_value = None
|
||||
_archive_cutoff_value = None
|
||||
_crl_reason_value = None
|
||||
_invalidity_date_value = None
|
||||
_certificate_issuer_value = None
|
||||
|
||||
def _set_extensions(self):
|
||||
"""
|
||||
Sets common named extensions to private attributes and creates a list
|
||||
of critical extensions
|
||||
"""
|
||||
|
||||
self._critical_extensions = set()
|
||||
|
||||
for extension in self['single_extensions']:
|
||||
name = extension['extn_id'].native
|
||||
attribute_name = '_%s_value' % name
|
||||
if hasattr(self, attribute_name):
|
||||
setattr(self, attribute_name, extension['extn_value'].parsed)
|
||||
if extension['critical'].native:
|
||||
self._critical_extensions.add(name)
|
||||
|
||||
self._processed_extensions = True
|
||||
|
||||
@property
|
||||
def critical_extensions(self):
|
||||
"""
|
||||
Returns a set of the names (or OID if not a known extension) of the
|
||||
extensions marked as critical
|
||||
|
||||
:return:
|
||||
A set of unicode strings
|
||||
"""
|
||||
|
||||
if not self._processed_extensions:
|
||||
self._set_extensions()
|
||||
return self._critical_extensions
|
||||
|
||||
@property
|
||||
def crl_value(self):
|
||||
"""
|
||||
This extension is used to locate the CRL that a certificate's revocation
|
||||
is contained within.
|
||||
|
||||
:return:
|
||||
None or a CrlId object
|
||||
"""
|
||||
|
||||
if self._processed_extensions is False:
|
||||
self._set_extensions()
|
||||
return self._crl_value
|
||||
|
||||
@property
|
||||
def archive_cutoff_value(self):
|
||||
"""
|
||||
This extension is used to indicate the date at which an archived
|
||||
(historical) certificate status entry will no longer be available.
|
||||
|
||||
:return:
|
||||
None or a GeneralizedTime object
|
||||
"""
|
||||
|
||||
if self._processed_extensions is False:
|
||||
self._set_extensions()
|
||||
return self._archive_cutoff_value
|
||||
|
||||
@property
|
||||
def crl_reason_value(self):
|
||||
"""
|
||||
This extension indicates the reason that a certificate was revoked.
|
||||
|
||||
:return:
|
||||
None or a CRLReason object
|
||||
"""
|
||||
|
||||
if self._processed_extensions is False:
|
||||
self._set_extensions()
|
||||
return self._crl_reason_value
|
||||
|
||||
@property
|
||||
def invalidity_date_value(self):
|
||||
"""
|
||||
This extension indicates the suspected date/time the private key was
|
||||
compromised or the certificate became invalid. This would usually be
|
||||
before the revocation date, which is when the CA processed the
|
||||
revocation.
|
||||
|
||||
:return:
|
||||
None or a GeneralizedTime object
|
||||
"""
|
||||
|
||||
if self._processed_extensions is False:
|
||||
self._set_extensions()
|
||||
return self._invalidity_date_value
|
||||
|
||||
@property
|
||||
def certificate_issuer_value(self):
|
||||
"""
|
||||
This extension indicates the issuer of the certificate in question.
|
||||
|
||||
:return:
|
||||
None or an x509.GeneralNames object
|
||||
"""
|
||||
|
||||
if self._processed_extensions is False:
|
||||
self._set_extensions()
|
||||
return self._certificate_issuer_value
|
||||
|
||||
|
||||
class Responses(SequenceOf):
|
||||
_child_spec = SingleResponse
|
||||
|
||||
|
||||
class ResponseDataExtensionId(ObjectIdentifier):
|
||||
_map = {
|
||||
'1.3.6.1.5.5.7.48.1.2': 'nonce',
|
||||
'1.3.6.1.5.5.7.48.1.9': 'extended_revoke',
|
||||
}
|
||||
|
||||
|
||||
class ResponseDataExtension(Sequence):
|
||||
_fields = [
|
||||
('extn_id', ResponseDataExtensionId),
|
||||
('critical', Boolean, {'default': False}),
|
||||
('extn_value', ParsableOctetString),
|
||||
]
|
||||
|
||||
_oid_pair = ('extn_id', 'extn_value')
|
||||
_oid_specs = {
|
||||
'nonce': OctetString,
|
||||
'extended_revoke': Null,
|
||||
}
|
||||
|
||||
|
||||
class ResponseDataExtensions(SequenceOf):
|
||||
_child_spec = ResponseDataExtension
|
||||
|
||||
|
||||
class ResponseData(Sequence):
|
||||
_fields = [
|
||||
('version', Version, {'explicit': 0, 'default': 'v1'}),
|
||||
('responder_id', ResponderId),
|
||||
('produced_at', GeneralizedTime),
|
||||
('responses', Responses),
|
||||
('response_extensions', ResponseDataExtensions, {'explicit': 1, 'optional': True}),
|
||||
]
|
||||
|
||||
|
||||
class BasicOCSPResponse(Sequence):
|
||||
_fields = [
|
||||
('tbs_response_data', ResponseData),
|
||||
('signature_algorithm', SignedDigestAlgorithm),
|
||||
('signature', OctetBitString),
|
||||
('certs', Certificates, {'explicit': 0, 'optional': True}),
|
||||
]
|
||||
|
||||
|
||||
class ResponseBytes(Sequence):
|
||||
_fields = [
|
||||
('response_type', ResponseType),
|
||||
('response', ParsableOctetString),
|
||||
]
|
||||
|
||||
_oid_pair = ('response_type', 'response')
|
||||
_oid_specs = {
|
||||
'basic_ocsp_response': BasicOCSPResponse,
|
||||
}
|
||||
|
||||
|
||||
class OCSPResponse(Sequence):
|
||||
_fields = [
|
||||
('response_status', OCSPResponseStatus),
|
||||
('response_bytes', ResponseBytes, {'explicit': 0, 'optional': True}),
|
||||
]
|
||||
|
||||
_processed_extensions = False
|
||||
_critical_extensions = None
|
||||
_nonce_value = None
|
||||
_extended_revoke_value = None
|
||||
|
||||
def _set_extensions(self):
|
||||
"""
|
||||
Sets common named extensions to private attributes and creates a list
|
||||
of critical extensions
|
||||
"""
|
||||
|
||||
self._critical_extensions = set()
|
||||
|
||||
for extension in self['response_bytes']['response'].parsed['tbs_response_data']['response_extensions']:
|
||||
name = extension['extn_id'].native
|
||||
attribute_name = '_%s_value' % name
|
||||
if hasattr(self, attribute_name):
|
||||
setattr(self, attribute_name, extension['extn_value'].parsed)
|
||||
if extension['critical'].native:
|
||||
self._critical_extensions.add(name)
|
||||
|
||||
self._processed_extensions = True
|
||||
|
||||
@property
|
||||
def critical_extensions(self):
|
||||
"""
|
||||
Returns a set of the names (or OID if not a known extension) of the
|
||||
extensions marked as critical
|
||||
|
||||
:return:
|
||||
A set of unicode strings
|
||||
"""
|
||||
|
||||
if not self._processed_extensions:
|
||||
self._set_extensions()
|
||||
return self._critical_extensions
|
||||
|
||||
@property
|
||||
def nonce_value(self):
|
||||
"""
|
||||
This extension is used to prevent replay attacks on the request/response
|
||||
exchange
|
||||
|
||||
:return:
|
||||
None or an OctetString object
|
||||
"""
|
||||
|
||||
if self._processed_extensions is False:
|
||||
self._set_extensions()
|
||||
return self._nonce_value
|
||||
|
||||
@property
|
||||
def extended_revoke_value(self):
|
||||
"""
|
||||
This extension is used to signal that the responder will return a
|
||||
"revoked" status for non-issued certificates.
|
||||
|
||||
:return:
|
||||
None or a Null object (if present)
|
||||
"""
|
||||
|
||||
if self._processed_extensions is False:
|
||||
self._set_extensions()
|
||||
return self._extended_revoke_value
|
||||
|
||||
@property
|
||||
def basic_ocsp_response(self):
|
||||
"""
|
||||
A shortcut into the BasicOCSPResponse sequence
|
||||
|
||||
:return:
|
||||
None or an asn1crypto.ocsp.BasicOCSPResponse object
|
||||
"""
|
||||
|
||||
return self['response_bytes']['response'].parsed
|
||||
|
||||
@property
|
||||
def response_data(self):
|
||||
"""
|
||||
A shortcut into the parsed, ResponseData sequence
|
||||
|
||||
:return:
|
||||
None or an asn1crypto.ocsp.ResponseData object
|
||||
"""
|
||||
|
||||
return self['response_bytes']['response'].parsed['tbs_response_data']
|
|
@ -0,0 +1,289 @@
|
|||
# coding: utf-8
|
||||
|
||||
"""
|
||||
Functions for parsing and dumping using the ASN.1 DER encoding. Exports the
|
||||
following items:
|
||||
|
||||
- emit()
|
||||
- parse()
|
||||
- peek()
|
||||
|
||||
Other type classes are defined that help compose the types listed above.
|
||||
"""
|
||||
|
||||
from __future__ import unicode_literals, division, absolute_import, print_function
|
||||
|
||||
import sys
|
||||
|
||||
from ._types import byte_cls, chr_cls, type_name
|
||||
from .util import int_from_bytes, int_to_bytes
|
||||
|
||||
_PY2 = sys.version_info <= (3,)
|
||||
_INSUFFICIENT_DATA_MESSAGE = 'Insufficient data - %s bytes requested but only %s available'
|
||||
|
||||
|
||||
def emit(class_, method, tag, contents):
|
||||
"""
|
||||
Constructs a byte string of an ASN.1 DER-encoded value
|
||||
|
||||
This is typically not useful. Instead, use one of the standard classes from
|
||||
asn1crypto.core, or construct a new class with specific fields, and call the
|
||||
.dump() method.
|
||||
|
||||
:param class_:
|
||||
An integer ASN.1 class value: 0 (universal), 1 (application),
|
||||
2 (context), 3 (private)
|
||||
|
||||
:param method:
|
||||
An integer ASN.1 method value: 0 (primitive), 1 (constructed)
|
||||
|
||||
:param tag:
|
||||
An integer ASN.1 tag value
|
||||
|
||||
:param contents:
|
||||
A byte string of the encoded byte contents
|
||||
|
||||
:return:
|
||||
A byte string of the ASN.1 DER value (header and contents)
|
||||
"""
|
||||
|
||||
if not isinstance(class_, int):
|
||||
raise TypeError('class_ must be an integer, not %s' % type_name(class_))
|
||||
|
||||
if class_ < 0 or class_ > 3:
|
||||
raise ValueError('class_ must be one of 0, 1, 2 or 3, not %s' % class_)
|
||||
|
||||
if not isinstance(method, int):
|
||||
raise TypeError('method must be an integer, not %s' % type_name(method))
|
||||
|
||||
if method < 0 or method > 1:
|
||||
raise ValueError('method must be 0 or 1, not %s' % method)
|
||||
|
||||
if not isinstance(tag, int):
|
||||
raise TypeError('tag must be an integer, not %s' % type_name(tag))
|
||||
|
||||
if tag < 0:
|
||||
raise ValueError('tag must be greater than zero, not %s' % tag)
|
||||
|
||||
if not isinstance(contents, byte_cls):
|
||||
raise TypeError('contents must be a byte string, not %s' % type_name(contents))
|
||||
|
||||
return _dump_header(class_, method, tag, contents) + contents
|
||||
|
||||
|
||||
def parse(contents, strict=False):
|
||||
"""
|
||||
Parses a byte string of ASN.1 BER/DER-encoded data.
|
||||
|
||||
This is typically not useful. Instead, use one of the standard classes from
|
||||
asn1crypto.core, or construct a new class with specific fields, and call the
|
||||
.load() class method.
|
||||
|
||||
:param contents:
|
||||
A byte string of BER/DER-encoded data
|
||||
|
||||
:param strict:
|
||||
A boolean indicating if trailing data should be forbidden - if so, a
|
||||
ValueError will be raised when trailing data exists
|
||||
|
||||
:raises:
|
||||
ValueError - when the contents do not contain an ASN.1 header or are truncated in some way
|
||||
TypeError - when contents is not a byte string
|
||||
|
||||
:return:
|
||||
A 6-element tuple:
|
||||
- 0: integer class (0 to 3)
|
||||
- 1: integer method
|
||||
- 2: integer tag
|
||||
- 3: byte string header
|
||||
- 4: byte string content
|
||||
- 5: byte string trailer
|
||||
"""
|
||||
|
||||
if not isinstance(contents, byte_cls):
|
||||
raise TypeError('contents must be a byte string, not %s' % type_name(contents))
|
||||
|
||||
contents_len = len(contents)
|
||||
info, consumed = _parse(contents, contents_len)
|
||||
if strict and consumed != contents_len:
|
||||
raise ValueError('Extra data - %d bytes of trailing data were provided' % (contents_len - consumed))
|
||||
return info
|
||||
|
||||
|
||||
def peek(contents):
|
||||
"""
|
||||
Parses a byte string of ASN.1 BER/DER-encoded data to find the length
|
||||
|
||||
This is typically used to look into an encoded value to see how long the
|
||||
next chunk of ASN.1-encoded data is. Primarily it is useful when a
|
||||
value is a concatenation of multiple values.
|
||||
|
||||
:param contents:
|
||||
A byte string of BER/DER-encoded data
|
||||
|
||||
:raises:
|
||||
ValueError - when the contents do not contain an ASN.1 header or are truncated in some way
|
||||
TypeError - when contents is not a byte string
|
||||
|
||||
:return:
|
||||
An integer with the number of bytes occupied by the ASN.1 value
|
||||
"""
|
||||
|
||||
if not isinstance(contents, byte_cls):
|
||||
raise TypeError('contents must be a byte string, not %s' % type_name(contents))
|
||||
|
||||
info, consumed = _parse(contents, len(contents))
|
||||
return consumed
|
||||
|
||||
|
||||
def _parse(encoded_data, data_len, pointer=0, lengths_only=False):
|
||||
"""
|
||||
Parses a byte string into component parts
|
||||
|
||||
:param encoded_data:
|
||||
A byte string that contains BER-encoded data
|
||||
|
||||
:param data_len:
|
||||
The integer length of the encoded data
|
||||
|
||||
:param pointer:
|
||||
The index in the byte string to parse from
|
||||
|
||||
:param lengths_only:
|
||||
A boolean to cause the call to return a 2-element tuple of the integer
|
||||
number of bytes in the header and the integer number of bytes in the
|
||||
contents. Internal use only.
|
||||
|
||||
:return:
|
||||
A 2-element tuple:
|
||||
- 0: A tuple of (class_, method, tag, header, content, trailer)
|
||||
- 1: An integer indicating how many bytes were consumed
|
||||
"""
|
||||
|
||||
if data_len < pointer + 2:
|
||||
raise ValueError(_INSUFFICIENT_DATA_MESSAGE % (2, data_len - pointer))
|
||||
|
||||
start = pointer
|
||||
first_octet = ord(encoded_data[pointer]) if _PY2 else encoded_data[pointer]
|
||||
pointer += 1
|
||||
|
||||
tag = first_octet & 31
|
||||
# Base 128 length using 8th bit as continuation indicator
|
||||
if tag == 31:
|
||||
tag = 0
|
||||
while True:
|
||||
num = ord(encoded_data[pointer]) if _PY2 else encoded_data[pointer]
|
||||
pointer += 1
|
||||
tag *= 128
|
||||
tag += num & 127
|
||||
if num >> 7 == 0:
|
||||
break
|
||||
|
||||
length_octet = ord(encoded_data[pointer]) if _PY2 else encoded_data[pointer]
|
||||
pointer += 1
|
||||
|
||||
if length_octet >> 7 == 0:
|
||||
if lengths_only:
|
||||
return (pointer, pointer + (length_octet & 127))
|
||||
contents_end = pointer + (length_octet & 127)
|
||||
|
||||
else:
|
||||
length_octets = length_octet & 127
|
||||
if length_octets:
|
||||
pointer += length_octets
|
||||
contents_end = pointer + int_from_bytes(encoded_data[pointer - length_octets:pointer], signed=False)
|
||||
if lengths_only:
|
||||
return (pointer, contents_end)
|
||||
|
||||
else:
|
||||
# To properly parse indefinite length values, we need to scan forward
|
||||
# parsing headers until we find a value with a length of zero. If we
|
||||
# just scanned looking for \x00\x00, nested indefinite length values
|
||||
# would not work.
|
||||
contents_end = pointer
|
||||
# Unfortunately we need to understand the contents of the data to
|
||||
# properly scan forward, which bleeds some representation info into
|
||||
# the parser. This condition handles the unused bits byte in
|
||||
# constructed bit strings.
|
||||
if tag == 3:
|
||||
contents_end += 1
|
||||
while contents_end < data_len:
|
||||
sub_header_end, contents_end = _parse(encoded_data, data_len, contents_end, lengths_only=True)
|
||||
if contents_end == sub_header_end and encoded_data[contents_end - 2:contents_end] == b'\x00\x00':
|
||||
break
|
||||
if lengths_only:
|
||||
return (pointer, contents_end)
|
||||
if contents_end > data_len:
|
||||
raise ValueError(_INSUFFICIENT_DATA_MESSAGE % (contents_end, data_len))
|
||||
return (
|
||||
(
|
||||
first_octet >> 6,
|
||||
(first_octet >> 5) & 1,
|
||||
tag,
|
||||
encoded_data[start:pointer],
|
||||
encoded_data[pointer:contents_end - 2],
|
||||
b'\x00\x00'
|
||||
),
|
||||
contents_end
|
||||
)
|
||||
|
||||
if contents_end > data_len:
|
||||
raise ValueError(_INSUFFICIENT_DATA_MESSAGE % (contents_end, data_len))
|
||||
return (
|
||||
(
|
||||
first_octet >> 6,
|
||||
(first_octet >> 5) & 1,
|
||||
tag,
|
||||
encoded_data[start:pointer],
|
||||
encoded_data[pointer:contents_end],
|
||||
b''
|
||||
),
|
||||
contents_end
|
||||
)
|
||||
|
||||
|
||||
def _dump_header(class_, method, tag, contents):
|
||||
"""
|
||||
Constructs the header bytes for an ASN.1 object
|
||||
|
||||
:param class_:
|
||||
An integer ASN.1 class value: 0 (universal), 1 (application),
|
||||
2 (context), 3 (private)
|
||||
|
||||
:param method:
|
||||
An integer ASN.1 method value: 0 (primitive), 1 (constructed)
|
||||
|
||||
:param tag:
|
||||
An integer ASN.1 tag value
|
||||
|
||||
:param contents:
|
||||
A byte string of the encoded byte contents
|
||||
|
||||
:return:
|
||||
A byte string of the ASN.1 DER header
|
||||
"""
|
||||
|
||||
header = b''
|
||||
|
||||
id_num = 0
|
||||
id_num |= class_ << 6
|
||||
id_num |= method << 5
|
||||
|
||||
if tag >= 31:
|
||||
header += chr_cls(id_num | 31)
|
||||
while tag > 0:
|
||||
continuation_bit = 0x80 if tag > 0x7F else 0
|
||||
header += chr_cls(continuation_bit | (tag & 0x7F))
|
||||
tag = tag >> 7
|
||||
else:
|
||||
header += chr_cls(id_num | tag)
|
||||
|
||||
length = len(contents)
|
||||
if length <= 127:
|
||||
header += chr_cls(length)
|
||||
else:
|
||||
length_bytes = int_to_bytes(length)
|
||||
header += chr_cls(0x80 | len(length_bytes))
|
||||
header += length_bytes
|
||||
|
||||
return header
|
|
@ -0,0 +1,84 @@
|
|||
# coding: utf-8
|
||||
|
||||
"""
|
||||
ASN.1 type classes for PDF signature structures. Adds extra oid mapping and
|
||||
value parsing to asn1crypto.x509.Extension() and asn1crypto.xms.CMSAttribute().
|
||||
"""
|
||||
|
||||
from __future__ import unicode_literals, division, absolute_import, print_function
|
||||
|
||||
from .cms import CMSAttributeType, CMSAttribute
|
||||
from .core import (
|
||||
Boolean,
|
||||
Integer,
|
||||
Null,
|
||||
ObjectIdentifier,
|
||||
OctetString,
|
||||
Sequence,
|
||||
SequenceOf,
|
||||
SetOf,
|
||||
)
|
||||
from .crl import CertificateList
|
||||
from .ocsp import OCSPResponse
|
||||
from .x509 import (
|
||||
Extension,
|
||||
ExtensionId,
|
||||
GeneralName,
|
||||
KeyPurposeId,
|
||||
)
|
||||
|
||||
|
||||
class AdobeArchiveRevInfo(Sequence):
|
||||
_fields = [
|
||||
('version', Integer)
|
||||
]
|
||||
|
||||
|
||||
class AdobeTimestamp(Sequence):
|
||||
_fields = [
|
||||
('version', Integer),
|
||||
('location', GeneralName),
|
||||
('requires_auth', Boolean, {'optional': True, 'default': False}),
|
||||
]
|
||||
|
||||
|
||||
class OtherRevInfo(Sequence):
|
||||
_fields = [
|
||||
('type', ObjectIdentifier),
|
||||
('value', OctetString),
|
||||
]
|
||||
|
||||
|
||||
class SequenceOfCertificateList(SequenceOf):
|
||||
_child_spec = CertificateList
|
||||
|
||||
|
||||
class SequenceOfOCSPResponse(SequenceOf):
|
||||
_child_spec = OCSPResponse
|
||||
|
||||
|
||||
class SequenceOfOtherRevInfo(SequenceOf):
|
||||
_child_spec = OtherRevInfo
|
||||
|
||||
|
||||
class RevocationInfoArchival(Sequence):
|
||||
_fields = [
|
||||
('crl', SequenceOfCertificateList, {'explicit': 0, 'optional': True}),
|
||||
('ocsp', SequenceOfOCSPResponse, {'explicit': 1, 'optional': True}),
|
||||
('other_rev_info', SequenceOfOtherRevInfo, {'explicit': 2, 'optional': True}),
|
||||
]
|
||||
|
||||
|
||||
class SetOfRevocationInfoArchival(SetOf):
|
||||
_child_spec = RevocationInfoArchival
|
||||
|
||||
|
||||
ExtensionId._map['1.2.840.113583.1.1.9.2'] = 'adobe_archive_rev_info'
|
||||
ExtensionId._map['1.2.840.113583.1.1.9.1'] = 'adobe_timestamp'
|
||||
ExtensionId._map['1.2.840.113583.1.1.10'] = 'adobe_ppklite_credential'
|
||||
Extension._oid_specs['adobe_archive_rev_info'] = AdobeArchiveRevInfo
|
||||
Extension._oid_specs['adobe_timestamp'] = AdobeTimestamp
|
||||
Extension._oid_specs['adobe_ppklite_credential'] = Null
|
||||
KeyPurposeId._map['1.2.840.113583.1.1.5'] = 'pdf_signing'
|
||||
CMSAttributeType._map['1.2.840.113583.1.1.8'] = 'adobe_revocation_info_archival'
|
||||
CMSAttribute._oid_specs['adobe_revocation_info_archival'] = SetOfRevocationInfoArchival
|
|
@ -0,0 +1,222 @@
|
|||
# coding: utf-8
|
||||
|
||||
"""
|
||||
Encoding DER to PEM and decoding PEM to DER. Exports the following items:
|
||||
|
||||
- armor()
|
||||
- detect()
|
||||
- unarmor()
|
||||
|
||||
"""
|
||||
|
||||
from __future__ import unicode_literals, division, absolute_import, print_function
|
||||
|
||||
import base64
|
||||
import re
|
||||
import sys
|
||||
|
||||
from ._errors import unwrap
|
||||
from ._types import type_name as _type_name, str_cls, byte_cls
|
||||
|
||||
if sys.version_info < (3,):
|
||||
from cStringIO import StringIO as BytesIO
|
||||
else:
|
||||
from io import BytesIO
|
||||
|
||||
|
||||
def detect(byte_string):
|
||||
"""
|
||||
Detect if a byte string seems to contain a PEM-encoded block
|
||||
|
||||
:param byte_string:
|
||||
A byte string to look through
|
||||
|
||||
:return:
|
||||
A boolean, indicating if a PEM-encoded block is contained in the byte
|
||||
string
|
||||
"""
|
||||
|
||||
if not isinstance(byte_string, byte_cls):
|
||||
raise TypeError(unwrap(
|
||||
'''
|
||||
byte_string must be a byte string, not %s
|
||||
''',
|
||||
_type_name(byte_string)
|
||||
))
|
||||
|
||||
return byte_string.find(b'-----BEGIN') != -1 or byte_string.find(b'---- BEGIN') != -1
|
||||
|
||||
|
||||
def armor(type_name, der_bytes, headers=None):
|
||||
"""
|
||||
Armors a DER-encoded byte string in PEM
|
||||
|
||||
:param type_name:
|
||||
A unicode string that will be capitalized and placed in the header
|
||||
and footer of the block. E.g. "CERTIFICATE", "PRIVATE KEY", etc. This
|
||||
will appear as "-----BEGIN CERTIFICATE-----" and
|
||||
"-----END CERTIFICATE-----".
|
||||
|
||||
:param der_bytes:
|
||||
A byte string to be armored
|
||||
|
||||
:param headers:
|
||||
An OrderedDict of the header lines to write after the BEGIN line
|
||||
|
||||
:return:
|
||||
A byte string of the PEM block
|
||||
"""
|
||||
|
||||
if not isinstance(der_bytes, byte_cls):
|
||||
raise TypeError(unwrap(
|
||||
'''
|
||||
der_bytes must be a byte string, not %s
|
||||
''' % _type_name(der_bytes)
|
||||
))
|
||||
|
||||
if not isinstance(type_name, str_cls):
|
||||
raise TypeError(unwrap(
|
||||
'''
|
||||
type_name must be a unicode string, not %s
|
||||
''',
|
||||
_type_name(type_name)
|
||||
))
|
||||
|
||||
type_name = type_name.upper().encode('ascii')
|
||||
|
||||
output = BytesIO()
|
||||
output.write(b'-----BEGIN ')
|
||||
output.write(type_name)
|
||||
output.write(b'-----\n')
|
||||
if headers:
|
||||
for key in headers:
|
||||
output.write(key.encode('ascii'))
|
||||
output.write(b': ')
|
||||
output.write(headers[key].encode('ascii'))
|
||||
output.write(b'\n')
|
||||
output.write(b'\n')
|
||||
b64_bytes = base64.b64encode(der_bytes)
|
||||
b64_len = len(b64_bytes)
|
||||
i = 0
|
||||
while i < b64_len:
|
||||
output.write(b64_bytes[i:i + 64])
|
||||
output.write(b'\n')
|
||||
i += 64
|
||||
output.write(b'-----END ')
|
||||
output.write(type_name)
|
||||
output.write(b'-----\n')
|
||||
|
||||
return output.getvalue()
|
||||
|
||||
|
||||
def _unarmor(pem_bytes):
|
||||
"""
|
||||
Convert a PEM-encoded byte string into one or more DER-encoded byte strings
|
||||
|
||||
:param pem_bytes:
|
||||
A byte string of the PEM-encoded data
|
||||
|
||||
:raises:
|
||||
ValueError - when the pem_bytes do not appear to be PEM-encoded bytes
|
||||
|
||||
:return:
|
||||
A generator of 3-element tuples in the format: (object_type, headers,
|
||||
der_bytes). The object_type is a unicode string of what is between
|
||||
"-----BEGIN " and "-----". Examples include: "CERTIFICATE",
|
||||
"PUBLIC KEY", "PRIVATE KEY". The headers is a dict containing any lines
|
||||
in the form "Name: Value" that are right after the begin line.
|
||||
"""
|
||||
|
||||
if not isinstance(pem_bytes, byte_cls):
|
||||
raise TypeError(unwrap(
|
||||
'''
|
||||
pem_bytes must be a byte string, not %s
|
||||
''',
|
||||
_type_name(pem_bytes)
|
||||
))
|
||||
|
||||
# Valid states include: "trash", "headers", "body"
|
||||
state = 'trash'
|
||||
headers = {}
|
||||
base64_data = b''
|
||||
object_type = None
|
||||
|
||||
found_start = False
|
||||
found_end = False
|
||||
|
||||
for line in pem_bytes.splitlines(False):
|
||||
if line == b'':
|
||||
continue
|
||||
|
||||
if state == "trash":
|
||||
# Look for a starting line since some CA cert bundle show the cert
|
||||
# into in a parsed format above each PEM block
|
||||
type_name_match = re.match(b'^(?:---- |-----)BEGIN ([A-Z0-9 ]+)(?: ----|-----)', line)
|
||||
if not type_name_match:
|
||||
continue
|
||||
object_type = type_name_match.group(1).decode('ascii')
|
||||
|
||||
found_start = True
|
||||
state = 'headers'
|
||||
continue
|
||||
|
||||
if state == 'headers':
|
||||
if line.find(b':') == -1:
|
||||
state = 'body'
|
||||
else:
|
||||
decoded_line = line.decode('ascii')
|
||||
name, value = decoded_line.split(':', 1)
|
||||
headers[name] = value.strip()
|
||||
continue
|
||||
|
||||
if state == 'body':
|
||||
if line[0:5] in (b'-----', b'---- '):
|
||||
der_bytes = base64.b64decode(base64_data)
|
||||
|
||||
yield (object_type, headers, der_bytes)
|
||||
|
||||
state = 'trash'
|
||||
headers = {}
|
||||
base64_data = b''
|
||||
object_type = None
|
||||
found_end = True
|
||||
continue
|
||||
|
||||
base64_data += line
|
||||
|
||||
if not found_start or not found_end:
|
||||
raise ValueError(unwrap(
|
||||
'''
|
||||
pem_bytes does not appear to contain PEM-encoded data - no
|
||||
BEGIN/END combination found
|
||||
'''
|
||||
))
|
||||
|
||||
|
||||
def unarmor(pem_bytes, multiple=False):
|
||||
"""
|
||||
Convert a PEM-encoded byte string into a DER-encoded byte string
|
||||
|
||||
:param pem_bytes:
|
||||
A byte string of the PEM-encoded data
|
||||
|
||||
:param multiple:
|
||||
If True, function will return a generator
|
||||
|
||||
:raises:
|
||||
ValueError - when the pem_bytes do not appear to be PEM-encoded bytes
|
||||
|
||||
:return:
|
||||
A 3-element tuple (object_name, headers, der_bytes). The object_name is
|
||||
a unicode string of what is between "-----BEGIN " and "-----". Examples
|
||||
include: "CERTIFICATE", "PUBLIC KEY", "PRIVATE KEY". The headers is a
|
||||
dict containing any lines in the form "Name: Value" that are right
|
||||
after the begin line.
|
||||
"""
|
||||
|
||||
generator = _unarmor(pem_bytes)
|
||||
|
||||
if not multiple:
|
||||
return next(generator)
|
||||
|
||||
return generator
|
|
@ -0,0 +1,193 @@
|
|||
# coding: utf-8
|
||||
|
||||
"""
|
||||
ASN.1 type classes for PKCS#12 files. Exports the following items:
|
||||
|
||||
- CertBag()
|
||||
- CrlBag()
|
||||
- Pfx()
|
||||
- SafeBag()
|
||||
- SecretBag()
|
||||
|
||||
Other type classes are defined that help compose the types listed above.
|
||||
"""
|
||||
|
||||
from __future__ import unicode_literals, division, absolute_import, print_function
|
||||
|
||||
from .algos import DigestInfo
|
||||
from .cms import ContentInfo, SignedData
|
||||
from .core import (
|
||||
Any,
|
||||
BMPString,
|
||||
Integer,
|
||||
ObjectIdentifier,
|
||||
OctetString,
|
||||
ParsableOctetString,
|
||||
Sequence,
|
||||
SequenceOf,
|
||||
SetOf,
|
||||
)
|
||||
from .keys import PrivateKeyInfo, EncryptedPrivateKeyInfo
|
||||
from .x509 import Certificate, KeyPurposeId
|
||||
|
||||
|
||||
# The structures in this file are taken from https://tools.ietf.org/html/rfc7292
|
||||
|
||||
class MacData(Sequence):
|
||||
_fields = [
|
||||
('mac', DigestInfo),
|
||||
('mac_salt', OctetString),
|
||||
('iterations', Integer, {'default': 1}),
|
||||
]
|
||||
|
||||
|
||||
class Version(Integer):
|
||||
_map = {
|
||||
3: 'v3'
|
||||
}
|
||||
|
||||
|
||||
class AttributeType(ObjectIdentifier):
|
||||
_map = {
|
||||
# https://tools.ietf.org/html/rfc2985#page-18
|
||||
'1.2.840.113549.1.9.20': 'friendly_name',
|
||||
'1.2.840.113549.1.9.21': 'local_key_id',
|
||||
# https://support.microsoft.com/en-us/kb/287547
|
||||
'1.3.6.1.4.1.311.17.1': 'microsoft_local_machine_keyset',
|
||||
# https://github.com/frohoff/jdk8u-dev-jdk/blob/master/src/share/classes/sun/security/pkcs12/PKCS12KeyStore.java
|
||||
# this is a set of OIDs, representing key usage, the usual value is a SET of one element OID 2.5.29.37.0
|
||||
'2.16.840.1.113894.746875.1.1': 'trusted_key_usage',
|
||||
}
|
||||
|
||||
|
||||
class SetOfAny(SetOf):
|
||||
_child_spec = Any
|
||||
|
||||
|
||||
class SetOfBMPString(SetOf):
|
||||
_child_spec = BMPString
|
||||
|
||||
|
||||
class SetOfOctetString(SetOf):
|
||||
_child_spec = OctetString
|
||||
|
||||
|
||||
class SetOfKeyPurposeId(SetOf):
|
||||
_child_spec = KeyPurposeId
|
||||
|
||||
|
||||
class Attribute(Sequence):
|
||||
_fields = [
|
||||
('type', AttributeType),
|
||||
('values', None),
|
||||
]
|
||||
|
||||
_oid_specs = {
|
||||
'friendly_name': SetOfBMPString,
|
||||
'local_key_id': SetOfOctetString,
|
||||
'microsoft_csp_name': SetOfBMPString,
|
||||
'trusted_key_usage': SetOfKeyPurposeId,
|
||||
}
|
||||
|
||||
def _values_spec(self):
|
||||
return self._oid_specs.get(self['type'].native, SetOfAny)
|
||||
|
||||
_spec_callbacks = {
|
||||
'values': _values_spec
|
||||
}
|
||||
|
||||
|
||||
class Attributes(SetOf):
|
||||
_child_spec = Attribute
|
||||
|
||||
|
||||
class Pfx(Sequence):
|
||||
_fields = [
|
||||
('version', Version),
|
||||
('auth_safe', ContentInfo),
|
||||
('mac_data', MacData, {'optional': True})
|
||||
]
|
||||
|
||||
_authenticated_safe = None
|
||||
|
||||
@property
|
||||
def authenticated_safe(self):
|
||||
if self._authenticated_safe is None:
|
||||
content = self['auth_safe']['content']
|
||||
if isinstance(content, SignedData):
|
||||
content = content['content_info']['content']
|
||||
self._authenticated_safe = AuthenticatedSafe.load(content.native)
|
||||
return self._authenticated_safe
|
||||
|
||||
|
||||
class AuthenticatedSafe(SequenceOf):
|
||||
_child_spec = ContentInfo
|
||||
|
||||
|
||||
class BagId(ObjectIdentifier):
|
||||
_map = {
|
||||
'1.2.840.113549.1.12.10.1.1': 'key_bag',
|
||||
'1.2.840.113549.1.12.10.1.2': 'pkcs8_shrouded_key_bag',
|
||||
'1.2.840.113549.1.12.10.1.3': 'cert_bag',
|
||||
'1.2.840.113549.1.12.10.1.4': 'crl_bag',
|
||||
'1.2.840.113549.1.12.10.1.5': 'secret_bag',
|
||||
'1.2.840.113549.1.12.10.1.6': 'safe_contents',
|
||||
}
|
||||
|
||||
|
||||
class CertId(ObjectIdentifier):
|
||||
_map = {
|
||||
'1.2.840.113549.1.9.22.1': 'x509',
|
||||
'1.2.840.113549.1.9.22.2': 'sdsi',
|
||||
}
|
||||
|
||||
|
||||
class CertBag(Sequence):
|
||||
_fields = [
|
||||
('cert_id', CertId),
|
||||
('cert_value', ParsableOctetString, {'explicit': 0}),
|
||||
]
|
||||
|
||||
_oid_pair = ('cert_id', 'cert_value')
|
||||
_oid_specs = {
|
||||
'x509': Certificate,
|
||||
}
|
||||
|
||||
|
||||
class CrlBag(Sequence):
|
||||
_fields = [
|
||||
('crl_id', ObjectIdentifier),
|
||||
('crl_value', OctetString, {'explicit': 0}),
|
||||
]
|
||||
|
||||
|
||||
class SecretBag(Sequence):
|
||||
_fields = [
|
||||
('secret_type_id', ObjectIdentifier),
|
||||
('secret_value', OctetString, {'explicit': 0}),
|
||||
]
|
||||
|
||||
|
||||
class SafeContents(SequenceOf):
|
||||
pass
|
||||
|
||||
|
||||
class SafeBag(Sequence):
|
||||
_fields = [
|
||||
('bag_id', BagId),
|
||||
('bag_value', Any, {'explicit': 0}),
|
||||
('bag_attributes', Attributes, {'optional': True}),
|
||||
]
|
||||
|
||||
_oid_pair = ('bag_id', 'bag_value')
|
||||
_oid_specs = {
|
||||
'key_bag': PrivateKeyInfo,
|
||||
'pkcs8_shrouded_key_bag': EncryptedPrivateKeyInfo,
|
||||
'cert_bag': CertBag,
|
||||
'crl_bag': CrlBag,
|
||||
'secret_bag': SecretBag,
|
||||
'safe_contents': SafeContents
|
||||
}
|
||||
|
||||
|
||||
SafeContents._child_spec = SafeBag
|
|
@ -0,0 +1,310 @@
|
|||
# coding: utf-8
|
||||
|
||||
"""
|
||||
ASN.1 type classes for the time stamp protocol (TSP). Exports the following
|
||||
items:
|
||||
|
||||
- TimeStampReq()
|
||||
- TimeStampResp()
|
||||
|
||||
Also adds TimeStampedData() support to asn1crypto.cms.ContentInfo(),
|
||||
TimeStampedData() and TSTInfo() support to
|
||||
asn1crypto.cms.EncapsulatedContentInfo() and some oids and value parsers to
|
||||
asn1crypto.cms.CMSAttribute().
|
||||
|
||||
Other type classes are defined that help compose the types listed above.
|
||||
"""
|
||||
|
||||
from __future__ import unicode_literals, division, absolute_import, print_function
|
||||
|
||||
from .algos import DigestAlgorithm
|
||||
from .cms import (
|
||||
CMSAttribute,
|
||||
CMSAttributeType,
|
||||
ContentInfo,
|
||||
ContentType,
|
||||
EncapsulatedContentInfo,
|
||||
)
|
||||
from .core import (
|
||||
Any,
|
||||
BitString,
|
||||
Boolean,
|
||||
Choice,
|
||||
GeneralizedTime,
|
||||
IA5String,
|
||||
Integer,
|
||||
ObjectIdentifier,
|
||||
OctetString,
|
||||
Sequence,
|
||||
SequenceOf,
|
||||
SetOf,
|
||||
UTF8String,
|
||||
)
|
||||
from .crl import CertificateList
|
||||
from .x509 import (
|
||||
Attributes,
|
||||
CertificatePolicies,
|
||||
GeneralName,
|
||||
GeneralNames,
|
||||
)
|
||||
|
||||
|
||||
# The structures in this file are based on https://tools.ietf.org/html/rfc3161,
|
||||
# https://tools.ietf.org/html/rfc4998, https://tools.ietf.org/html/rfc5544,
|
||||
# https://tools.ietf.org/html/rfc5035, https://tools.ietf.org/html/rfc2634
|
||||
|
||||
class Version(Integer):
|
||||
_map = {
|
||||
0: 'v0',
|
||||
1: 'v1',
|
||||
2: 'v2',
|
||||
3: 'v3',
|
||||
4: 'v4',
|
||||
5: 'v5',
|
||||
}
|
||||
|
||||
|
||||
class MessageImprint(Sequence):
|
||||
_fields = [
|
||||
('hash_algorithm', DigestAlgorithm),
|
||||
('hashed_message', OctetString),
|
||||
]
|
||||
|
||||
|
||||
class Accuracy(Sequence):
|
||||
_fields = [
|
||||
('seconds', Integer, {'optional': True}),
|
||||
('millis', Integer, {'implicit': 0, 'optional': True}),
|
||||
('micros', Integer, {'implicit': 1, 'optional': True}),
|
||||
]
|
||||
|
||||
|
||||
class Extension(Sequence):
|
||||
_fields = [
|
||||
('extn_id', ObjectIdentifier),
|
||||
('critical', Boolean, {'default': False}),
|
||||
('extn_value', OctetString),
|
||||
]
|
||||
|
||||
|
||||
class Extensions(SequenceOf):
|
||||
_child_spec = Extension
|
||||
|
||||
|
||||
class TSTInfo(Sequence):
|
||||
_fields = [
|
||||
('version', Version),
|
||||
('policy', ObjectIdentifier),
|
||||
('message_imprint', MessageImprint),
|
||||
('serial_number', Integer),
|
||||
('gen_time', GeneralizedTime),
|
||||
('accuracy', Accuracy, {'optional': True}),
|
||||
('ordering', Boolean, {'default': False}),
|
||||
('nonce', Integer, {'optional': True}),
|
||||
('tsa', GeneralName, {'explicit': 0, 'optional': True}),
|
||||
('extensions', Extensions, {'implicit': 1, 'optional': True}),
|
||||
]
|
||||
|
||||
|
||||
class TimeStampReq(Sequence):
|
||||
_fields = [
|
||||
('version', Version),
|
||||
('message_imprint', MessageImprint),
|
||||
('req_policy', ObjectIdentifier, {'optional': True}),
|
||||
('nonce', Integer, {'optional': True}),
|
||||
('cert_req', Boolean, {'default': False}),
|
||||
('extensions', Extensions, {'implicit': 0, 'optional': True}),
|
||||
]
|
||||
|
||||
|
||||
class PKIStatus(Integer):
|
||||
_map = {
|
||||
0: 'granted',
|
||||
1: 'granted_with_mods',
|
||||
2: 'rejection',
|
||||
3: 'waiting',
|
||||
4: 'revocation_warning',
|
||||
5: 'revocation_notification',
|
||||
}
|
||||
|
||||
|
||||
class PKIFreeText(SequenceOf):
|
||||
_child_spec = UTF8String
|
||||
|
||||
|
||||
class PKIFailureInfo(BitString):
|
||||
_map = {
|
||||
0: 'bad_alg',
|
||||
2: 'bad_request',
|
||||
5: 'bad_data_format',
|
||||
14: 'time_not_available',
|
||||
15: 'unaccepted_policy',
|
||||
16: 'unaccepted_extensions',
|
||||
17: 'add_info_not_available',
|
||||
25: 'system_failure',
|
||||
}
|
||||
|
||||
|
||||
class PKIStatusInfo(Sequence):
|
||||
_fields = [
|
||||
('status', PKIStatus),
|
||||
('status_string', PKIFreeText, {'optional': True}),
|
||||
('fail_info', PKIFailureInfo, {'optional': True}),
|
||||
]
|
||||
|
||||
|
||||
class TimeStampResp(Sequence):
|
||||
_fields = [
|
||||
('status', PKIStatusInfo),
|
||||
('time_stamp_token', ContentInfo),
|
||||
]
|
||||
|
||||
|
||||
class MetaData(Sequence):
|
||||
_fields = [
|
||||
('hash_protected', Boolean),
|
||||
('file_name', UTF8String, {'optional': True}),
|
||||
('media_type', IA5String, {'optional': True}),
|
||||
('other_meta_data', Attributes, {'optional': True}),
|
||||
]
|
||||
|
||||
|
||||
class TimeStampAndCRL(SequenceOf):
|
||||
_fields = [
|
||||
('time_stamp', EncapsulatedContentInfo),
|
||||
('crl', CertificateList, {'optional': True}),
|
||||
]
|
||||
|
||||
|
||||
class TimeStampTokenEvidence(SequenceOf):
|
||||
_child_spec = TimeStampAndCRL
|
||||
|
||||
|
||||
class DigestAlgorithms(SequenceOf):
|
||||
_child_spec = DigestAlgorithm
|
||||
|
||||
|
||||
class EncryptionInfo(Sequence):
|
||||
_fields = [
|
||||
('encryption_info_type', ObjectIdentifier),
|
||||
('encryption_info_value', Any),
|
||||
]
|
||||
|
||||
|
||||
class PartialHashtree(SequenceOf):
|
||||
_child_spec = OctetString
|
||||
|
||||
|
||||
class PartialHashtrees(SequenceOf):
|
||||
_child_spec = PartialHashtree
|
||||
|
||||
|
||||
class ArchiveTimeStamp(Sequence):
|
||||
_fields = [
|
||||
('digest_algorithm', DigestAlgorithm, {'implicit': 0, 'optional': True}),
|
||||
('attributes', Attributes, {'implicit': 1, 'optional': True}),
|
||||
('reduced_hashtree', PartialHashtrees, {'implicit': 2, 'optional': True}),
|
||||
('time_stamp', ContentInfo),
|
||||
]
|
||||
|
||||
|
||||
class ArchiveTimeStampSequence(SequenceOf):
|
||||
_child_spec = ArchiveTimeStamp
|
||||
|
||||
|
||||
class EvidenceRecord(Sequence):
|
||||
_fields = [
|
||||
('version', Version),
|
||||
('digest_algorithms', DigestAlgorithms),
|
||||
('crypto_infos', Attributes, {'implicit': 0, 'optional': True}),
|
||||
('encryption_info', EncryptionInfo, {'implicit': 1, 'optional': True}),
|
||||
('archive_time_stamp_sequence', ArchiveTimeStampSequence),
|
||||
]
|
||||
|
||||
|
||||
class OtherEvidence(Sequence):
|
||||
_fields = [
|
||||
('oe_type', ObjectIdentifier),
|
||||
('oe_value', Any),
|
||||
]
|
||||
|
||||
|
||||
class Evidence(Choice):
|
||||
_alternatives = [
|
||||
('tst_evidence', TimeStampTokenEvidence, {'implicit': 0}),
|
||||
('ers_evidence', EvidenceRecord, {'implicit': 1}),
|
||||
('other_evidence', OtherEvidence, {'implicit': 2}),
|
||||
]
|
||||
|
||||
|
||||
class TimeStampedData(Sequence):
|
||||
_fields = [
|
||||
('version', Version),
|
||||
('data_uri', IA5String, {'optional': True}),
|
||||
('meta_data', MetaData, {'optional': True}),
|
||||
('content', OctetString, {'optional': True}),
|
||||
('temporal_evidence', Evidence),
|
||||
]
|
||||
|
||||
|
||||
class IssuerSerial(Sequence):
|
||||
_fields = [
|
||||
('issuer', GeneralNames),
|
||||
('serial_number', Integer),
|
||||
]
|
||||
|
||||
|
||||
class ESSCertID(Sequence):
|
||||
_fields = [
|
||||
('cert_hash', OctetString),
|
||||
('issuer_serial', IssuerSerial, {'optional': True}),
|
||||
]
|
||||
|
||||
|
||||
class ESSCertIDs(SequenceOf):
|
||||
_child_spec = ESSCertID
|
||||
|
||||
|
||||
class SigningCertificate(Sequence):
|
||||
_fields = [
|
||||
('certs', ESSCertIDs),
|
||||
('policies', CertificatePolicies, {'optional': True}),
|
||||
]
|
||||
|
||||
|
||||
class SetOfSigningCertificates(SetOf):
|
||||
_child_spec = SigningCertificate
|
||||
|
||||
|
||||
class ESSCertIDv2(Sequence):
|
||||
_fields = [
|
||||
('hash_algorithm', DigestAlgorithm, {'default': {'algorithm': 'sha256'}}),
|
||||
('cert_hash', OctetString),
|
||||
('issuer_serial', IssuerSerial, {'optional': True}),
|
||||
]
|
||||
|
||||
|
||||
class ESSCertIDv2s(SequenceOf):
|
||||
_child_spec = ESSCertIDv2
|
||||
|
||||
|
||||
class SigningCertificateV2(Sequence):
|
||||
_fields = [
|
||||
('certs', ESSCertIDv2s),
|
||||
('policies', CertificatePolicies, {'optional': True}),
|
||||
]
|
||||
|
||||
|
||||
class SetOfSigningCertificatesV2(SetOf):
|
||||
_child_spec = SigningCertificateV2
|
||||
|
||||
|
||||
EncapsulatedContentInfo._oid_specs['tst_info'] = TSTInfo
|
||||
EncapsulatedContentInfo._oid_specs['timestamped_data'] = TimeStampedData
|
||||
ContentInfo._oid_specs['timestamped_data'] = TimeStampedData
|
||||
ContentType._map['1.2.840.113549.1.9.16.1.4'] = 'tst_info'
|
||||
ContentType._map['1.2.840.113549.1.9.16.1.31'] = 'timestamped_data'
|
||||
CMSAttributeType._map['1.2.840.113549.1.9.16.2.12'] = 'signing_certificate'
|
||||
CMSAttribute._oid_specs['signing_certificate'] = SetOfSigningCertificates
|
||||
CMSAttributeType._map['1.2.840.113549.1.9.16.2.47'] = 'signing_certificate_v2'
|
||||
CMSAttribute._oid_specs['signing_certificate_v2'] = SetOfSigningCertificatesV2
|
|
@ -0,0 +1,712 @@
|
|||
# coding: utf-8
|
||||
|
||||
"""
|
||||
Miscellaneous data helpers, including functions for converting integers to and
|
||||
from bytes and UTC timezone. Exports the following items:
|
||||
|
||||
- OrderedDict()
|
||||
- int_from_bytes()
|
||||
- int_to_bytes()
|
||||
- timezone.utc
|
||||
- inet_ntop()
|
||||
- inet_pton()
|
||||
- uri_to_iri()
|
||||
- iri_to_uri()
|
||||
"""
|
||||
|
||||
from __future__ import unicode_literals, division, absolute_import, print_function
|
||||
|
||||
import math
|
||||
import sys
|
||||
from datetime import datetime, date, time
|
||||
|
||||
from ._errors import unwrap
|
||||
from ._iri import iri_to_uri, uri_to_iri # noqa
|
||||
from ._ordereddict import OrderedDict # noqa
|
||||
from ._types import type_name
|
||||
|
||||
if sys.platform == 'win32':
|
||||
from ._inet import inet_ntop, inet_pton
|
||||
else:
|
||||
from socket import inet_ntop, inet_pton # noqa
|
||||
|
||||
|
||||
# Python 2
|
||||
if sys.version_info <= (3,):
|
||||
|
||||
from datetime import timedelta, tzinfo
|
||||
|
||||
py2 = True
|
||||
|
||||
def int_to_bytes(value, signed=False, width=None):
|
||||
"""
|
||||
Converts an integer to a byte string
|
||||
|
||||
:param value:
|
||||
The integer to convert
|
||||
|
||||
:param signed:
|
||||
If the byte string should be encoded using two's complement
|
||||
|
||||
:param width:
|
||||
None == auto, otherwise an integer of the byte width for the return
|
||||
value
|
||||
|
||||
:return:
|
||||
A byte string
|
||||
"""
|
||||
|
||||
# Handle negatives in two's complement
|
||||
is_neg = False
|
||||
if signed and value < 0:
|
||||
is_neg = True
|
||||
bits = int(math.ceil(len('%x' % abs(value)) / 2.0) * 8)
|
||||
value = (value + (1 << bits)) % (1 << bits)
|
||||
|
||||
hex_str = '%x' % value
|
||||
if len(hex_str) & 1:
|
||||
hex_str = '0' + hex_str
|
||||
|
||||
output = hex_str.decode('hex')
|
||||
|
||||
if signed and not is_neg and ord(output[0:1]) & 0x80:
|
||||
output = b'\x00' + output
|
||||
|
||||
if width is not None:
|
||||
if is_neg:
|
||||
pad_char = b'\xFF'
|
||||
else:
|
||||
pad_char = b'\x00'
|
||||
output = (pad_char * (width - len(output))) + output
|
||||
elif is_neg and ord(output[0:1]) & 0x80 == 0:
|
||||
output = b'\xFF' + output
|
||||
|
||||
return output
|
||||
|
||||
def int_from_bytes(value, signed=False):
|
||||
"""
|
||||
Converts a byte string to an integer
|
||||
|
||||
:param value:
|
||||
The byte string to convert
|
||||
|
||||
:param signed:
|
||||
If the byte string should be interpreted using two's complement
|
||||
|
||||
:return:
|
||||
An integer
|
||||
"""
|
||||
|
||||
if value == b'':
|
||||
return 0
|
||||
|
||||
num = long(value.encode("hex"), 16) # noqa
|
||||
|
||||
if not signed:
|
||||
return num
|
||||
|
||||
# Check for sign bit and handle two's complement
|
||||
if ord(value[0:1]) & 0x80:
|
||||
bit_len = len(value) * 8
|
||||
return num - (1 << bit_len)
|
||||
|
||||
return num
|
||||
|
||||
class utc(tzinfo): # noqa
|
||||
|
||||
def tzname(self, _):
|
||||
return b'UTC+00:00'
|
||||
|
||||
def utcoffset(self, _):
|
||||
return timedelta(0)
|
||||
|
||||
def dst(self, _):
|
||||
return timedelta(0)
|
||||
|
||||
class timezone(): # noqa
|
||||
|
||||
utc = utc()
|
||||
|
||||
|
||||
# Python 3
|
||||
else:
|
||||
|
||||
from datetime import timezone # noqa
|
||||
|
||||
py2 = False
|
||||
|
||||
def int_to_bytes(value, signed=False, width=None):
|
||||
"""
|
||||
Converts an integer to a byte string
|
||||
|
||||
:param value:
|
||||
The integer to convert
|
||||
|
||||
:param signed:
|
||||
If the byte string should be encoded using two's complement
|
||||
|
||||
:param width:
|
||||
None == auto, otherwise an integer of the byte width for the return
|
||||
value
|
||||
|
||||
:return:
|
||||
A byte string
|
||||
"""
|
||||
|
||||
if width is None:
|
||||
if signed:
|
||||
if value < 0:
|
||||
bits_required = abs(value + 1).bit_length()
|
||||
else:
|
||||
bits_required = value.bit_length()
|
||||
if bits_required % 8 == 0:
|
||||
bits_required += 1
|
||||
else:
|
||||
bits_required = value.bit_length()
|
||||
width = math.ceil(bits_required / 8) or 1
|
||||
return value.to_bytes(width, byteorder='big', signed=signed)
|
||||
|
||||
def int_from_bytes(value, signed=False):
|
||||
"""
|
||||
Converts a byte string to an integer
|
||||
|
||||
:param value:
|
||||
The byte string to convert
|
||||
|
||||
:param signed:
|
||||
If the byte string should be interpreted using two's complement
|
||||
|
||||
:return:
|
||||
An integer
|
||||
"""
|
||||
|
||||
return int.from_bytes(value, 'big', signed=signed)
|
||||
|
||||
|
||||
_DAYS_PER_MONTH_YEAR_0 = {
|
||||
1: 31,
|
||||
2: 29, # Year 0 was a leap year
|
||||
3: 31,
|
||||
4: 30,
|
||||
5: 31,
|
||||
6: 30,
|
||||
7: 31,
|
||||
8: 31,
|
||||
9: 30,
|
||||
10: 31,
|
||||
11: 30,
|
||||
12: 31
|
||||
}
|
||||
|
||||
|
||||
class extended_date(object):
|
||||
"""
|
||||
A datetime.date-like object that can represent the year 0. This is just
|
||||
to handle 0000-01-01 found in some certificates.
|
||||
"""
|
||||
|
||||
year = None
|
||||
month = None
|
||||
day = None
|
||||
|
||||
def __init__(self, year, month, day):
|
||||
"""
|
||||
:param year:
|
||||
The integer 0
|
||||
|
||||
:param month:
|
||||
An integer from 1 to 12
|
||||
|
||||
:param day:
|
||||
An integer from 1 to 31
|
||||
"""
|
||||
|
||||
if year != 0:
|
||||
raise ValueError('year must be 0')
|
||||
|
||||
if month < 1 or month > 12:
|
||||
raise ValueError('month is out of range')
|
||||
|
||||
if day < 0 or day > _DAYS_PER_MONTH_YEAR_0[month]:
|
||||
raise ValueError('day is out of range')
|
||||
|
||||
self.year = year
|
||||
self.month = month
|
||||
self.day = day
|
||||
|
||||
def _format(self, format):
|
||||
"""
|
||||
Performs strftime(), always returning a unicode string
|
||||
|
||||
:param format:
|
||||
A strftime() format string
|
||||
|
||||
:return:
|
||||
A unicode string of the formatted date
|
||||
"""
|
||||
|
||||
format = format.replace('%Y', '0000')
|
||||
# Year 0 is 1BC and a leap year. Leap years repeat themselves
|
||||
# every 28 years. Because of adjustments and the proleptic gregorian
|
||||
# calendar, the simplest way to format is to substitute year 2000.
|
||||
temp = date(2000, self.month, self.day)
|
||||
if '%c' in format:
|
||||
c_out = temp.strftime('%c')
|
||||
# Handle full years
|
||||
c_out = c_out.replace('2000', '0000')
|
||||
c_out = c_out.replace('%', '%%')
|
||||
format = format.replace('%c', c_out)
|
||||
if '%x' in format:
|
||||
x_out = temp.strftime('%x')
|
||||
# Handle formats such as 08/16/2000 or 16.08.2000
|
||||
x_out = x_out.replace('2000', '0000')
|
||||
x_out = x_out.replace('%', '%%')
|
||||
format = format.replace('%x', x_out)
|
||||
return temp.strftime(format)
|
||||
|
||||
def isoformat(self):
|
||||
"""
|
||||
Formats the date as %Y-%m-%d
|
||||
|
||||
:return:
|
||||
The date formatted to %Y-%m-%d as a unicode string in Python 3
|
||||
and a byte string in Python 2
|
||||
"""
|
||||
|
||||
return self.strftime('0000-%m-%d')
|
||||
|
||||
def strftime(self, format):
|
||||
"""
|
||||
Formats the date using strftime()
|
||||
|
||||
:param format:
|
||||
The strftime() format string
|
||||
|
||||
:return:
|
||||
The formatted date as a unicode string in Python 3 and a byte
|
||||
string in Python 2
|
||||
"""
|
||||
|
||||
output = self._format(format)
|
||||
if py2:
|
||||
return output.encode('utf-8')
|
||||
return output
|
||||
|
||||
def replace(self, year=None, month=None, day=None):
|
||||
"""
|
||||
Returns a new datetime.date or asn1crypto.util.extended_date
|
||||
object with the specified components replaced
|
||||
|
||||
:return:
|
||||
A datetime.date or asn1crypto.util.extended_date object
|
||||
"""
|
||||
|
||||
if year is None:
|
||||
year = self.year
|
||||
if month is None:
|
||||
month = self.month
|
||||
if day is None:
|
||||
day = self.day
|
||||
|
||||
if year > 0:
|
||||
cls = date
|
||||
else:
|
||||
cls = extended_date
|
||||
|
||||
return cls(
|
||||
year,
|
||||
month,
|
||||
day
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
if py2:
|
||||
return self.__bytes__()
|
||||
else:
|
||||
return self.__unicode__()
|
||||
|
||||
def __bytes__(self):
|
||||
return self.__unicode__().encode('utf-8')
|
||||
|
||||
def __unicode__(self):
|
||||
return self._format('%Y-%m-%d')
|
||||
|
||||
def __eq__(self, other):
|
||||
if not isinstance(other, self.__class__):
|
||||
return False
|
||||
return self.__cmp__(other) == 0
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self.__eq__(other)
|
||||
|
||||
def _comparison_error(self, other):
|
||||
raise TypeError(unwrap(
|
||||
'''
|
||||
An asn1crypto.util.extended_date object can only be compared to
|
||||
an asn1crypto.util.extended_date or datetime.date object, not %s
|
||||
''',
|
||||
type_name(other)
|
||||
))
|
||||
|
||||
def __cmp__(self, other):
|
||||
if isinstance(other, date):
|
||||
return -1
|
||||
|
||||
if not isinstance(other, self.__class__):
|
||||
self._comparison_error(other)
|
||||
|
||||
st = (
|
||||
self.year,
|
||||
self.month,
|
||||
self.day
|
||||
)
|
||||
ot = (
|
||||
other.year,
|
||||
other.month,
|
||||
other.day
|
||||
)
|
||||
|
||||
if st < ot:
|
||||
return -1
|
||||
if st > ot:
|
||||
return 1
|
||||
return 0
|
||||
|
||||
def __lt__(self, other):
|
||||
return self.__cmp__(other) < 0
|
||||
|
||||
def __le__(self, other):
|
||||
return self.__cmp__(other) <= 0
|
||||
|
||||
def __gt__(self, other):
|
||||
return self.__cmp__(other) > 0
|
||||
|
||||
def __ge__(self, other):
|
||||
return self.__cmp__(other) >= 0
|
||||
|
||||
|
||||
class extended_datetime(object):
|
||||
"""
|
||||
A datetime.datetime-like object that can represent the year 0. This is just
|
||||
to handle 0000-01-01 found in some certificates.
|
||||
"""
|
||||
|
||||
year = None
|
||||
month = None
|
||||
day = None
|
||||
hour = None
|
||||
minute = None
|
||||
second = None
|
||||
microsecond = None
|
||||
tzinfo = None
|
||||
|
||||
def __init__(self, year, month, day, hour=0, minute=0, second=0, microsecond=0, tzinfo=None):
|
||||
"""
|
||||
:param year:
|
||||
The integer 0
|
||||
|
||||
:param month:
|
||||
An integer from 1 to 12
|
||||
|
||||
:param day:
|
||||
An integer from 1 to 31
|
||||
|
||||
:param hour:
|
||||
An integer from 0 to 23
|
||||
|
||||
:param minute:
|
||||
An integer from 0 to 59
|
||||
|
||||
:param second:
|
||||
An integer from 0 to 59
|
||||
|
||||
:param microsecond:
|
||||
An integer from 0 to 999999
|
||||
"""
|
||||
|
||||
if year != 0:
|
||||
raise ValueError('year must be 0')
|
||||
|
||||
if month < 1 or month > 12:
|
||||
raise ValueError('month is out of range')
|
||||
|
||||
if day < 0 or day > _DAYS_PER_MONTH_YEAR_0[month]:
|
||||
raise ValueError('day is out of range')
|
||||
|
||||
if hour < 0 or hour > 23:
|
||||
raise ValueError('hour is out of range')
|
||||
|
||||
if minute < 0 or minute > 59:
|
||||
raise ValueError('minute is out of range')
|
||||
|
||||
if second < 0 or second > 59:
|
||||
raise ValueError('second is out of range')
|
||||
|
||||
if microsecond < 0 or microsecond > 999999:
|
||||
raise ValueError('microsecond is out of range')
|
||||
|
||||
self.year = year
|
||||
self.month = month
|
||||
self.day = day
|
||||
self.hour = hour
|
||||
self.minute = minute
|
||||
self.second = second
|
||||
self.microsecond = microsecond
|
||||
self.tzinfo = tzinfo
|
||||
|
||||
def date(self):
|
||||
"""
|
||||
:return:
|
||||
An asn1crypto.util.extended_date of the date
|
||||
"""
|
||||
|
||||
return extended_date(self.year, self.month, self.day)
|
||||
|
||||
def time(self):
|
||||
"""
|
||||
:return:
|
||||
A datetime.time object of the time
|
||||
"""
|
||||
|
||||
return time(self.hour, self.minute, self.second, self.microsecond, self.tzinfo)
|
||||
|
||||
def utcoffset(self):
|
||||
"""
|
||||
:return:
|
||||
None or a datetime.timedelta() of the offset from UTC
|
||||
"""
|
||||
|
||||
if self.tzinfo is None:
|
||||
return None
|
||||
return self.tzinfo.utcoffset(self.replace(year=2000))
|
||||
|
||||
def dst(self):
|
||||
"""
|
||||
:return:
|
||||
None or a datetime.timedelta() of the daylight savings time offset
|
||||
"""
|
||||
|
||||
if self.tzinfo is None:
|
||||
return None
|
||||
return self.tzinfo.dst(self.replace(year=2000))
|
||||
|
||||
def tzname(self):
|
||||
"""
|
||||
:return:
|
||||
None or the name of the timezone as a unicode string in Python 3
|
||||
and a byte string in Python 2
|
||||
"""
|
||||
|
||||
if self.tzinfo is None:
|
||||
return None
|
||||
return self.tzinfo.tzname(self.replace(year=2000))
|
||||
|
||||
def _format(self, format):
|
||||
"""
|
||||
Performs strftime(), always returning a unicode string
|
||||
|
||||
:param format:
|
||||
A strftime() format string
|
||||
|
||||
:return:
|
||||
A unicode string of the formatted datetime
|
||||
"""
|
||||
|
||||
format = format.replace('%Y', '0000')
|
||||
# Year 0 is 1BC and a leap year. Leap years repeat themselves
|
||||
# every 28 years. Because of adjustments and the proleptic gregorian
|
||||
# calendar, the simplest way to format is to substitute year 2000.
|
||||
temp = datetime(
|
||||
2000,
|
||||
self.month,
|
||||
self.day,
|
||||
self.hour,
|
||||
self.minute,
|
||||
self.second,
|
||||
self.microsecond,
|
||||
self.tzinfo
|
||||
)
|
||||
if '%c' in format:
|
||||
c_out = temp.strftime('%c')
|
||||
# Handle full years
|
||||
c_out = c_out.replace('2000', '0000')
|
||||
c_out = c_out.replace('%', '%%')
|
||||
format = format.replace('%c', c_out)
|
||||
if '%x' in format:
|
||||
x_out = temp.strftime('%x')
|
||||
# Handle formats such as 08/16/2000 or 16.08.2000
|
||||
x_out = x_out.replace('2000', '0000')
|
||||
x_out = x_out.replace('%', '%%')
|
||||
format = format.replace('%x', x_out)
|
||||
return temp.strftime(format)
|
||||
|
||||
def isoformat(self, sep='T'):
|
||||
"""
|
||||
Formats the date as "%Y-%m-%d %H:%M:%S" with the sep param between the
|
||||
date and time portions
|
||||
|
||||
:param set:
|
||||
A single character of the separator to place between the date and
|
||||
time
|
||||
|
||||
:return:
|
||||
The formatted datetime as a unicode string in Python 3 and a byte
|
||||
string in Python 2
|
||||
"""
|
||||
|
||||
if self.microsecond == 0:
|
||||
return self.strftime('0000-%%m-%%d%s%%H:%%M:%%S' % sep)
|
||||
return self.strftime('0000-%%m-%%d%s%%H:%%M:%%S.%%f' % sep)
|
||||
|
||||
def strftime(self, format):
|
||||
"""
|
||||
Formats the date using strftime()
|
||||
|
||||
:param format:
|
||||
The strftime() format string
|
||||
|
||||
:return:
|
||||
The formatted date as a unicode string in Python 3 and a byte
|
||||
string in Python 2
|
||||
"""
|
||||
|
||||
output = self._format(format)
|
||||
if py2:
|
||||
return output.encode('utf-8')
|
||||
return output
|
||||
|
||||
def replace(self, year=None, month=None, day=None, hour=None, minute=None,
|
||||
second=None, microsecond=None, tzinfo=None):
|
||||
"""
|
||||
Returns a new datetime.datetime or asn1crypto.util.extended_datetime
|
||||
object with the specified components replaced
|
||||
|
||||
:return:
|
||||
A datetime.datetime or asn1crypto.util.extended_datetime object
|
||||
"""
|
||||
|
||||
if year is None:
|
||||
year = self.year
|
||||
if month is None:
|
||||
month = self.month
|
||||
if day is None:
|
||||
day = self.day
|
||||
if hour is None:
|
||||
hour = self.hour
|
||||
if minute is None:
|
||||
minute = self.minute
|
||||
if second is None:
|
||||
second = self.second
|
||||
if microsecond is None:
|
||||
microsecond = self.microsecond
|
||||
if tzinfo is None:
|
||||
tzinfo = self.tzinfo
|
||||
|
||||
if year > 0:
|
||||
cls = datetime
|
||||
else:
|
||||
cls = extended_datetime
|
||||
|
||||
return cls(
|
||||
year,
|
||||
month,
|
||||
day,
|
||||
hour,
|
||||
minute,
|
||||
second,
|
||||
microsecond,
|
||||
tzinfo
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
if py2:
|
||||
return self.__bytes__()
|
||||
else:
|
||||
return self.__unicode__()
|
||||
|
||||
def __bytes__(self):
|
||||
return self.__unicode__().encode('utf-8')
|
||||
|
||||
def __unicode__(self):
|
||||
format = '%Y-%m-%d %H:%M:%S'
|
||||
if self.microsecond != 0:
|
||||
format += '.%f'
|
||||
return self._format(format)
|
||||
|
||||
def __eq__(self, other):
|
||||
if not isinstance(other, self.__class__):
|
||||
return False
|
||||
return self.__cmp__(other) == 0
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self.__eq__(other)
|
||||
|
||||
def _comparison_error(self, other):
|
||||
"""
|
||||
Raises a TypeError about the other object not being suitable for
|
||||
comparison
|
||||
|
||||
:param other:
|
||||
The object being compared to
|
||||
"""
|
||||
|
||||
raise TypeError(unwrap(
|
||||
'''
|
||||
An asn1crypto.util.extended_datetime object can only be compared to
|
||||
an asn1crypto.util.extended_datetime or datetime.datetime object,
|
||||
not %s
|
||||
''',
|
||||
type_name(other)
|
||||
))
|
||||
|
||||
def __cmp__(self, other):
|
||||
so = self.utcoffset()
|
||||
oo = other.utcoffset()
|
||||
|
||||
if (so is not None and oo is None) or (so is None and oo is not None):
|
||||
raise TypeError("can't compare offset-naive and offset-aware datetimes")
|
||||
|
||||
if isinstance(other, datetime):
|
||||
return -1
|
||||
|
||||
if not isinstance(other, self.__class__):
|
||||
self._comparison_error(other)
|
||||
|
||||
st = (
|
||||
self.year,
|
||||
self.month,
|
||||
self.day,
|
||||
self.hour,
|
||||
self.minute,
|
||||
self.second,
|
||||
self.microsecond,
|
||||
so
|
||||
)
|
||||
ot = (
|
||||
other.year,
|
||||
other.month,
|
||||
other.day,
|
||||
other.hour,
|
||||
other.minute,
|
||||
other.second,
|
||||
other.microsecond,
|
||||
oo
|
||||
)
|
||||
|
||||
if st < ot:
|
||||
return -1
|
||||
if st > ot:
|
||||
return 1
|
||||
return 0
|
||||
|
||||
def __lt__(self, other):
|
||||
return self.__cmp__(other) < 0
|
||||
|
||||
def __le__(self, other):
|
||||
return self.__cmp__(other) <= 0
|
||||
|
||||
def __gt__(self, other):
|
||||
return self.__cmp__(other) > 0
|
||||
|
||||
def __ge__(self, other):
|
||||
return self.__cmp__(other) >= 0
|
|
@ -0,0 +1,6 @@
|
|||
# coding: utf-8
|
||||
from __future__ import unicode_literals, division, absolute_import, print_function
|
||||
|
||||
|
||||
__version__ = '0.24.0'
|
||||
__version_info__ = (0, 24, 0)
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,339 @@
|
|||
# changelog
|
||||
|
||||
## 0.24.0
|
||||
|
||||
- `x509.Certificate().self_signed` will no longer return `"yes"` under any
|
||||
circumstances. This helps prevent confusion since the library does not
|
||||
verify the signature. Instead a library like oscrypto should be used
|
||||
to confirm if a certificate is self-signed.
|
||||
- Added various OIDs to `x509.KeyPurposeId()`
|
||||
- Added `x509.Certificate().private_key_usage_period_value`
|
||||
- Added structures for parsing common subject directory attributes for
|
||||
X.509 certificates, including `x509.SubjectDirectoryAttribute()`
|
||||
- Added `algos.AnyAlgorithmIdentifier()` for situations where an
|
||||
algorithm identifier may contain a digest, signed digest or encryption
|
||||
algorithm OID
|
||||
- Fixed a bug with `x509.Certificate().subject_directory_attributes_value`
|
||||
not returning the correct value
|
||||
- Fixed a bug where explicitly-tagged fields in a `core.Sequence()` would
|
||||
not function properly when the field had a default value
|
||||
- Fixed a bug with type checking in `pem.armor()`
|
||||
|
||||
## 0.23.0
|
||||
|
||||
- Backwards compatibility break: the `tag_type`, `explicit_tag` and
|
||||
`explicit_class` attributes on `core.Asn1Value` no longer exist and were
|
||||
replaced by the `implicit` and `explicit` attributes. Field param dicts
|
||||
may use the new `explicit` and `implicit` keys, or the old `tag_type` and
|
||||
`tag` keys. The attribute changes will likely to have little to no impact
|
||||
since they were primarily an implementation detail.
|
||||
- Teletex strings used inside of X.509 certificates are now interpreted
|
||||
using Windows-1252 (a superset of ISO-8859-1). This enables compatibility
|
||||
with certificates generated by OpenSSL. Strict parsing of Teletex strings
|
||||
can be retained by using the `x509.strict_teletex()` context manager.
|
||||
- Added support for nested explicit tagging, supporting values that are
|
||||
defined with explicit tagging and then added as a field of another
|
||||
structure using explicit tagging.
|
||||
- Fixed a `UnicodeDecodeError` when trying to find the (optional) dependency
|
||||
OpenSSL on Python 2
|
||||
- Fixed `next_update` field of `crl.TbsCertList` to be optional
|
||||
- Added the `x509.Certificate.sha256_fingerprint` property
|
||||
- `x509.Certificate.ocsp_urls` and `x509.DistributionPoint.url` will now
|
||||
return `https://`, `ldap://` and `ldaps://` URLs in addition to `http://`.
|
||||
- Added CMS Attribute Protection definitions from RFC 6211
|
||||
- Added OIDs from RFC 6962
|
||||
|
||||
## 0.22.0
|
||||
|
||||
- Added `parser.peek()`
|
||||
- Implemented proper support for BER-encoded indefinite length strings of
|
||||
all kinds - `core.BitString`, `core.OctetString` and all of the `core`
|
||||
classes that are natively represented as Python unicode strings
|
||||
- Fixed a bug with encoding LDAP URLs in `x509.URI`
|
||||
- Correct `x509.DNSName` to allow a leading `.`, such as when used with
|
||||
`x509.NameConstraints`
|
||||
- Fixed an issue with dumping the parsed contents of `core.Any` when
|
||||
explicitly tagged
|
||||
- Custom `setup.py clean` now accepts the short `-a` flag for compatibility
|
||||
|
||||
## 0.21.1
|
||||
|
||||
- Fixed a regression where explicit tagging of a field containing a
|
||||
`core.Choice` would result in an incorrect header
|
||||
- Fixed a bug where an `IndexError` was being raised instead of a `ValueError`
|
||||
when a value was truncated to not include enough bytes for the header
|
||||
- Corrected the spec for the `value` field of `pkcs12.Attribute`
|
||||
- Added support for `2.16.840.1.113894.746875.1.1` OID to
|
||||
`pkcs12.AttributeType`
|
||||
|
||||
## 0.21.0
|
||||
|
||||
- Added `core.load()` for loading standard, universal types without knowing
|
||||
the spec beforehand
|
||||
- Added a `strict` keyword arg to the various `load()` methods and functions in
|
||||
`core` that checks for trailing data and raises a `ValueError` when found
|
||||
- Added `asn1crypto.parser` submodule with `emit()` and `parse()` functions for
|
||||
low-level integration
|
||||
- Added `asn1crypto.version` for version introspection without side-effects
|
||||
- Added `algos.DSASignature`
|
||||
- Fixed a bug with the `_header` attribute of explicitly-tagged values only
|
||||
containing the explicit tag header instead of both the explicit tag header
|
||||
and the encapsulated value header
|
||||
|
||||
## 0.20.0
|
||||
|
||||
- Added support for year 0
|
||||
- Added the OID for unique identifier to `x509.NameType`
|
||||
- Fixed a bug creating the native representation of a `core.BitString` with
|
||||
leading null bytes
|
||||
- Added a `.cast()` method to allow converting between different
|
||||
representations of the same data, e.g. `core.BitString` and
|
||||
`core.OctetBitString`
|
||||
|
||||
## 0.19.0
|
||||
|
||||
- Force `algos.DigestAlgorithm` to encoding `parameters` as `Null` when the
|
||||
`algorithm` is `sha1`, `sha224`, `sha256`, `sha384` or `sha512` per RFC 4055
|
||||
- Resolved an issue where a BER-encoded indefinite-length value could not be
|
||||
properly parsed when embedded inside of a `core.Sequence` or `core.Set`
|
||||
- Fix `x509.Name.build()` to properly handle dotted OID type values
|
||||
- `core.Choice` can now be constructed from a single-element `dict` or a
|
||||
two-element `tuple` to allow for better usability when constructing values
|
||||
from native Python values
|
||||
- All `core` objects can now be passed to `print()` with an exception being
|
||||
raised
|
||||
|
||||
## 0.18.5
|
||||
|
||||
- Don't fail importing if `ctypes` or `_ctypes` is not available
|
||||
|
||||
## 0.18.4
|
||||
|
||||
- `core.Sequence` will now raise an exception when an unknown field is provided
|
||||
- Prevent `UnicodeDecodeError` on Python 2 when calling
|
||||
`core.OctetString.debug()`
|
||||
- Corrected the default value for the `hash_algorithm` field of
|
||||
`tsp.ESSCertIDv2`
|
||||
- Fixed a bug constructing a `cms.SignedData` object
|
||||
- Ensure that specific RSA OIDs are always paired with `parameters` set to
|
||||
`core.Null`
|
||||
|
||||
## 0.18.3
|
||||
|
||||
- Fixed DER encoding of `core.BitString` when a `_map` is specified (i.e. a
|
||||
"named bit list") to omit trailing zero bits. This fixes compliance of
|
||||
various `x509` structures with RFC 5280.
|
||||
- Corrected a side effect in `keys.PrivateKeyInfo.wrap()` that would cause the
|
||||
original `keys.ECPrivateKey` structure to become corrupt
|
||||
- `core.IntegerOctetString` now correctly encodes the integer as an unsigned
|
||||
value when converting to bytes. Previously decoding was unsigned, but
|
||||
encoding was signed.
|
||||
- Fix `util.int_from_bytes()` on Python 2 to return `0` from an empty byte
|
||||
string
|
||||
|
||||
## 0.18.2
|
||||
|
||||
- Allow `_perf` submodule to be removed from source tree when embedding
|
||||
|
||||
## 0.18.1
|
||||
|
||||
- Fixed DER encoding of `core.Set` and `core.SetOf`
|
||||
- Fixed a bug in `x509.Name.build()` that could generate invalid DER encoding
|
||||
- Improved exception messages when parsing nested structures via the `.native`
|
||||
attribute
|
||||
- `algos.SignedDigestAlgorithm` now ensures the `parameters` are set to
|
||||
`Null` when `algorithm` is `sha224_rsa`, `sha256_rsa`, `sha384_rsa` or
|
||||
`sha512_rsa`, per RFC 4055
|
||||
- Corrected the definition of `pdf.AdobeTimestamp` to mark the
|
||||
`requires_auth` field as optional
|
||||
- Add support for the OID `1.2.840.113549.1.9.16.2.14` to
|
||||
`cms.CMSAttributeType`
|
||||
- Improve attribute support for `cms.AttributeCertificateV2`
|
||||
- Handle `cms.AttributeCertificateV2` when incorrectly tagged as
|
||||
`cms.AttributeCertificateV1` in `cms.CertificateChoices`
|
||||
|
||||
## 0.18.0
|
||||
|
||||
- Improved general parsing performance by 10-15%
|
||||
- Add support for Windows XP
|
||||
- Added `core.ObjectIdentifier.dotted` attribute to always return dotted
|
||||
integer unicode string
|
||||
- Added `core.ObjectIdentifier.map()` and `core.ObjectIdentifier.unmap()`
|
||||
class methods to map dotted integer unicode strings to user-friendly unicode
|
||||
strings and back
|
||||
- Added various Apple OIDs to `x509.KeyPurposeId`
|
||||
- Fixed a bug parsing nested indefinite-length-encoded values
|
||||
- Fixed a bug with `x509.Certificate.issuer_alt_name_value` if it is the first
|
||||
extension queried
|
||||
- `keys.PublicKeyInfo.bit_size` and `keys.PrivateKeyInfo.bit_size` values are
|
||||
now rounded up to the next closest multiple of 8
|
||||
|
||||
## 0.17.1
|
||||
|
||||
- Fix a bug in `x509.URI` parsing IRIs containing explicit port numbers on
|
||||
Python 3.x
|
||||
|
||||
## 0.17.0
|
||||
|
||||
- Added `x509.TrustedCertificate` for handling OpenSSL auxiliary certificate
|
||||
information appended after a certificate
|
||||
- Added `core.Concat` class for situations such as `x509.TrustedCertificate`
|
||||
- Allow "broken" X.509 certificates to use `core.IA5String` where an
|
||||
`x509.DirectoryString` should be used instead
|
||||
- Added `keys.PrivateKeyInfo.public_key_info` attribute
|
||||
- Added a bunch of OIDs to `x509.KeyPurposeId`
|
||||
|
||||
## 0.16.0
|
||||
|
||||
- Added DH key exchange structures: `algos.KeyExchangeAlgorithm`,
|
||||
`algos.KeyExchangeAlgorithmId` and `algos.DHParameters`.
|
||||
- Added DH public key support to `keys.PublicKeyInfo`,
|
||||
`keys.PublicKeyAlgorithm` and `keys.PublicKeyAlgorithmId`. New structures
|
||||
include `keys.DomainParameters` and `keys.ValidationParms`.
|
||||
|
||||
## 0.15.1
|
||||
|
||||
- Fixed `cms.CMSAttributes` to be a `core.SetOf` instead of `core.SequenceOf`
|
||||
- `cms.CMSAttribute` can now parse unknown attribute contrustruct without an
|
||||
exception being raised
|
||||
- `x509.PolicyMapping` now uses `x509.PolicyIdentifier` for field types
|
||||
- Fixed `pdf.RevocationInfoArchival` so that all fields are now of the type
|
||||
`core.SequenceOf` instead of a single value
|
||||
- Added support for the `name_distinguisher`, `telephone_number` and
|
||||
`organization_identifier` OIDs to `x509.Name`
|
||||
- Fixed `x509.Name.native` to not accidentally create nested lists when three
|
||||
of more values for a single type are part of the name
|
||||
- `x509.Name.human_friendly` now reverses the order of fields when the data
|
||||
in an `x509.Name` was encoded in most-specific to least-specific order, which
|
||||
is the opposite of the standard way of least-specific to most-specific.
|
||||
- `x509.NameType.human_friendly` no longer raises an exception when an
|
||||
unknown OID is encountered
|
||||
- Raise a `ValueError` when parsing a `core.Set` and an unknown field is
|
||||
encountered
|
||||
|
||||
## 0.15.0
|
||||
|
||||
- Added support for the TLS feature extension from RFC 7633
|
||||
- `x509.Name.build()` now accepts a keyword parameter `use_printable` to force
|
||||
string encoding to be `core.PrintableString` instead of `core.UTF8String`
|
||||
- Added the functions `util.uri_to_iri()` and `util.iri_to_uri()`
|
||||
- Changed `algos.SignedDigestAlgorithmId` to use the preferred OIDs when
|
||||
mapping a unicode string name to an OID. Previously there were multiple OIDs
|
||||
for some algorithms, and different OIDs would sometimes be selected due to
|
||||
the fact that the `_map` `dict` is not ordered.
|
||||
|
||||
## 0.14.1
|
||||
|
||||
- Fixed a bug generating `x509.Certificate.sha1_fingerprint` on Python 2
|
||||
|
||||
## 0.14.0
|
||||
|
||||
- Added the `x509.Certificate.sha1_fingerprint` attribute
|
||||
|
||||
## 0.13.0
|
||||
|
||||
- Backwards compatibility break: the native representation of some
|
||||
`algos.EncryptionAlgorithmId` values changed. `aes128` became `aes128_cbc`,
|
||||
`aes192` became `aes192_cbc` and `aes256` became `aes256_cbc`.
|
||||
- Added more OIDs to `algos.EncryptionAlgorithmId`
|
||||
- Added more OIDs to `cms.KeyEncryptionAlgorithmId`
|
||||
- `x509.Name.human_friendly` now properly supports multiple values per
|
||||
`x509.NameTypeAndValue` object
|
||||
- Added `ocsp.OCSPResponse.basic_ocsp_response` and
|
||||
`ocsp.OCSPResponse.response_data` properties
|
||||
- Added `algos.EncryptionAlgorithm.encryption_mode` property
|
||||
- Fixed a bug with parsing times containing timezone offsets in Python 3
|
||||
- The `attributes` field of `csr.CertificationRequestInfo` is now optional,
|
||||
for compatibility with other ASN.1 parsers
|
||||
|
||||
## 0.12.2
|
||||
|
||||
- Correct `core.Sequence.__setitem__()` so set `core.VOID` to an optional
|
||||
field when `None` is set
|
||||
|
||||
## 0.12.1
|
||||
|
||||
- Fixed a `unicode`/`bytes` bug with `x509.URI.dump()` on Python 2
|
||||
|
||||
## 0.12.0
|
||||
|
||||
- Backwards Compatibility Break: `core.NoValue` was renamed to `core.Void` and
|
||||
a singleton was added as `core.VOID`
|
||||
- 20-30% improvement in parsing performance
|
||||
- `core.Void` now implements `__nonzero__`
|
||||
- `core.Asn1Value.copy()` now performs a deep copy
|
||||
- All `core` value classes are now compatible with the `copy` module
|
||||
- `core.SequenceOf` and `core.SetOf` now implement `__contains__`
|
||||
- Added `x509.Name.__len__()`
|
||||
- Fixed a bug where `core.Choice.validate()` would not properly account for
|
||||
explicit tagging
|
||||
- `core.Choice.load()` now properly passes itself as the spec when parsing
|
||||
- `x509.Certificate.crl_distribution_points` no longer throws an exception if
|
||||
the `DistributionPoint` does not have a value for the `distribution_point`
|
||||
field
|
||||
|
||||
## 0.11.1
|
||||
|
||||
- Corrected `core.UTCTime` to interpret year <= 49 as 20xx and >= 50 as 19xx
|
||||
- `keys.PublicKeyInfo.hash_algo` can now handle DSA keys without parameters
|
||||
- Added `crl.CertificateList.sha256` and `crl.CertificateList.sha1`
|
||||
- Fixed `x509.Name.build()` to properly encode `country_name`, `serial_number`
|
||||
and `dn_qualifier` as `core.PrintableString` as specified in RFC 5280,
|
||||
instead of `core.UTF8String`
|
||||
|
||||
## 0.11.0
|
||||
|
||||
- Added Python 2.6 support
|
||||
- Added ability to compare primitive type objects
|
||||
- Implemented proper support for internationalized domains, URLs and email
|
||||
addresses in `x509.Certificate`
|
||||
- Comparing `x509.Name` and `x509.GeneralName` objects adheres to RFC 5280
|
||||
- `x509.Certificate.self_signed` and `x509.Certificate.self_issued` no longer
|
||||
require that certificate is for a CA
|
||||
- Fixed `x509.Certificate.valid_domains` to adhere to RFC 6125
|
||||
- Added `x509.Certificate.is_valid_domain_ip()`
|
||||
- Added `x509.Certificate.sha1` and `x509.Certificate.sha256`
|
||||
- Exposed `util.inet_ntop()` and `util.inet_pton()` for IP address encoding
|
||||
- Improved exception messages for improper types to include type's module name
|
||||
|
||||
## 0.10.1
|
||||
|
||||
- Fixed bug in `core.Sequence` affecting Python 2.7 and pypy
|
||||
|
||||
## 0.10.0
|
||||
|
||||
- Added PEM encoding/decoding functionality
|
||||
- `core.BitString` now uses item access instead of attributes for named bit
|
||||
access
|
||||
- `core.BitString.native` now uses a `set` of unicode strings when `_map` is
|
||||
present
|
||||
- Removed `core.Asn1Value.pprint()` method
|
||||
- Added `core.ParsableOctetString` class
|
||||
- Added `core.ParsableOctetBitString` class
|
||||
- Added `core.Asn1Value.copy()` method
|
||||
- Added `core.Asn1Value.debug()` method
|
||||
- Added `core.SequenceOf.append()` method
|
||||
- Added `core.Sequence.spec()` and `core.SequenceOf.spec()` methods
|
||||
- Added correct IP address parsing to `x509.GeneralName`
|
||||
- `x509.Name` and `x509.GeneralName` are now compared according to rules in
|
||||
RFC 5280
|
||||
- Added convenience attributes to:
|
||||
- `algos.SignedDigestAlgorithm`
|
||||
- `crl.CertificateList`
|
||||
- `crl.RevokedCertificate`
|
||||
- `keys.PublicKeyInfo`
|
||||
- `ocsp.OCSPRequest`
|
||||
- `ocsp.Request`
|
||||
- `ocsp.OCSPResponse`
|
||||
- `ocsp.SingleResponse`
|
||||
- `x509.Certificate`
|
||||
- `x509.Name`
|
||||
- Added `asn1crypto.util` module with the following items:
|
||||
- `int_to_bytes()`
|
||||
- `int_from_bytes()`
|
||||
- `timezone.utc`
|
||||
- Added `setup.py clean` command
|
||||
|
||||
## 0.9.0
|
||||
|
||||
- Initial release
|
|
@ -0,0 +1,79 @@
|
|||
# PEM Decoder and Encoder
|
||||
|
||||
Often times DER-encoded data is wrapped in PEM encoding. This allows the binary
|
||||
DER data to be identified and reliably sent over various communication channels.
|
||||
|
||||
The `asn1crypto.pem` module includes three functions:
|
||||
|
||||
- `detect(byte_string)`
|
||||
- `unarmor(pem_bytes, multiple=False)`
|
||||
- `armor(type_name, der_bytes, headers=None)`
|
||||
|
||||
## detect()
|
||||
|
||||
The `detect()` function accepts a byte string and looks for a `BEGIN` block
|
||||
line. This is useful to determine in a byte string needs to be PEM-decoded
|
||||
before parsing.
|
||||
|
||||
```python
|
||||
from asn1crypto import pem, x509
|
||||
|
||||
with open('/path/to/cert', 'rb') as f:
|
||||
der_bytes = f.read()
|
||||
if pem.detect(der_bytes):
|
||||
_, _, der_bytes = pem.unarmor(der_bytes)
|
||||
```
|
||||
|
||||
## unarmor()
|
||||
|
||||
The `unarmor()` function accepts a byte string and the flag to indicates if
|
||||
more than one PEM block may be contained in the byte string. The result is
|
||||
a three-element tuple.
|
||||
|
||||
- The first element is a unicode string of the type of PEM block. Examples
|
||||
include: `CERTIFICATE`, `PRIVATE KEY`, `PUBLIC KEY`.
|
||||
- The second element is a `dict` of PEM block headers. Headers are typically
|
||||
only used by encrypted OpenSSL private keys, and are in the format
|
||||
`Name: Value`.
|
||||
- The third element is a byte string of the decoded block contents.
|
||||
|
||||
```python
|
||||
from asn1crypto import pem, x509
|
||||
|
||||
with open('/path/to/cert', 'rb') as f:
|
||||
der_bytes = f.read()
|
||||
if pem.detect(der_bytes):
|
||||
type_name, headers, der_bytes = pem.unarmor(der_bytes)
|
||||
|
||||
cert = x509.Certificate.load(der_bytes)
|
||||
```
|
||||
|
||||
If the `multiple` keyword argument is set to `True`, a generator will be
|
||||
returned.
|
||||
|
||||
```python
|
||||
from asn1crypto import pem, x509
|
||||
|
||||
certs = []
|
||||
with open('/path/to/ca_certs', 'rb') as f:
|
||||
for type_name, headers, der_bytes in pem.unarmor(f.read(), multiple=True):
|
||||
certs.append(x509.Certificate.load(der_bytes))
|
||||
```
|
||||
|
||||
## armor()
|
||||
|
||||
The `armor()` function accepts three parameters: a unicode string of the block
|
||||
type name, a byte string to encode and an optional keyword argument `headers`,
|
||||
that should be a `dict` of headers to add after the `BEGIN` line. Headers are
|
||||
typically only used by encrypted OpenSSL private keys.
|
||||
|
||||
```python
|
||||
from asn1crypto import pem, x509
|
||||
|
||||
# cert is an instance of x509.Certificate
|
||||
|
||||
with open('/path/to/cert', 'wb') as f:
|
||||
der_bytes = cert.dump()
|
||||
pem_bytes = pem.armor('CERTIFICATE', der_bytes)
|
||||
f.write(pem_bytes)
|
||||
```
|
|
@ -0,0 +1,23 @@
|
|||
# asn1crypto Documentation
|
||||
|
||||
The documentation for *asn1crypto* is composed of tutorials on basic usage and
|
||||
links to the source for the various pre-defined type classes.
|
||||
|
||||
## Tutorials
|
||||
|
||||
- [Universal Types with BER/DER Decoder and DER Encoder](universal_types.md)
|
||||
- [PEM Decoder and Encoder](pem.md)
|
||||
|
||||
## Reference
|
||||
|
||||
- [Universal types](../asn1crypto/core.py), `asn1crypto.core`
|
||||
- [Digest, HMAC, signed digest and encryption algorithms](../asn1crypto/algos.py), `asn1crypto.algos`
|
||||
- [Private and public keys](../asn1crypto/keys.py), `asn1crypto.keys`
|
||||
- [X.509 certificates](../asn1crypto/x509.py), `asn1crypto.x509`
|
||||
- [Certificate revocation lists (CRLs)](../asn1crypto/crl.py), `asn1crypto.crl`
|
||||
- [Online certificate status protocol (OCSP)](../asn1crypto/ocsp.py), `asn1crypto.ocsp`
|
||||
- [Certificate signing requests (CSRs)](../asn1crypto/csr.py), `asn1crypto.csr`
|
||||
- [Private key/certificate containers (PKCS#12)](../asn1crypto/pkcs12.py), `asn1crypto.pkcs12`
|
||||
- [Cryptographic message syntax (CMS, PKCS#7)](../asn1crypto/cms.py), `asn1crypto.cms`
|
||||
- [Time stamp protocol (TSP)](../asn1crypto/tsp.py), `asn1crypto.tsp`
|
||||
- [PDF signatures](../asn1crypto/pdf.py), `asn1crypto.pdf`
|
|
@ -0,0 +1,675 @@
|
|||
# Universal Types with BER/DER Decoder and DER Encoder
|
||||
|
||||
The *asn1crypto* library is a combination of universal type classes that
|
||||
implement BER/DER decoding and DER encoding, a PEM encoder and decoder, and a
|
||||
number of pre-built cryptographic type classes. This document covers the
|
||||
universal type classes.
|
||||
|
||||
For a general overview of ASN.1 as used in cryptography, please see
|
||||
[A Layman's Guide to a Subset of ASN.1, BER, and DER](http://luca.ntop.org/Teaching/Appunti/asn1.html).
|
||||
|
||||
This page contains the following sections:
|
||||
|
||||
- [Universal Types](#universal-types)
|
||||
- [Basic Usage](#basic-usage)
|
||||
- [Sequence](#sequence)
|
||||
- [Set](#set)
|
||||
- [SequenceOf](#sequenceof)
|
||||
- [SetOf](#setof)
|
||||
- [Integer](#integer)
|
||||
- [Enumerated](#enumerated)
|
||||
- [ObjectIdentifier](#objectidentifier)
|
||||
- [BitString](#bitstring)
|
||||
- [Strings](#strings)
|
||||
- [UTCTime](#utctime)
|
||||
- [GeneralizedTime](#generalizedtime)
|
||||
- [Choice](#choice)
|
||||
- [Any](#any)
|
||||
- [Specification via OID](#specification-via-oid)
|
||||
- [Explicit and Implicit Tagging](#explicit-and-implicit-tagging)
|
||||
|
||||
## Universal Types
|
||||
|
||||
For general purpose ASN.1 parsing, the `asn1crypto.core` module is used. It
|
||||
contains the following classes, that parse, represent and serialize all of the
|
||||
ASN.1 universal types:
|
||||
|
||||
| Class | Native Type | Implementation Notes |
|
||||
| ------------------ | -------------------------------------- | ------------------------------------ |
|
||||
| `Boolean` | `bool` | |
|
||||
| `Integer` | `int` | may be `long` on Python 2 |
|
||||
| `BitString` | `tuple` of `int` or `set` of `unicode` | `set` used if `_map` present |
|
||||
| `OctetString` | `bytes` (`str`) | |
|
||||
| `Null` | `None` | |
|
||||
| `ObjectIdentifier` | `str` (`unicode`) | string is dotted integer format |
|
||||
| `ObjectDescriptor` | | no native conversion |
|
||||
| `InstanceOf` | | no native conversion |
|
||||
| `Real` | | no native conversion |
|
||||
| `Enumerated` | `str` (`unicode`) | `_map` must be set |
|
||||
| `UTF8String` | `str` (`unicode`) | |
|
||||
| `RelativeOid` | `str` (`unicode`) | string is dotted integer format |
|
||||
| `Sequence` | `OrderedDict` | |
|
||||
| `SequenceOf` | `list` | |
|
||||
| `Set` | `OrderedDict` | |
|
||||
| `SetOf` | `list` | |
|
||||
| `EmbeddedPdv` | `OrderedDict` | no named field parsing |
|
||||
| `NumericString` | `str` (`unicode`) | no charset limitations |
|
||||
| `PrintableString` | `str` (`unicode`) | no charset limitations |
|
||||
| `TeletexString` | `str` (`unicode`) | |
|
||||
| `VideotexString` | `bytes` (`str`) | no unicode conversion |
|
||||
| `IA5String` | `str` (`unicode`) | |
|
||||
| `UTCTime` | `datetime.datetime` | |
|
||||
| `GeneralizedTime` | `datetime.datetime` | treated as UTC when no timezone |
|
||||
| `GraphicString` | `str` (`unicode`) | unicode conversion as latin1 |
|
||||
| `VisibleString` | `str` (`unicode`) | no charset limitations |
|
||||
| `GeneralString` | `str` (`unicode`) | unicode conversion as latin1 |
|
||||
| `UniversalString` | `str` (`unicode`) | |
|
||||
| `CharacterString` | `str` (`unicode`) | unicode conversion as latin1 |
|
||||
| `BMPString` | `str` (`unicode`) | |
|
||||
|
||||
For *Native Type*, the Python 3 type is listed first, with the Python 2 type
|
||||
in parentheses.
|
||||
|
||||
As mentioned next to some of the types, value parsing may not be implemented
|
||||
for types not currently used in cryptography (such as `ObjectDescriptor`,
|
||||
`InstanceOf` and `Real`). Additionally some of the string classes don't
|
||||
enforce character set limitations, and for some string types that accept all
|
||||
different encodings, the default encoding is set to latin1.
|
||||
|
||||
In addition, there are a few overridden types where various specifications use
|
||||
a `BitString` or `OctetString` type to represent a different type. These
|
||||
include:
|
||||
|
||||
| Class | Native Type | Implementation Notes |
|
||||
| -------------------- | ------------------- | ------------------------------- |
|
||||
| `OctetBitString` | `bytes` (`str`) | |
|
||||
| `IntegerBitString` | `int` | may be `long` on Python 2 |
|
||||
| `IntegerOctetString` | `int` | may be `long` on Python 2 |
|
||||
|
||||
For situations where the DER encoded bytes from one type is embedded in another,
|
||||
the `ParsableOctetString` and `ParsableOctetBitString` classes exist. These
|
||||
function the same as `OctetString` and `OctetBitString`, however they also
|
||||
have an attribute `.parsed` and a method `.parse()` that allows for
|
||||
parsing the content as ASN.1 structures.
|
||||
|
||||
All of these overrides can be used with the `cast()` method to convert between
|
||||
them. The only requirement is that the class being casted to has the same tag
|
||||
as the original class. No re-encoding is done, rather the contents are simply
|
||||
re-interpreted.
|
||||
|
||||
```python
|
||||
from asn1crypto.core import BitString, OctetBitString, IntegerBitString
|
||||
|
||||
bit = BitString({
|
||||
0, 0, 0, 0, 0, 0, 0, 1,
|
||||
0, 0, 0, 0, 0, 0, 1, 0,
|
||||
})
|
||||
|
||||
# Will print (0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0)
|
||||
print(bit.native)
|
||||
|
||||
octet = bit.cast(OctetBitString)
|
||||
|
||||
# Will print b'\x01\x02'
|
||||
print(octet.native)
|
||||
|
||||
i = bit.cast(IntegerBitString)
|
||||
|
||||
# Will print 258
|
||||
print(i.native)
|
||||
```
|
||||
|
||||
## Basic Usage
|
||||
|
||||
All of the universal types implement four methods, a class method `.load()` and
|
||||
the instance methods `.dump()`, `.copy()` and `.debug()`.
|
||||
|
||||
`.load()` accepts a byte string of DER or BER encoded data and returns an
|
||||
object of the class it was called on. `.dump()` returns the serialization of
|
||||
an object into DER encoding.
|
||||
|
||||
```python
|
||||
from asn1crypto.core import Sequence
|
||||
|
||||
parsed = Sequence.load(der_byte_string)
|
||||
serialized = parsed.dump()
|
||||
```
|
||||
|
||||
By default, *asn1crypto* tries to be efficient and caches serialized data for
|
||||
better performance. If the input data is possibly BER encoded, but the output
|
||||
must be DER encoded, the `force` parameter may be used with `.dump()`.
|
||||
|
||||
```python
|
||||
from asn1crypto.core import Sequence
|
||||
|
||||
parsed = Sequence.load(der_byte_string)
|
||||
der_serialized = parsed.dump(force=True)
|
||||
```
|
||||
|
||||
The `.copy()` method creates a deep copy of an object, allowing child fields to
|
||||
be modified without affecting the original.
|
||||
|
||||
```python
|
||||
from asn1crypto.core import Sequence
|
||||
|
||||
seq1 = Sequence.load(der_byte_string)
|
||||
seq2 = seq1.copy()
|
||||
seq2[0] = seq1[0] + 1
|
||||
if seq1[0] != seq2[0]:
|
||||
print('Copies have distinct contents')
|
||||
```
|
||||
|
||||
The `.debug()` method is available to help in situations where interaction with
|
||||
another ASN.1 serializer or parsing is not functioning as expected. Calling
|
||||
this method will print a tree structure with information about the header bytes,
|
||||
class, method, tag, special tagging, content bytes, native Python value, child
|
||||
fields and any sub-parsed values.
|
||||
|
||||
```python
|
||||
from asn1crypto.core import Sequence
|
||||
|
||||
parsed = Sequence.load(der_byte_string)
|
||||
parsed.debug()
|
||||
```
|
||||
|
||||
In addition to the available methods, every instance has a `.native` property
|
||||
that converts the data into a native Python data type.
|
||||
|
||||
```python
|
||||
import pprint
|
||||
from asn1crypto.core import Sequence
|
||||
|
||||
parsed = Sequence.load(der_byte_string)
|
||||
pprint(parsed.native)
|
||||
```
|
||||
|
||||
## Sequence
|
||||
|
||||
One of the core structures when dealing with ASN.1 is the Sequence type. The
|
||||
`Sequence` class can handle field with universal data types, however in most
|
||||
situations the `_fields` property will need to be set with the expected
|
||||
definition of each field in the Sequence.
|
||||
|
||||
### Configuration
|
||||
|
||||
The `_fields` property must be set to a `list` of 2-3 element `tuple`s. The
|
||||
first element in the tuple must be a unicode string of the field name. The
|
||||
second must be a type class - either a universal type, or a custom type. The
|
||||
third, and optional, element is a `dict` with parameters to pass to the type
|
||||
class for things like default values, marking the field as optional, or
|
||||
implicit/explicit tagging.
|
||||
|
||||
```python
|
||||
from asn1crypto.core import Sequence, Integer, OctetString, IA5String
|
||||
|
||||
class MySequence(Sequence):
|
||||
_fields = [
|
||||
('field_one', Integer),
|
||||
('field_two', OctetString),
|
||||
('field_three', IA5String, {'optional': True}),
|
||||
]
|
||||
```
|
||||
|
||||
Implicit and explicit tagging will be covered in more detail later, however
|
||||
the following are options that can be set for each field type class:
|
||||
|
||||
- `{'default: 1}` sets the field's default value to `1`, allowing it to be
|
||||
omitted from the serialized form
|
||||
- `{'optional': True}` set the field to be optional, allowing it to be
|
||||
omitted
|
||||
|
||||
### Usage
|
||||
|
||||
To access values of the sequence, use dict-like access via `[]` and use the
|
||||
name of the field:
|
||||
|
||||
```python
|
||||
seq = MySequence.load(der_byte_string)
|
||||
print(seq['field_two'].native)
|
||||
```
|
||||
|
||||
The values of fields can be set by assigning via `[]`. If the value assigned is
|
||||
of the correct type class, it will be used as-is. If the value is not of the
|
||||
correct type class, a new instance of that type class will be created and the
|
||||
value will be passed to the constructor.
|
||||
|
||||
```python
|
||||
seq = MySequence.load(der_byte_string)
|
||||
# These statements will result in the same state
|
||||
seq['field_one'] = Integer(5)
|
||||
seq['field_one'] = 5
|
||||
```
|
||||
|
||||
When fields are complex types such as `Sequence` or `SequenceOf`, there is no
|
||||
way to construct the value out of a native Python data type.
|
||||
|
||||
### Optional Fields
|
||||
|
||||
When a field is configured via the `optional` parameter, not present in the
|
||||
`Sequence`, but accessed, the `VOID` object will be returned. This is an object
|
||||
that is serialized to an empty byte string and returns `None` when `.native` is
|
||||
accessed.
|
||||
|
||||
## Set
|
||||
|
||||
The `Set` class is configured in the same was as `Sequence`, however it allows
|
||||
serialized fields to be in any order, per the ASN.1 standard.
|
||||
|
||||
```python
|
||||
from asn1crypto.core import Set, Integer, OctetString, IA5String
|
||||
|
||||
class MySet(Set):
|
||||
_fields = [
|
||||
('field_one', Integer),
|
||||
('field_two', OctetString),
|
||||
('field_three', IA5String, {'optional': True}),
|
||||
]
|
||||
```
|
||||
|
||||
## SequenceOf
|
||||
|
||||
The `SequenceOf` class is used to allow for zero or more instances of a type.
|
||||
The class uses the `_child_spec` property to define the instance class type.
|
||||
|
||||
```python
|
||||
from asn1crypto.core import SequenceOf, Integer
|
||||
|
||||
class Integers(SequenceOf):
|
||||
_child_spec = Integer
|
||||
```
|
||||
|
||||
Values in the `SequenceOf` can be accessed via `[]` with an integer key. The
|
||||
length of the `SequenceOf` is determined via `len()`.
|
||||
|
||||
```python
|
||||
values = Integers.load(der_byte_string)
|
||||
for i in range(0, len(values)):
|
||||
print(values[i].native)
|
||||
```
|
||||
|
||||
## SetOf
|
||||
|
||||
The `SetOf` class is an exact duplicate of `SequenceOf`. According to the ASN.1
|
||||
standard, the difference is that a `SequenceOf` is explicitly ordered, however
|
||||
`SetOf` may be in any order. This is an equivalent comparison of a Python `list`
|
||||
and `set`.
|
||||
|
||||
```python
|
||||
from asn1crypto.core import SetOf, Integer
|
||||
|
||||
class Integers(SetOf):
|
||||
_child_spec = Integer
|
||||
```
|
||||
|
||||
## Integer
|
||||
|
||||
The `Integer` class allows values to be *named*. An `Integer` with named values
|
||||
may contain any integer, however special values with named will be represented
|
||||
as those names when `.native` is called.
|
||||
|
||||
Named values are configured via the `_map` property, which must be a `dict`
|
||||
with the keys being integers and the values being unicode strings.
|
||||
|
||||
```python
|
||||
from asn1crypto.core import Integer
|
||||
|
||||
class Version(Integer):
|
||||
_map = {
|
||||
1: 'v1',
|
||||
2: 'v2',
|
||||
}
|
||||
|
||||
# Will print: "v1"
|
||||
print(Version(1).native)
|
||||
|
||||
# Will print: 4
|
||||
print(Version(4).native)
|
||||
```
|
||||
|
||||
## Enumerated
|
||||
|
||||
The `Enumerated` class is almost identical to `Integer`, however only values in
|
||||
the `_map` property are valid.
|
||||
|
||||
```python
|
||||
from asn1crypto.core import Enumerated
|
||||
|
||||
class Version(Enumerated):
|
||||
_map = {
|
||||
1: 'v1',
|
||||
2: 'v2',
|
||||
}
|
||||
|
||||
# Will print: "v1"
|
||||
print(Version(1).native)
|
||||
|
||||
# Will raise a ValueError exception
|
||||
print(Version(4).native)
|
||||
```
|
||||
|
||||
## ObjectIdentifier
|
||||
|
||||
The `ObjectIdentifier` class represents values of the ASN.1 type of the same
|
||||
name. `ObjectIdentifier` instances are converted to a unicode string in a
|
||||
dotted-integer format when `.native` is accessed.
|
||||
|
||||
While this standard conversion is a reasonable baseline, in most situations
|
||||
it will be more maintainable to map the OID strings to a unicode string
|
||||
containing a description of what the OID repesents.
|
||||
|
||||
The mapping of OID strings to name strings is configured via the `_map`
|
||||
property, which is a `dict` object with keys being unicode OID string and the
|
||||
values being a unicode string.
|
||||
|
||||
The `.dotted` attribute will always return a unicode string of the dotted
|
||||
integer form of the OID.
|
||||
|
||||
The class methods `.map()` and `.unmap()` will convert a dotted integer unicode
|
||||
string to the user-friendly name, and vice-versa.
|
||||
|
||||
```python
|
||||
from asn1crypto.core import ObjectIdentifier
|
||||
|
||||
class MyType(ObjectIdentifier):
|
||||
_map = {
|
||||
'1.8.2.1.23': 'value_name',
|
||||
'1.8.2.1.24': 'other_value',
|
||||
}
|
||||
|
||||
# Will print: "value_name"
|
||||
print(MyType('1.8.2.1.23').native)
|
||||
|
||||
# Will print: "1.8.2.1.23"
|
||||
print(MyType('1.8.2.1.23').dotted)
|
||||
|
||||
# Will print: "1.8.2.1.25"
|
||||
print(MyType('1.8.2.1.25').native)
|
||||
|
||||
# Will print "value_name"
|
||||
print(MyType.map('1.8.2.1.23'))
|
||||
|
||||
# Will print "1.8.2.1.23"
|
||||
print(MyType.unmap('value_name'))
|
||||
```
|
||||
|
||||
## BitString
|
||||
|
||||
When no `_map` is set for a `BitString` class, the native representation is a
|
||||
`tuple` of `int`s (being either `1` or `0`).
|
||||
|
||||
```python
|
||||
from asn1crypto.core import BitString
|
||||
|
||||
b1 = BitString((1, 0, 1))
|
||||
```
|
||||
|
||||
Additionally, it is possible to set the `_map` property to a dict where the
|
||||
keys are bit indexes and the values are unicode string names. This allows
|
||||
checking the value of a given bit by item access, and the native representation
|
||||
becomes a `set` of unicode strings.
|
||||
|
||||
```python
|
||||
from asn1crypto.core import BitString
|
||||
|
||||
class MyFlags(BitString):
|
||||
_map = {
|
||||
0: 'edit',
|
||||
1: 'delete',
|
||||
2: 'manage_users',
|
||||
}
|
||||
|
||||
permissions = MyFlags({'edit', 'delete'})
|
||||
|
||||
# This will be printed
|
||||
if permissions['edit'] and permissions['delete']:
|
||||
print('Can edit and delete')
|
||||
|
||||
# This will not
|
||||
if 'manage_users' in permissions.native:
|
||||
print('Is admin')
|
||||
```
|
||||
|
||||
## Strings
|
||||
|
||||
ASN.1 contains quite a number of string types:
|
||||
|
||||
| Type | Standard Encoding | Implementation Encoding | Notes |
|
||||
| ----------------- | --------------------------------- | ----------------------- | ------------------------------------------------------------------------- |
|
||||
| `UTF8String` | UTF-8 | UTF-8 | |
|
||||
| `NumericString` | ASCII `[0-9 ]` | ISO 8859-1 | The implementation is a superset of supported characters |
|
||||
| `PrintableString` | ASCII `[a-zA-Z0-9 '()+,\\-./:=?]` | ISO 8859-1 | The implementation is a superset of supported characters |
|
||||
| `TeletexString` | ITU T.61 | Custom | The implementation is based off of https://en.wikipedia.org/wiki/ITU_T.61 |
|
||||
| `VideotexString` | *?* | *None* | This has no set encoding, and it not used in cryptography |
|
||||
| `IA5String` | ITU T.50 (very similar to ASCII) | ISO 8859-1 | The implementation is a superset of supported characters |
|
||||
| `GraphicString` | * | ISO 8859-1 | This has not set encoding, but seems to often contain ISO 8859-1 |
|
||||
| `VisibleString` | ASCII (printable) | ISO 8859-1 | The implementation is a superset of supported characters |
|
||||
| `GeneralString` | * | ISO 8859-1 | This has not set encoding, but seems to often contain ISO 8859-1 |
|
||||
| `UniversalString` | UTF-32 | UTF-32 | |
|
||||
| `CharacterString` | * | ISO 8859-1 | This has not set encoding, but seems to often contain ISO 8859-1 |
|
||||
| `BMPString` | UTF-16 | UTF-16 | |
|
||||
|
||||
As noted in the table above, many of the implementations are supersets of the
|
||||
supported characters. This simplifies parsing, but puts the onus of using valid
|
||||
characters on the developer. However, in general `UTF8String`, `BMPString` or
|
||||
`UniversalString` should be preferred when a choice is given.
|
||||
|
||||
All string types other than `VideotexString` are created from unicode strings.
|
||||
|
||||
```python
|
||||
from asn1crypto.core import IA5String
|
||||
|
||||
print(IA5String('Testing!').native)
|
||||
```
|
||||
|
||||
## UTCTime
|
||||
|
||||
The class `UTCTime` accepts a unicode string in one of the formats:
|
||||
|
||||
- `%y%m%d%H%MZ`
|
||||
- `%y%m%d%H%M%SZ`
|
||||
- `%y%m%d%H%M%z`
|
||||
- `%y%m%d%H%M%S%z`
|
||||
|
||||
or a `datetime.datetime` instance. See the
|
||||
[Python datetime strptime() reference](https://docs.python.org/3/library/datetime.html#strftime-and-strptime-behavior)
|
||||
for details of the formats.
|
||||
|
||||
When `.native` is accessed, it returns a `datetime.datetime` object with a
|
||||
`tzinfo` of `asn1crypto.util.timezone.utc`.
|
||||
|
||||
## GeneralizedTime
|
||||
|
||||
The class `GeneralizedTime` accepts a unicode string in one of the formats:
|
||||
|
||||
- `%Y%m%d%H`
|
||||
- `%Y%m%d%H%M`
|
||||
- `%Y%m%d%H%M%S`
|
||||
- `%Y%m%d%H%M%S.%f`
|
||||
- `%Y%m%d%HZ`
|
||||
- `%Y%m%d%H%MZ`
|
||||
- `%Y%m%d%H%M%SZ`
|
||||
- `%Y%m%d%H%M%S.%fZ`
|
||||
- `%Y%m%d%H%z`
|
||||
- `%Y%m%d%H%M%z`
|
||||
- `%Y%m%d%H%M%S%z`
|
||||
- `%Y%m%d%H%M%S.%f%z`
|
||||
|
||||
or a `datetime.datetime` instance. See the
|
||||
[Python datetime strptime() reference](https://docs.python.org/3/library/datetime.html#strftime-and-strptime-behavior)
|
||||
for details of the formats.
|
||||
|
||||
When `.native` is accessed, it returns a `datetime.datetime` object with a
|
||||
`tzinfo` of `asn1crypto.util.timezone.utc`. For formats where the time has a
|
||||
timezone offset is specified (`[+-]\d{4}`), the time is converted to UTC. For
|
||||
times without a timezone, the time is assumed to be in UTC.
|
||||
|
||||
## Choice
|
||||
|
||||
The `Choice` class allows handling ASN.1 Choice structures. The `_alternatives`
|
||||
property must be set to a `list` containing 2-3 element `tuple`s. The first
|
||||
element in the tuple is the alternative name. The second element is the type
|
||||
class for the alternative. The, optional, third element is a `dict` of
|
||||
parameters to pass to the type class constructor. This is used primarily for
|
||||
implicit and explicit tagging.
|
||||
|
||||
```python
|
||||
from asn1crypto.core import Choice, Integer, OctetString, IA5String
|
||||
|
||||
class MyChoice(Choice):
|
||||
_alternatives = [
|
||||
('option_one', Integer),
|
||||
('option_two', OctetString),
|
||||
('option_three', IA5String),
|
||||
]
|
||||
```
|
||||
|
||||
`Choice` objects has two extra properties, `.name` and `.chosen`. The `.name`
|
||||
property contains the name of the chosen alternative. The `.chosen` property
|
||||
contains the instance of the chosen type class.
|
||||
|
||||
```python
|
||||
parsed = MyChoice.load(der_bytes)
|
||||
print(parsed.name)
|
||||
print(type(parsed.chosen))
|
||||
```
|
||||
|
||||
The `.native` property and `.dump()` method work as with the universal type
|
||||
classes. Under the hood they just proxy the calls to the `.chosen` object.
|
||||
|
||||
## Any
|
||||
|
||||
The `Any` class implements the ASN.1 Any type, which allows any data type. By
|
||||
default objects of this class do not perform any parsing. However, the
|
||||
`.parse()` instance method allows parsing the contents of the `Any` object,
|
||||
either into a universal type, or to a specification pass in via the `spec`
|
||||
parameter.
|
||||
|
||||
This type is not used as a top-level structure, but instead allows `Sequence`
|
||||
and `Set` objects to accept varying contents, usually based on some sort of
|
||||
`ObjectIdentifier`.
|
||||
|
||||
```python
|
||||
from asn1crypto.core import Sequence, ObjectIdentifier, Any, Integer, OctetString
|
||||
|
||||
class MySequence(Sequence):
|
||||
_fields = [
|
||||
('type', ObjectIdentifier),
|
||||
('value', Any),
|
||||
]
|
||||
```
|
||||
|
||||
## Specification via OID
|
||||
|
||||
Throughout the usage of ASN.1 in cryptography, a pattern is present where an
|
||||
`ObjectIdenfitier` is used to determine what specification should be used to
|
||||
interpret another field in a `Sequence`. Usually the other field is an instance
|
||||
of `Any`, however occasionally it is an `OctetString` or `OctetBitString`.
|
||||
|
||||
*asn1crypto* provides the `_oid_pair` and `_oid_specs` properties of the
|
||||
`Sequence` class to allow handling these situations.
|
||||
|
||||
The `_oid_pair` is a tuple with two unicode string elements. The first is the
|
||||
name of the field that is an `ObjectIdentifier` and the second if the name of
|
||||
the field that has a variable specification based on the first field. *In
|
||||
situations where the value field should be an `OctetString` or `OctetBitString`,
|
||||
`ParsableOctetString` and `ParsableOctetBitString` will need to be used instead
|
||||
to allow for the sub-parsing of the contents.*
|
||||
|
||||
The `_oid_specs` property is a `dict` object with `ObjectIdentifier` values as
|
||||
the keys (either dotted or mapped notation) and a type class as the value. When
|
||||
the first field in `_oid_pair` has a value equal to one of the keys in
|
||||
`_oid_specs`, then the corresponding type class will be used as the
|
||||
specification for the second field of `_oid_pair`.
|
||||
|
||||
```python
|
||||
from asn1crypto.core import Sequence, ObjectIdentifier, Any, OctetString, Integer
|
||||
|
||||
class MyId(ObjectIdentifier):
|
||||
_map = {
|
||||
'1.2.3.4': 'initialization_vector',
|
||||
'1.2.3.5': 'iterations',
|
||||
}
|
||||
|
||||
class MySequence(Sequence):
|
||||
_fields = [
|
||||
('type', MyId),
|
||||
('value', Any),
|
||||
]
|
||||
|
||||
_oid_pair = ('type', 'value')
|
||||
_oid_specs = {
|
||||
'initialization_vector': OctetString,
|
||||
'iterations': Integer,
|
||||
}
|
||||
```
|
||||
|
||||
## Explicit and Implicit Tagging
|
||||
|
||||
When working with `Sequence`, `Set` and `Choice` it is often necessary to
|
||||
disambiguate between fields because of a number of factors:
|
||||
|
||||
- In `Sequence` the presence of an optional field must be determined by tag number
|
||||
- In `Set`, each field must have a different tag number since they can be in any order
|
||||
- In `Choice`, each alternative must have a different tag number to determine which is present
|
||||
|
||||
The universal types all have unique tag numbers. However, if a `Sequence`, `Set`
|
||||
or `Choice` has more than one field with the same universal type, tagging allows
|
||||
a way to keep the semantics of the original type, but with a different tag
|
||||
number.
|
||||
|
||||
Implicit tagging simply changes the tag number of a type to a different value.
|
||||
However, Explicit tagging wraps the existing type in another tag with the
|
||||
specified tag number.
|
||||
|
||||
In general, most situations allow for implicit tagging, with the notable
|
||||
exception than a field that is a `Choice` type must always be explicitly tagged.
|
||||
Otherwise, using implicit tagging would modify the tag of the chosen
|
||||
alternative, breaking the mechanism by which `Choice` works.
|
||||
|
||||
Here is an example of implicit and explicit tagging where explicit tagging on
|
||||
the `Sequence` allows a `Choice` type field to be optional, and where implicit
|
||||
tagging in the `Choice` structure allows disambiguating between two string of
|
||||
the same type.
|
||||
|
||||
```python
|
||||
from asn1crypto.core import Sequence, Choice, IA5String, UTCTime, ObjectIdentifier
|
||||
|
||||
class Person(Choice):
|
||||
_alternatives = [
|
||||
('name', IA5String),
|
||||
('email', IA5String, {'implicit': 0}),
|
||||
]
|
||||
|
||||
class Record(Sequence):
|
||||
_fields = [
|
||||
('id', ObjectIdentifier),
|
||||
('created', UTCTime),
|
||||
('creator', Person, {'explicit': 0, 'optional': True}),
|
||||
]
|
||||
```
|
||||
|
||||
As is shown above, the keys `implicit` and `explicit` are used for tagging,
|
||||
and are passed to a type class constructor via the optional third element of
|
||||
a field or alternative tuple. Both parameters may be an integer tag number, or
|
||||
a 2-element tuple of string class name and integer tag.
|
||||
|
||||
If a tagging value needs its tagging changed, the `.untag()` method can be used
|
||||
to create a copy of the object without explicit/implicit tagging. The `.retag()`
|
||||
method can be used to change the tagging. This method accepts one parameter, a
|
||||
dict with either or both of the keys `implicit` and `explicit`.
|
||||
|
||||
```python
|
||||
person = Person(name='email', value='will@wbond.net')
|
||||
|
||||
# Will display True
|
||||
print(person.implicit)
|
||||
|
||||
# Will display False
|
||||
print(person.untag().implicit)
|
||||
|
||||
# Will display 0
|
||||
print(person.tag)
|
||||
|
||||
# Will display 1
|
||||
print(person.retag({'implicit': 1}).tag)
|
||||
```
|
|
@ -0,0 +1,232 @@
|
|||
# asn1crypto
|
||||
|
||||
A fast, pure Python library for parsing and serializing ASN.1 structures.
|
||||
|
||||
- [Features](#features)
|
||||
- [Why Another Python ASN.1 Library?](#why-another-python-asn1-library)
|
||||
- [Related Crypto Libraries](#related-crypto-libraries)
|
||||
- [Current Release](#current-release)
|
||||
- [Dependencies](#dependencies)
|
||||
- [Installation](#installation)
|
||||
- [License](#license)
|
||||
- [Documentation](#documentation)
|
||||
- [Continuous Integration](#continuous-integration)
|
||||
- [Testing](#testing)
|
||||
- [Development](#development)
|
||||
|
||||
[![Travis CI](https://api.travis-ci.org/wbond/asn1crypto.svg?branch=master)](https://travis-ci.org/wbond/asn1crypto)
|
||||
[![AppVeyor](https://ci.appveyor.com/api/projects/status/github/wbond/asn1crypto?branch=master&svg=true)](https://ci.appveyor.com/project/wbond/asn1crypto)
|
||||
[![CircleCI](https://circleci.com/gh/wbond/asn1crypto.svg?style=shield)](https://circleci.com/gh/wbond/asn1crypto)
|
||||
[![Codecov](https://codecov.io/gh/wbond/asn1crypto/branch/master/graph/badge.svg)](https://codecov.io/gh/wbond/asn1crypto)
|
||||
[![PyPI](https://img.shields.io/pypi/v/asn1crypto.svg)](https://pypi.python.org/pypi/asn1crypto)
|
||||
|
||||
## Features
|
||||
|
||||
In addition to an ASN.1 BER/DER decoder and DER serializer, the project includes
|
||||
a bunch of ASN.1 structures for use with various common cryptography standards:
|
||||
|
||||
| Standard | Module | Source |
|
||||
| ---------------------- | ------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
|
||||
| X.509 | [`asn1crypto.x509`](asn1crypto/x509.py) | [RFC 5280](https://tools.ietf.org/html/rfc5280) |
|
||||
| CRL | [`asn1crypto.crl`](asn1crypto/crl.py) | [RFC 5280](https://tools.ietf.org/html/rfc5280) |
|
||||
| CSR | [`asn1crypto.csr`](asn1crypto/csr.py) | [RFC 2986](https://tools.ietf.org/html/rfc2986), [RFC 2985](https://tools.ietf.org/html/rfc2985) |
|
||||
| OCSP | [`asn1crypto.ocsp`](asn1crypto/ocsp.py) | [RFC 6960](https://tools.ietf.org/html/rfc6960) |
|
||||
| PKCS#12 | [`asn1crypto.pkcs12`](asn1crypto/pkcs12.py) | [RFC 7292](https://tools.ietf.org/html/rfc7292) |
|
||||
| PKCS#8 | [`asn1crypto.keys`](asn1crypto/keys.py) | [RFC 5208](https://tools.ietf.org/html/rfc5208) |
|
||||
| PKCS#1 v2.1 (RSA keys) | [`asn1crypto.keys`](asn1crypto/keys.py) | [RFC 3447](https://tools.ietf.org/html/rfc3447) |
|
||||
| DSA keys | [`asn1crypto.keys`](asn1crypto/keys.py) | [RFC 3279](https://tools.ietf.org/html/rfc3279) |
|
||||
| Elliptic curve keys | [`asn1crypto.keys`](asn1crypto/keys.py) | [SECG SEC1 V2](http://www.secg.org/sec1-v2.pdf) |
|
||||
| PKCS#3 v1.4 | [`asn1crypto.algos`](asn1crypto/algos.py) | [PKCS#3 v1.4](ftp://ftp.rsasecurity.com/pub/pkcs/ascii/pkcs-3.asc) |
|
||||
| PKCS#5 v2.1 | [`asn1crypto.algos`](asn1crypto/algos.py) | [PKCS#5 v2.1](http://www.emc.com/collateral/white-papers/h11302-pkcs5v2-1-password-based-cryptography-standard-wp.pdf) |
|
||||
| CMS (and PKCS#7) | [`asn1crypto.cms`](asn1crypto/cms.py) | [RFC 5652](https://tools.ietf.org/html/rfc5652), [RFC 2315](https://tools.ietf.org/html/rfc2315) |
|
||||
| TSP | [`asn1crypto.tsp`](asn1crypto/tsp.py) | [RFC 3161](https://tools.ietf.org/html/rfc3161) |
|
||||
| PDF signatures | [`asn1crypto.pdf`](asn1crypto/pdf.py) | [PDF 1.7](http://wwwimages.adobe.com/content/dam/Adobe/en/devnet/pdf/pdfs/PDF32000_2008.pdf) |
|
||||
|
||||
## Why Another Python ASN.1 Library?
|
||||
|
||||
Python has long had the [pyasn1](https://pypi.python.org/pypi/pyasn1) and
|
||||
[pyasn1_modules](https://pypi.python.org/pypi/pyasn1-modules) available for
|
||||
parsing and serializing ASN.1 structures. While the project does include a
|
||||
comprehensive set of tools for parsing and serializing, the performance of the
|
||||
library can be very poor, especially when dealing with bit fields and parsing
|
||||
large structures such as CRLs.
|
||||
|
||||
After spending extensive time using *pyasn1*, the following issues were
|
||||
identified:
|
||||
|
||||
1. Poor performance
|
||||
2. Verbose, non-pythonic API
|
||||
3. Out-dated and incomplete definitions in *pyasn1-modules*
|
||||
4. No simple way to map data to native Python data structures
|
||||
5. No mechanism for overridden universal ASN.1 types
|
||||
|
||||
The *pyasn1* API is largely method driven, and uses extensive configuration
|
||||
objects and lowerCamelCase names. There were no consistent options for
|
||||
converting types of native Python data structures. Since the project supports
|
||||
out-dated versions of Python, many newer language features are unavailable
|
||||
for use.
|
||||
|
||||
Time was spent trying to profile issues with the performance, however the
|
||||
architecture made it hard to pin down the primary source of the poor
|
||||
performance. Attempts were made to improve performance by utilizing unreleased
|
||||
patches and delaying parsing using the `Any` type. Even with such changes, the
|
||||
performance was still unacceptably slow.
|
||||
|
||||
Finally, a number of structures in the cryptographic space use universal data
|
||||
types such as `BitString` and `OctetString`, but interpret the data as other
|
||||
types. For instance, signatures are really byte strings, but are encoded as
|
||||
`BitString`. Elliptic curve keys use both `BitString` and `OctetString` to
|
||||
represent integers. Parsing these structures as the base universal types and
|
||||
then re-interpreting them wastes computation.
|
||||
|
||||
*asn1crypto* uses the following techniques to improve performance, especially
|
||||
when extracting one or two fields from large, complex structures:
|
||||
|
||||
- Delayed parsing of byte string values
|
||||
- Persistence of original ASN.1 encoded data until a value is changed
|
||||
- Lazy loading of child fields
|
||||
- Utilization of high-level Python stdlib modules
|
||||
|
||||
While there is no extensive performance test suite, the
|
||||
`CRLTests.test_parse_crl` test case was used to parse a 21MB CRL file on a
|
||||
late 2013 rMBP. *asn1crypto* parsed the certificate serial numbers in just
|
||||
under 8 seconds. With *pyasn1*, using definitions from *pyasn1-modules*, the
|
||||
same parsing took over 4,100 seconds.
|
||||
|
||||
For smaller structures the performance difference can range from a few times
|
||||
faster to an order of magnitude of more.
|
||||
|
||||
## Related Crypto Libraries
|
||||
|
||||
*asn1crypto* is part of the modularcrypto family of Python packages:
|
||||
|
||||
- [asn1crypto](https://github.com/wbond/asn1crypto)
|
||||
- [oscrypto](https://github.com/wbond/oscrypto)
|
||||
- [csrbuilder](https://github.com/wbond/csrbuilder)
|
||||
- [certbuilder](https://github.com/wbond/certbuilder)
|
||||
- [crlbuilder](https://github.com/wbond/crlbuilder)
|
||||
- [ocspbuilder](https://github.com/wbond/ocspbuilder)
|
||||
- [certvalidator](https://github.com/wbond/certvalidator)
|
||||
|
||||
## Current Release
|
||||
|
||||
0.24.0 - [changelog](changelog.md)
|
||||
|
||||
## Dependencies
|
||||
|
||||
Python 2.6, 2.7, 3.2, 3.3, 3.4, 3.5, 3.6 or pypy. *No third-party packages
|
||||
required.*
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
pip install asn1crypto
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
*asn1crypto* is licensed under the terms of the MIT license. See the
|
||||
[LICENSE](LICENSE) file for the exact license text.
|
||||
|
||||
## Documentation
|
||||
|
||||
The documentation for *asn1crypto* is composed of tutorials on basic usage and
|
||||
links to the source for the various pre-defined type classes.
|
||||
|
||||
### Tutorials
|
||||
|
||||
- [Universal Types with BER/DER Decoder and DER Encoder](docs/universal_types.md)
|
||||
- [PEM Encoder and Decoder](docs/pem.md)
|
||||
|
||||
### Reference
|
||||
|
||||
- [Universal types](asn1crypto/core.py), `asn1crypto.core`
|
||||
- [Digest, HMAC, signed digest and encryption algorithms](asn1crypto/algos.py), `asn1crypto.algos`
|
||||
- [Private and public keys](asn1crypto/keys.py), `asn1crypto.keys`
|
||||
- [X509 certificates](asn1crypto/x509.py), `asn1crypto.x509`
|
||||
- [Certificate revocation lists (CRLs)](asn1crypto/crl.py), `asn1crypto.crl`
|
||||
- [Online certificate status protocol (OCSP)](asn1crypto/ocsp.py), `asn1crypto.ocsp`
|
||||
- [Certificate signing requests (CSRs)](asn1crypto/csr.py), `asn1crypto.csr`
|
||||
- [Private key/certificate containers (PKCS#12)](asn1crypto/pkcs12.py), `asn1crypto.pkcs12`
|
||||
- [Cryptographic message syntax (CMS, PKCS#7)](asn1crypto/cms.py), `asn1crypto.cms`
|
||||
- [Time stamp protocol (TSP)](asn1crypto/tsp.py), `asn1crypto.tsp`
|
||||
- [PDF signatures](asn1crypto/pdf.py), `asn1crypto.pdf`
|
||||
|
||||
## Continuous Integration
|
||||
|
||||
- [Windows](https://ci.appveyor.com/project/wbond/asn1crypto/history) via AppVeyor
|
||||
- [OS X](https://circleci.com/gh/wbond/asn1crypto) via CircleCI
|
||||
- [Linux](https://travis-ci.org/wbond/asn1crypto/builds) via Travis CI
|
||||
- [Test Coverage](https://codecov.io/gh/wbond/asn1crypto/commits) via Codecov
|
||||
|
||||
## Testing
|
||||
|
||||
Tests are written using `unittest` and require no third-party packages:
|
||||
|
||||
```bash
|
||||
python run.py tests
|
||||
```
|
||||
|
||||
To run only some tests, pass a regular expression as a parameter to `tests`.
|
||||
|
||||
```bash
|
||||
python run.py tests ocsp
|
||||
```
|
||||
|
||||
## Development
|
||||
|
||||
To install the package used for linting, execute:
|
||||
|
||||
```bash
|
||||
pip install --user -r requires/lint
|
||||
```
|
||||
|
||||
The following command will run the linter:
|
||||
|
||||
```bash
|
||||
python run.py lint
|
||||
```
|
||||
|
||||
Support for code coverage can be installed via:
|
||||
|
||||
```bash
|
||||
pip install --user -r requires/coverage
|
||||
```
|
||||
|
||||
Coverage is measured by running:
|
||||
|
||||
```bash
|
||||
python run.py coverage
|
||||
```
|
||||
|
||||
To install the necessary packages for releasing a new version on PyPI, run:
|
||||
|
||||
```bash
|
||||
pip install --user -r requires/release
|
||||
```
|
||||
|
||||
Releases are created by:
|
||||
|
||||
- Making a git tag in [semver](http://semver.org/) format
|
||||
- Running the command:
|
||||
|
||||
```bash
|
||||
python run.py release
|
||||
```
|
||||
|
||||
Existing releases can be found at https://pypi.python.org/pypi/asn1crypto.
|
||||
|
||||
## CI Tasks
|
||||
|
||||
A task named `deps` exists to ensure a modern version of `pip` is installed,
|
||||
along with all necessary testing dependencies.
|
||||
|
||||
The `ci` task runs `lint` (if flake8 is available for the version of Python) and
|
||||
`coverage` (or `tests` if coverage is not available for the version of Python).
|
||||
If the current directory is a clean git working copy, the coverage data is
|
||||
submitted to codecov.io.
|
||||
|
||||
```bash
|
||||
python run.py deps
|
||||
python run.py ci
|
||||
```
|
|
@ -0,0 +1,81 @@
|
|||
import os
|
||||
import shutil
|
||||
|
||||
from setuptools import setup, find_packages, Command
|
||||
|
||||
from asn1crypto import version
|
||||
|
||||
|
||||
class CleanCommand(Command):
|
||||
user_options = [
|
||||
('all', 'a', '(Compatibility with original clean command)'),
|
||||
]
|
||||
|
||||
def initialize_options(self):
|
||||
self.all = False
|
||||
|
||||
def finalize_options(self):
|
||||
pass
|
||||
|
||||
def run(self):
|
||||
folder = os.path.dirname(os.path.abspath(__file__))
|
||||
for sub_folder in ['build', 'dist', 'asn1crypto.egg-info']:
|
||||
full_path = os.path.join(folder, sub_folder)
|
||||
if os.path.exists(full_path):
|
||||
shutil.rmtree(full_path)
|
||||
for root, dirnames, filenames in os.walk(os.path.join(folder, 'asn1crypto')):
|
||||
for filename in filenames:
|
||||
if filename[-4:] == '.pyc':
|
||||
os.unlink(os.path.join(root, filename))
|
||||
for dirname in list(dirnames):
|
||||
if dirname == '__pycache__':
|
||||
shutil.rmtree(os.path.join(root, dirname))
|
||||
|
||||
|
||||
setup(
|
||||
name='asn1crypto',
|
||||
version=version.__version__,
|
||||
|
||||
description=(
|
||||
'Fast ASN.1 parser and serializer with definitions for private keys, '
|
||||
'public keys, certificates, CRL, OCSP, CMS, PKCS#3, PKCS#7, PKCS#8, '
|
||||
'PKCS#12, PKCS#5, X.509 and TSP'
|
||||
),
|
||||
long_description='Docs for this project are maintained at https://github.com/wbond/asn1crypto#readme.',
|
||||
|
||||
url='https://github.com/wbond/asn1crypto',
|
||||
|
||||
author='wbond',
|
||||
author_email='will@wbond.net',
|
||||
|
||||
license='MIT',
|
||||
|
||||
classifiers=[
|
||||
'Development Status :: 4 - Beta',
|
||||
|
||||
'Intended Audience :: Developers',
|
||||
|
||||
'License :: OSI Approved :: MIT License',
|
||||
|
||||
'Programming Language :: Python :: 2.6',
|
||||
'Programming Language :: Python :: 2.7',
|
||||
'Programming Language :: Python :: 3.2',
|
||||
'Programming Language :: Python :: 3.3',
|
||||
'Programming Language :: Python :: 3.4',
|
||||
'Programming Language :: Python :: 3.5',
|
||||
'Programming Language :: Python :: 3.6',
|
||||
'Programming Language :: Python :: Implementation :: PyPy',
|
||||
|
||||
'Topic :: Security :: Cryptography',
|
||||
],
|
||||
|
||||
keywords='asn1 crypto pki x509 certificate rsa dsa ec dh',
|
||||
|
||||
packages=find_packages(exclude=['tests*', 'dev*']),
|
||||
|
||||
test_suite='tests.make_suite',
|
||||
|
||||
cmdclass={
|
||||
'clean': CleanCommand,
|
||||
}
|
||||
)
|
Loading…
Reference in New Issue