shell bypass 403
曾与蒿藜同雨露,한때 잡초와 쑥과 함께 비와 이슬을 나누던 곳이 이제는 소나무와 삼나무와 함께 서리와 눈을 견뎌내고 있다.终随松柏到冰霜.かつては雑草やヨモギと共に雨や露を分かち合っていたが、今では松やヒノキと共に霜や雪に耐えている。曾与蒿藜同雨露,Once sharing rain and dew with weeds and wormwood, now enduring frost and snow with pines and cypresses.终随松柏到冰霜.曾与蒿藜同雨露한때 잡초와 쑥과 함께 비와 이슬을 나누던 곳이 이제는 소나무와 삼나무와 함께 서리와 눈을 견뎌내고 있다.,终随松柏到冰霜.譖セ荳手珍阯懷酔髮ィ髴イ�檎サ磯囂譚セ譟丞芦蜀ー髴�曾与蒿藜同雨露,鏇句笌钂胯棞鍚岄洦闇诧紝缁堥殢鏉炬煆鍒板啺闇�终随松柏到冰霜.曾与蒿藜同雨露,한때 잡초와 쑥과 함께 비와 이슬을 나누던 곳이 이제는 소나무와 삼나무와 함께 서리와 눈을 견뎌내고 있다.终随松柏到冰霜.曾与蒿藜同雨露,终随松柏到冰霜.
# coding: utf-8
"""
ASN.1 type classes for X.509 certificates. Exports the following items:
- Attributes()
- Certificate()
- Extensions()
- GeneralName()
- GeneralNames()
- Name()
Other type classes are defined that help compose the types listed above.
"""
from __future__ import unicode_literals, division, absolute_import, print_function
from encodings import idna # noqa
import hashlib
import re
import socket
import stringprep
import sys
import unicodedata
from ._errors import unwrap
from ._iri import iri_to_uri, uri_to_iri
from ._ordereddict import OrderedDict
from ._types import type_name, str_cls, bytes_to_list
from .algos import AlgorithmIdentifier, SignedDigestAlgorithm
from .core import (
Any,
BitString,
BMPString,
Boolean,
Choice,
Concat,
GeneralizedTime,
GeneralString,
IA5String,
Integer,
Null,
NumericString,
ObjectIdentifier,
OctetBitString,
OctetString,
ParsableOctetString,
PrintableString,
Sequence,
SequenceOf,
Set,
SetOf,
TeletexString,
UniversalString,
UTCTime,
UTF8String,
VisibleString,
VOID,
)
from .keys import PublicKeyInfo
from .util import int_to_bytes, int_from_bytes, inet_ntop, inet_pton
# The structures in this file are taken from https://tools.ietf.org/html/rfc5280
# and a few other supplementary sources, mostly due to extra supported
# extension and name OIDs
class DNSName(IA5String):
_encoding = 'idna'
_bad_tag = 19
def __ne__(self, other):
return not self == other
def __eq__(self, other):
"""
Equality as defined by https://tools.ietf.org/html/rfc5280#section-7.2
:param other:
Another DNSName object
:return:
A boolean
"""
if not isinstance(other, DNSName):
return False
return self.__unicode__().lower() == other.__unicode__().lower()
def set(self, value):
"""
Sets the value of the DNS name
:param value:
A unicode string
"""
if not isinstance(value, str_cls):
raise TypeError(unwrap(
'''
%s value must be a unicode string, not %s
''',
type_name(self),
type_name(value)
))
if value.startswith('.'):
encoded_value = b'.' + value[1:].encode(self._encoding)
else:
encoded_value = value.encode(self._encoding)
self._unicode = value
self.contents = encoded_value
self._header = None
if self._trailer != b'':
self._trailer = b''
class URI(IA5String):
def set(self, value):
"""
Sets the value of the string
:param value:
A unicode string
"""
if not isinstance(value, str_cls):
raise TypeError(unwrap(
'''
%s value must be a unicode string, not %s
''',
type_name(self),
type_name(value)
))
self._unicode = value
self.contents = iri_to_uri(value)
self._header = None
if self._trailer != b'':
self._trailer = b''
def __ne__(self, other):
return not self == other
def __eq__(self, other):
"""
Equality as defined by https://tools.ietf.org/html/rfc5280#section-7.4
:param other:
Another URI object
:return:
A boolean
"""
if not isinstance(other, URI):
return False
return iri_to_uri(self.native) == iri_to_uri(other.native)
def __unicode__(self):
"""
:return:
A unicode string
"""
if self.contents is None:
return ''
if self._unicode is None:
self._unicode = uri_to_iri(self._merge_chunks())
return self._unicode
class EmailAddress(IA5String):
_contents = None
# If the value has gone through the .set() method, thus normalizing it
_normalized = False
@property
def contents(self):
"""
:return:
A byte string of the DER-encoded contents of the sequence
"""
return self._contents
@contents.setter
def contents(self, value):
"""
:param value:
A byte string of the DER-encoded contents of the sequence
"""
self._normalized = False
self._contents = value
def set(self, value):
"""
Sets the value of the string
:param value:
A unicode string
"""
if not isinstance(value, str_cls):
raise TypeError(unwrap(
'''
%s value must be a unicode string, not %s
''',
type_name(self),
type_name(value)
))
if value.find('@') != -1:
mailbox, hostname = value.rsplit('@', 1)
encoded_value = mailbox.encode('ascii') + b'@' + hostname.encode('idna')
else:
encoded_value = value.encode('ascii')
self._normalized = True
self._unicode = value
self.contents = encoded_value
self._header = None
if self._trailer != b'':
self._trailer = b''
def __unicode__(self):
"""
:return:
A unicode string
"""
if self._unicode is None:
contents = self._merge_chunks()
if contents.find(b'@') == -1:
self._unicode = contents.decode('ascii')
else:
mailbox, hostname = contents.rsplit(b'@', 1)
self._unicode = mailbox.decode('ascii') + '@' + hostname.decode('idna')
return self._unicode
def __ne__(self, other):
return not self == other
def __eq__(self, other):
"""
Equality as defined by https://tools.ietf.org/html/rfc5280#section-7.5
:param other:
Another EmailAddress object
:return:
A boolean
"""
if not isinstance(other, EmailAddress):
return False
if not self._normalized:
self.set(self.native)
if not other._normalized:
other.set(other.native)
if self._contents.find(b'@') == -1 or other._contents.find(b'@') == -1:
return self._contents == other._contents
other_mailbox, other_hostname = other._contents.rsplit(b'@', 1)
mailbox, hostname = self._contents.rsplit(b'@', 1)
if mailbox != other_mailbox:
return False
if hostname.lower() != other_hostname.lower():
return False
return True
class IPAddress(OctetString):
def parse(self, spec=None, spec_params=None):
"""
This method is not applicable to IP addresses
"""
raise ValueError(unwrap(
'''
IP address values can not be parsed
'''
))
def set(self, value):
"""
Sets the value of the object
:param value:
A unicode string containing an IPv4 address, IPv4 address with CIDR,
an IPv6 address or IPv6 address with CIDR
"""
if not isinstance(value, str_cls):
raise TypeError(unwrap(
'''
%s value must be a unicode string, not %s
''',
type_name(self),
type_name(value)
))
original_value = value
has_cidr = value.find('/') != -1
cidr = 0
if has_cidr:
parts = value.split('/', 1)
value = parts[0]
cidr = int(parts[1])
if cidr < 0:
raise ValueError(unwrap(
'''
%s value contains a CIDR range less than 0
''',
type_name(self)
))
if value.find(':') != -1:
family = socket.AF_INET6
if cidr > 128:
raise ValueError(unwrap(
'''
%s value contains a CIDR range bigger than 128, the maximum
value for an IPv6 address
''',
type_name(self)
))
cidr_size = 128
else:
family = socket.AF_INET
if cidr > 32:
raise ValueError(unwrap(
'''
%s value contains a CIDR range bigger than 32, the maximum
value for an IPv4 address
''',
type_name(self)
))
cidr_size = 32
cidr_bytes = b''
if has_cidr:
cidr_mask = '1' * cidr
cidr_mask += '0' * (cidr_size - len(cidr_mask))
cidr_bytes = int_to_bytes(int(cidr_mask, 2))
cidr_bytes = (b'\x00' * ((cidr_size // 8) - len(cidr_bytes))) + cidr_bytes
self._native = original_value
self.contents = inet_pton(family, value) + cidr_bytes
self._bytes = self.contents
self._header = None
if self._trailer != b'':
self._trailer = b''
@property
def native(self):
"""
The a native Python datatype representation of this value
:return:
A unicode string or None
"""
if self.contents is None:
return None
if self._native is None:
byte_string = self.__bytes__()
byte_len = len(byte_string)
cidr_int = None
if byte_len in set([32, 16]):
value = inet_ntop(socket.AF_INET6, byte_string[0:16])
if byte_len > 16:
cidr_int = int_from_bytes(byte_string[16:])
elif byte_len in set([8, 4]):
value = inet_ntop(socket.AF_INET, byte_string[0:4])
if byte_len > 4:
cidr_int = int_from_bytes(byte_string[4:])
if cidr_int is not None:
cidr_bits = '{0:b}'.format(cidr_int)
cidr = len(cidr_bits.rstrip('0'))
value = value + '/' + str_cls(cidr)
self._native = value
return self._native
def __ne__(self, other):
return not self == other
def __eq__(self, other):
"""
:param other:
Another IPAddress object
:return:
A boolean
"""
if not isinstance(other, IPAddress):
return False
return self.__bytes__() == other.__bytes__()
class Attribute(Sequence):
_fields = [
('type', ObjectIdentifier),
('values', SetOf, {'spec': Any}),
]
class Attributes(SequenceOf):
_child_spec = Attribute
class KeyUsage(BitString):
_map = {
0: 'digital_signature',
1: 'non_repudiation',
2: 'key_encipherment',
3: 'data_encipherment',
4: 'key_agreement',
5: 'key_cert_sign',
6: 'crl_sign',
7: 'encipher_only',
8: 'decipher_only',
}
class PrivateKeyUsagePeriod(Sequence):
_fields = [
('not_before', GeneralizedTime, {'tag_type': 'implicit', 'tag': 0, 'optional': True}),
('not_after', GeneralizedTime, {'tag_type': 'implicit', 'tag': 1, 'optional': True}),
]
class DirectoryString(Choice):
_alternatives = [
('teletex_string', TeletexString),
('printable_string', PrintableString),
('universal_string', UniversalString),
('utf8_string', UTF8String),
('bmp_string', BMPString),
# This is an invalid/bad alternative, but some broken certs use it
('ia5_string', IA5String),
]
class NameType(ObjectIdentifier):
_map = {
'2.5.4.3': 'common_name',
'2.5.4.4': 'surname',
'2.5.4.5': 'serial_number',
'2.5.4.6': 'country_name',
'2.5.4.7': 'locality_name',
'2.5.4.8': 'state_or_province_name',
'2.5.4.9': 'street_address',
'2.5.4.10': 'organization_name',
'2.5.4.11': 'organizational_unit_name',
'2.5.4.12': 'title',
'2.5.4.15': 'business_category',
'2.5.4.17': 'postal_code',
'2.5.4.20': 'telephone_number',
'2.5.4.41': 'name',
'2.5.4.42': 'given_name',
'2.5.4.43': 'initials',
'2.5.4.44': 'generation_qualifier',
'2.5.4.45': 'unique_identifier',
'2.5.4.46': 'dn_qualifier',
'2.5.4.65': 'pseudonym',
'2.5.4.97': 'organization_identifier',
# https://tools.ietf.org/html/rfc2985#page-26
'1.2.840.113549.1.9.1': 'email_address',
# Page 10 of https://cabforum.org/wp-content/uploads/EV-V1_5_5.pdf
'1.3.6.1.4.1.311.60.2.1.1': 'incorporation_locality',
'1.3.6.1.4.1.311.60.2.1.2': 'incorporation_state_or_province',
'1.3.6.1.4.1.311.60.2.1.3': 'incorporation_country',
# https://tools.ietf.org/html/rfc2247#section-4
'0.9.2342.19200300.100.1.25': 'domain_component',
# http://www.alvestrand.no/objectid/0.2.262.1.10.7.20.html
'0.2.262.1.10.7.20': 'name_distinguisher',
}
# This order is largely based on observed order seen in EV certs from
# Symantec and DigiCert. Some of the uncommon name-related fields are
# just placed in what seems like a reasonable order.
preferred_order = [
'incorporation_country',
'incorporation_state_or_province',
'incorporation_locality',
'business_category',
'serial_number',
'country_name',
'postal_code',
'state_or_province_name',
'locality_name',
'street_address',
'organization_name',
'organizational_unit_name',
'title',
'common_name',
'initials',
'generation_qualifier',
'surname',
'given_name',
'name',
'pseudonym',
'dn_qualifier',
'telephone_number',
'email_address',
'domain_component',
'name_distinguisher',
'organization_identifier',
]
@classmethod
def preferred_ordinal(cls, attr_name):
"""
Returns an ordering value for a particular attribute key.
Unrecognized attributes and OIDs will be sorted lexically at the end.
:return:
An orderable value.
"""
attr_name = cls.map(attr_name)
if attr_name in cls.preferred_order:
ordinal = cls.preferred_order.index(attr_name)
else:
ordinal = len(cls.preferred_order)
return (ordinal, attr_name)
@property
def human_friendly(self):
"""
:return:
A human-friendly unicode string to display to users
"""
return {
'common_name': 'Common Name',
'surname': 'Surname',
'serial_number': 'Serial Number',
'country_name': 'Country',
'locality_name': 'Locality',
'state_or_province_name': 'State/Province',
'street_address': 'Street Address',
'organization_name': 'Organization',
'organizational_unit_name': 'Organizational Unit',
'title': 'Title',
'business_category': 'Business Category',
'postal_code': 'Postal Code',
'telephone_number': 'Telephone Number',
'name': 'Name',
'given_name': 'Given Name',
'initials': 'Initials',
'generation_qualifier': 'Generation Qualifier',
'unique_identifier': 'Unique Identifier',
'dn_qualifier': 'DN Qualifier',
'pseudonym': 'Pseudonym',
'email_address': 'Email Address',
'incorporation_locality': 'Incorporation Locality',
'incorporation_state_or_province': 'Incorporation State/Province',
'incorporation_country': 'Incorporation Country',
'domain_component': 'Domain Component',
'name_distinguisher': 'Name Distinguisher',
'organization_identifier': 'Organization Identifier',
}.get(self.native, self.native)
class NameTypeAndValue(Sequence):
_fields = [
('type', NameType),
('value', Any),
]
_oid_pair = ('type', 'value')
_oid_specs = {
'common_name': DirectoryString,
'surname': DirectoryString,
'serial_number': DirectoryString,
'country_name': DirectoryString,
'locality_name': DirectoryString,
'state_or_province_name': DirectoryString,
'street_address': DirectoryString,
'organization_name': DirectoryString,
'organizational_unit_name': DirectoryString,
'title': DirectoryString,
'business_category': DirectoryString,
'postal_code': DirectoryString,
'telephone_number': PrintableString,
'name': DirectoryString,
'given_name': DirectoryString,
'initials': DirectoryString,
'generation_qualifier': DirectoryString,
'unique_identifier': OctetBitString,
'dn_qualifier': DirectoryString,
'pseudonym': DirectoryString,
# https://tools.ietf.org/html/rfc2985#page-26
'email_address': EmailAddress,
# Page 10 of https://cabforum.org/wp-content/uploads/EV-V1_5_5.pdf
'incorporation_locality': DirectoryString,
'incorporation_state_or_province': DirectoryString,
'incorporation_country': DirectoryString,
'domain_component': DNSName,
'name_distinguisher': DirectoryString,
'organization_identifier': DirectoryString,
}
_prepped = None
@property
def prepped_value(self):
"""
Returns the value after being processed by the internationalized string
preparation as specified by RFC 5280
:return:
A unicode string
"""
if self._prepped is None:
self._prepped = self._ldap_string_prep(self['value'].native)
return self._prepped
def __ne__(self, other):
return not self == other
def __eq__(self, other):
"""
Equality as defined by https://tools.ietf.org/html/rfc5280#section-7.1
:param other:
Another NameTypeAndValue object
:return:
A boolean
"""
if not isinstance(other, NameTypeAndValue):
return False
if other['type'].native != self['type'].native:
return False
return other.prepped_value == self.prepped_value
def _ldap_string_prep(self, string):
"""
Implements the internationalized string preparation algorithm from
RFC 4518. https://tools.ietf.org/html/rfc4518#section-2
:param string:
A unicode string to prepare
:return:
A prepared unicode string, ready for comparison
"""
# Map step
string = re.sub('[\u00ad\u1806\u034f\u180b-\u180d\ufe0f-\uff00\ufffc]+', '', string)
string = re.sub('[\u0009\u000a\u000b\u000c\u000d\u0085]', ' ', string)
if sys.maxunicode == 0xffff:
# Some installs of Python 2.7 don't support 8-digit unicode escape
# ranges, so we have to break them into pieces
# Original was: \U0001D173-\U0001D17A and \U000E0020-\U000E007F
string = re.sub('\ud834[\udd73-\udd7a]|\udb40[\udc20-\udc7f]|\U000e0001', '', string)
else:
string = re.sub('[\U0001D173-\U0001D17A\U000E0020-\U000E007F\U000e0001]', '', string)
string = re.sub(
'[\u0000-\u0008\u000e-\u001f\u007f-\u0084\u0086-\u009f\u06dd\u070f\u180e\u200c-\u200f'
'\u202a-\u202e\u2060-\u2063\u206a-\u206f\ufeff\ufff9-\ufffb]+',
'',
string
)
string = string.replace('\u200b', '')
string = re.sub('[\u00a0\u1680\u2000-\u200a\u2028-\u2029\u202f\u205f\u3000]', ' ', string)
string = ''.join(map(stringprep.map_table_b2, string))
# Normalize step
string = unicodedata.normalize('NFKC', string)
# Prohibit step
for char in string:
if stringprep.in_table_a1(char):
raise ValueError(unwrap(
'''
X.509 Name objects may not contain unassigned code points
'''
))
if stringprep.in_table_c8(char):
raise ValueError(unwrap(
'''
X.509 Name objects may not contain change display or
zzzzdeprecated characters
'''
))
if stringprep.in_table_c3(char):
raise ValueError(unwrap(
'''
X.509 Name objects may not contain private use characters
'''
))
if stringprep.in_table_c4(char):
raise ValueError(unwrap(
'''
X.509 Name objects may not contain non-character code points
'''
))
if stringprep.in_table_c5(char):
raise ValueError(unwrap(
'''
X.509 Name objects may not contain surrogate code points
'''
))
if char == '\ufffd':
raise ValueError(unwrap(
'''
X.509 Name objects may not contain the replacement character
'''
))
# Check bidirectional step - here we ensure that we are not mixing
# left-to-right and right-to-left text in the string
has_r_and_al_cat = False
has_l_cat = False
for char in string:
if stringprep.in_table_d1(char):
has_r_and_al_cat = True
elif stringprep.in_table_d2(char):
has_l_cat = True
if has_r_and_al_cat:
first_is_r_and_al = stringprep.in_table_d1(string[0])
last_is_r_and_al = stringprep.in_table_d1(string[-1])
if has_l_cat or not first_is_r_and_al or not last_is_r_and_al:
raise ValueError(unwrap(
'''
X.509 Name object contains a malformed bidirectional
sequence
'''
))
# Insignificant space handling step
string = ' ' + re.sub(' +', ' ', string).strip() + ' '
return string
class RelativeDistinguishedName(SetOf):
_child_spec = NameTypeAndValue
@property
def hashable(self):
"""
:return:
A unicode string that can be used as a dict key or in a set
"""
output = []
values = self._get_values(self)
for key in sorted(values.keys()):
output.append('%s: %s' % (key, values[key]))
# Unit separator is used here since the normalization process for
# values moves any such character, and the keys are all dotted integers
# or under_score_words
return '\x1F'.join(output)
def __ne__(self, other):
return not self == other
def __eq__(self, other):
"""
Equality as defined by https://tools.ietf.org/html/rfc5280#section-7.1
:param other:
Another RelativeDistinguishedName object
:return:
A boolean
"""
if not isinstance(other, RelativeDistinguishedName):
return False
if len(self) != len(other):
return False
self_types = self._get_types(self)
other_types = self._get_types(other)
if self_types != other_types:
return False
self_values = self._get_values(self)
other_values = self._get_values(other)
for type_name_ in self_types:
if self_values[type_name_] != other_values[type_name_]:
return False
return True
def _get_types(self, rdn):
"""
Returns a set of types contained in an RDN
:param rdn:
A RelativeDistinguishedName object
:return:
A set object with unicode strings of NameTypeAndValue type field
values
"""
return set([ntv['type'].native for ntv in rdn])
def _get_values(self, rdn):
"""
Returns a dict of prepped values contained in an RDN
:param rdn:
A RelativeDistinguishedName object
:return:
A dict object with unicode strings of NameTypeAndValue value field
values that have been prepped for comparison
"""
output = {}
[output.update([(ntv['type'].native, ntv.prepped_value)]) for ntv in rdn]
return output
class RDNSequence(SequenceOf):
_child_spec = RelativeDistinguishedName
@property
def hashable(self):
"""
:return:
A unicode string that can be used as a dict key or in a set
"""
# Record separator is used here since the normalization process for
# values moves any such character, and the keys are all dotted integers
# or under_score_words
return '\x1E'.join(rdn.hashable for rdn in self)
def __ne__(self, other):
return not self == other
def __eq__(self, other):
"""
Equality as defined by https://tools.ietf.org/html/rfc5280#section-7.1
:param other:
Another RDNSequence object
:return:
A boolean
"""
if not isinstance(other, RDNSequence):
return False
if len(self) != len(other):
return False
for index, self_rdn in enumerate(self):
if other[index] != self_rdn:
return False
return True
class Name(Choice):
_alternatives = [
('', RDNSequence),
]
_human_friendly = None
_sha1 = None
_sha256 = None
@classmethod
def build(cls, name_dict, use_printable=False):
"""
Creates a Name object from a dict of unicode string keys and values.
The keys should be from NameType._map, or a dotted-integer OID unicode
string.
:param name_dict:
A dict of name information, e.g. {"common_name": "Will Bond",
"country_name": "US", "organization": "Codex Non Sufficit LC"}
:param use_printable:
A bool - if PrintableString should be used for encoding instead of
UTF8String. This is for backwards compatiblity with old software.
:return:
An x509.Name object
"""
rdns = []
if not use_printable:
encoding_name = 'utf8_string'
encoding_class = UTF8String
else:
encoding_name = 'printable_string'
encoding_class = PrintableString
# Sort the attributes according to NameType.preferred_order
name_dict = OrderedDict(
sorted(
name_dict.items(),
key=lambda item: NameType.preferred_ordinal(item[0])
)
)
for attribute_name, attribute_value in name_dict.items():
attribute_name = NameType.map(attribute_name)
if attribute_name == 'email_address':
value = EmailAddress(attribute_value)
elif attribute_name == 'domain_component':
value = DNSName(attribute_value)
elif attribute_name in set(['dn_qualifier', 'country_name', 'serial_number']):
value = DirectoryString(
name='printable_string',
value=PrintableString(attribute_value)
)
else:
value = DirectoryString(
name=encoding_name,
value=encoding_class(attribute_value)
)
rdns.append(RelativeDistinguishedName([
NameTypeAndValue({
'type': attribute_name,
'value': value
})
]))
return cls(name='', value=RDNSequence(rdns))
@property
def hashable(self):
"""
:return:
A unicode string that can be used as a dict key or in a set
"""
return self.chosen.hashable
def __len__(self):
return len(self.chosen)
def __ne__(self, other):
return not self == other
def __eq__(self, other):
"""
Equality as defined by https://tools.ietf.org/html/rfc5280#section-7.1
:param other:
Another Name object
:return:
A boolean
"""
if not isinstance(other, Name):
return False
return self.chosen == other.chosen
@property
def native(self):
if self._native is None:
self._native = OrderedDict()
for rdn in self.chosen.native:
for type_val in rdn:
field_name = type_val['type']
if field_name in self._native:
existing = self._native[field_name]
if not isinstance(existing, list):
existing = self._native[field_name] = [existing]
existing.append(type_val['value'])
else:
self._native[field_name] = type_val['value']
return self._native
@property
def human_friendly(self):
"""
:return:
A human-friendly unicode string containing the parts of the name
"""
if self._human_friendly is None:
data = OrderedDict()
last_field = None
for rdn in self.chosen:
for type_val in rdn:
field_name = type_val['type'].human_friendly
last_field = field_name
if field_name in data:
data[field_name] = [data[field_name]]
data[field_name].append(type_val['value'])
else:
data[field_name] = type_val['value']
to_join = []
keys = data.keys()
if last_field == 'Country':
keys = reversed(list(keys))
for key in keys:
value = data[key]
native_value = self._recursive_humanize(value)
to_join.append('%s: %s' % (key, native_value))
has_comma = False
for element in to_join:
if element.find(',') != -1:
has_comma = True
break
separator = ', ' if not has_comma else '; '
self._human_friendly = separator.join(to_join[::-1])
return self._human_friendly
def _recursive_humanize(self, value):
"""
Recursively serializes data compiled from the RDNSequence
:param value:
An Asn1Value object, or a list of Asn1Value objects
:return:
A unicode string
"""
if isinstance(value, list):
return', '.join(
reversed([self._recursive_humanize(sub_value) for sub_value in value])
)
return value.native
@property
def sha1(self):
"""
:return:
The SHA1 hash of the DER-encoded bytes of this name
"""
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 name
"""
if self._sha256 is None:
self._sha256 = hashlib.sha256(self.dump()).digest()
return self._sha256
class AnotherName(Sequence):
_fields = [
('type_id', ObjectIdentifier),
('value', Any, {'tag_type': 'explicit', 'tag': 0}),
]
class CountryName(Choice):
class_ = 1
tag = 1
_alternatives = [
('x121_dcc_code', NumericString),
('iso_3166_alpha2_code', PrintableString),
]
class AdministrationDomainName(Choice):
class_ = 1
tag = 2
_alternatives = [
('numeric', NumericString),
('printable', PrintableString),
]
class PrivateDomainName(Choice):
_alternatives = [
('numeric', NumericString),
('printable', PrintableString),
]
class PersonalName(Set):
_fields = [
('surname', PrintableString, {'tag_type': 'implicit', 'tag': 0}),
('given_name', PrintableString, {'tag_type': 'implicit', 'tag': 1, 'optional': True}),
('initials', PrintableString, {'tag_type': 'implicit', 'tag': 2, 'optional': True}),
('generation_qualifier', PrintableString, {'tag_type': 'implicit', 'tag': 3, 'optional': True}),
]
class TeletexPersonalName(Set):
_fields = [
('surname', TeletexString, {'tag_type': 'implicit', 'tag': 0}),
('given_name', TeletexString, {'tag_type': 'implicit', 'tag': 1, 'optional': True}),
('initials', TeletexString, {'tag_type': 'implicit', 'tag': 2, 'optional': True}),
('generation_qualifier', TeletexString, {'tag_type': 'implicit', 'tag': 3, 'optional': True}),
]
class OrganizationalUnitNames(SequenceOf):
_child_spec = PrintableString
class TeletexOrganizationalUnitNames(SequenceOf):
_child_spec = TeletexString
class BuiltInStandardAttributes(Sequence):
_fields = [
('country_name', CountryName, {'optional': True}),
('administration_domain_name', AdministrationDomainName, {'optional': True}),
('network_address', NumericString, {'tag_type': 'implicit', 'tag': 0, 'optional': True}),
('terminal_identifier', PrintableString, {'tag_type': 'implicit', 'tag': 1, 'optional': True}),
('private_domain_name', PrivateDomainName, {'tag_type': 'explicit', 'tag': 2, 'optional': True}),
('organization_name', PrintableString, {'tag_type': 'implicit', 'tag': 3, 'optional': True}),
('numeric_user_identifier', NumericString, {'tag_type': 'implicit', 'tag': 4, 'optional': True}),
('personal_name', PersonalName, {'tag_type': 'implicit', 'tag': 5, 'optional': True}),
('organizational_unit_names', OrganizationalUnitNames, {'tag_type': 'implicit', 'tag': 6, 'optional': True}),
]
class BuiltInDomainDefinedAttribute(Sequence):
_fields = [
('type', PrintableString),
('value', PrintableString),
]
class BuiltInDomainDefinedAttributes(SequenceOf):
_child_spec = BuiltInDomainDefinedAttribute
class TeletexDomainDefinedAttribute(Sequence):
_fields = [
('type', TeletexString),
('value', TeletexString),
]
class TeletexDomainDefinedAttributes(SequenceOf):
_child_spec = TeletexDomainDefinedAttribute
class PhysicalDeliveryCountryName(Choice):
_alternatives = [
('x121_dcc_code', NumericString),
('iso_3166_alpha2_code', PrintableString),
]
class PostalCode(Choice):
_alternatives = [
('numeric_code', NumericString),
('printable_code', PrintableString),
]
class PDSParameter(Set):
_fields = [
('printable_string', PrintableString, {'optional': True}),
('teletex_string', TeletexString, {'optional': True}),
]
class PrintableAddress(SequenceOf):
_child_spec = PrintableString
class UnformattedPostalAddress(Set):
_fields = [
('printable_address', PrintableAddress, {'optional': True}),
('teletex_string', TeletexString, {'optional': True}),
]
class E1634Address(Sequence):
_fields = [
('number', NumericString, {'tag_type': 'implicit', 'tag': 0}),
('sub_address', NumericString, {'tag_type': 'implicit', 'tag': 1, 'optional': True}),
]
class NAddresses(SetOf):
_child_spec = OctetString
class PresentationAddress(Sequence):
_fields = [
('p_selector', OctetString, {'tag_type': 'explicit', 'tag': 0, 'optional': True}),
('s_selector', OctetString, {'tag_type': 'explicit', 'tag': 1, 'optional': True}),
('t_selector', OctetString, {'tag_type': 'explicit', 'tag': 2, 'optional': True}),
('n_addresses', NAddresses, {'tag_type': 'explicit', 'tag': 3}),
]
class ExtendedNetworkAddress(Choice):
_alternatives = [
('e163_4_address', E1634Address),
('psap_address', PresentationAddress, {'tag_type': 'implicit', 'tag': 0})
]
class TerminalType(Integer):
_map = {
3: 'telex',
4: 'teletex',
5: 'g3_facsimile',
6: 'g4_facsimile',
7: 'ia5_terminal',
8: 'videotex',
}
class ExtensionAttributeType(Integer):
_map = {
1: 'common_name',
2: 'teletex_common_name',
3: 'teletex_organization_name',
4: 'teletex_personal_name',
5: 'teletex_organization_unit_names',
6: 'teletex_domain_defined_attributes',
7: 'pds_name',
8: 'physical_delivery_country_name',
9: 'postal_code',
10: 'physical_delivery_office_name',
11: 'physical_delivery_office_number',
12: 'extension_of_address_components',
13: 'physical_delivery_personal_name',
14: 'physical_delivery_organization_name',
15: 'extension_physical_delivery_address_components',
16: 'unformatted_postal_address',
17: 'street_address',
18: 'post_office_box_address',
19: 'poste_restante_address',
20: 'unique_postal_name',
21: 'local_postal_attributes',
22: 'extended_network_address',
23: 'terminal_type',
}
class ExtensionAttribute(Sequence):
_fields = [
('extension_attribute_type', ExtensionAttributeType, {'tag_type': 'implicit', 'tag': 0}),
('extension_attribute_value', Any, {'tag_type': 'explicit', 'tag': 1}),
]
_oid_pair = ('extension_attribute_type', 'extension_attribute_value')
_oid_specs = {
'common_name': PrintableString,
'teletex_common_name': TeletexString,
'teletex_organization_name': TeletexString,
'teletex_personal_name': TeletexPersonalName,
'teletex_organization_unit_names': TeletexOrganizationalUnitNames,
'teletex_domain_defined_attributes': TeletexDomainDefinedAttributes,
'pds_name': PrintableString,
'physical_delivery_country_name': PhysicalDeliveryCountryName,
'postal_code': PostalCode,
'physical_delivery_office_name': PDSParameter,
'physical_delivery_office_number': PDSParameter,
'extension_of_address_components': PDSParameter,
'physical_delivery_personal_name': PDSParameter,
'physical_delivery_organization_name': PDSParameter,
'extension_physical_delivery_address_components': PDSParameter,
'unformatted_postal_address': UnformattedPostalAddress,
'street_address': PDSParameter,
'post_office_box_address': PDSParameter,
'poste_restante_address': PDSParameter,
'unique_postal_name': PDSParameter,
'local_postal_attributes': PDSParameter,
'extended_network_address': ExtendedNetworkAddress,
'terminal_type': TerminalType,
}
class ExtensionAttributes(SequenceOf):
_child_spec = ExtensionAttribute
class ORAddress(Sequence):
_fields = [
('built_in_standard_attributes', BuiltInStandardAttributes),
('built_in_domain_defined_attributes', BuiltInDomainDefinedAttributes, {'optional': True}),
('extension_attributes', ExtensionAttributes, {'optional': True}),
]
class EDIPartyName(Sequence):
_fields = [
('name_assigner', DirectoryString, {'tag_type': 'implicit', 'tag': 0, 'optional': True}),
('party_name', DirectoryString, {'tag_type': 'implicit', 'tag': 1}),
]
class GeneralName(Choice):
_alternatives = [
('other_name', AnotherName, {'tag_type': 'implicit', 'tag': 0}),
('rfc822_name', EmailAddress, {'tag_type': 'implicit', 'tag': 1}),
('dns_name', DNSName, {'tag_type': 'implicit', 'tag': 2}),
('x400_address', ORAddress, {'tag_type': 'implicit', 'tag': 3}),
('directory_name', Name, {'tag_type': 'explicit', 'tag': 4}),
('edi_party_name', EDIPartyName, {'tag_type': 'implicit', 'tag': 5}),
('uniform_resource_identifier', URI, {'tag_type': 'implicit', 'tag': 6}),
('ip_address', IPAddress, {'tag_type': 'implicit', 'tag': 7}),
('registered_id', ObjectIdentifier, {'tag_type': 'implicit', 'tag': 8}),
]
def __ne__(self, other):
return not self == other
def __eq__(self, other):
"""
Does not support other_name, x400_address or edi_party_name
:param other:
The other GeneralName to compare to
:return:
A boolean
"""
if self.name in ('other_name', 'x400_address', 'edi_party_name'):
raise ValueError(unwrap(
'''
Comparison is not supported for GeneralName objects of
choice %s
''',
self.name
))
if other.name in ('other_name', 'x400_address', 'edi_party_name'):
raise ValueError(unwrap(
'''
Comparison is not supported for GeneralName objects of choice
%s''',
other.name
))
if self.name != other.name:
return False
return self.chosen == other.chosen
class GeneralNames(SequenceOf):
_child_spec = GeneralName
class Time(Choice):
_alternatives = [
('utc_time', UTCTime),
('general_time', GeneralizedTime),
]
class Validity(Sequence):
_fields = [
('not_before', Time),
('not_after', Time),
]
class BasicConstraints(Sequence):
_fields = [
('ca', Boolean, {'default': False}),
('path_len_constraint', Integer, {'optional': True}),
]
class AuthorityKeyIdentifier(Sequence):
_fields = [
('key_identifier', OctetString, {'tag_type': 'implicit', 'tag': 0, 'optional': True}),
('authority_cert_issuer', GeneralNames, {'tag_type': 'implicit', 'tag': 1, 'optional': True}),
('authority_cert_serial_number', Integer, {'tag_type': 'implicit', 'tag': 2, 'optional': True}),
]
class DistributionPointName(Choice):
_alternatives = [
('full_name', GeneralNames, {'tag_type': 'implicit', 'tag': 0}),
('name_relative_to_crl_issuer', RelativeDistinguishedName, {'tag_type': 'implicit', 'tag': 1}),
]
class ReasonFlags(BitString):
_map = {
0: 'unused',
1: 'key_compromise',
2: 'ca_compromise',
3: 'affiliation_changed',
4: 'superseded',
5: 'cessation_of_operation',
6: 'certificate_hold',
7: 'privilege_withdrawn',
8: 'aa_compromise',
}
class GeneralSubtree(Sequence):
_fields = [
('base', GeneralName),
('minimum', Integer, {'tag_type': 'implicit', 'tag': 0, 'default': 0}),
('maximum', Integer, {'tag_type': 'implicit', 'tag': 1, 'optional': True}),
]
class GeneralSubtrees(SequenceOf):
_child_spec = GeneralSubtree
class NameConstraints(Sequence):
_fields = [
('permitted_subtrees', GeneralSubtrees, {'tag_type': 'implicit', 'tag': 0, 'optional': True}),
('excluded_subtrees', GeneralSubtrees, {'tag_type': 'implicit', 'tag': 1, 'optional': True}),
]
class DistributionPoint(Sequence):
_fields = [
('distribution_point', DistributionPointName, {'tag_type': 'explicit', 'tag': 0, 'optional': True}),
('reasons', ReasonFlags, {'tag_type': 'implicit', 'tag': 1, 'optional': True}),
('crl_issuer', GeneralNames, {'tag_type': 'implicit', 'tag': 2, 'optional': True}),
]
_url = False
@property
def url(self):
"""
:return:
None or a unicode string of the distribution point's URL
"""
if self._url is False:
self._url = None
name = self['distribution_point']
if name.name != 'full_name':
raise ValueError(unwrap(
'''
CRL distribution points that are relative to the issuer are
not supported
'''
))
for general_name in name.chosen:
if general_name.name == 'uniform_resource_identifier':
url = general_name.native
if url[0:7] == 'http://':
self._url = url
break
return self._url
class CRLDistributionPoints(SequenceOf):
_child_spec = DistributionPoint
class DisplayText(Choice):
_alternatives = [
('ia5_string', IA5String),
('visible_string', VisibleString),
('bmp_string', BMPString),
('utf8_string', UTF8String),
]
class NoticeNumbers(SequenceOf):
_child_spec = Integer
class NoticeReference(Sequence):
_fields = [
('organization', DisplayText),
('notice_numbers', NoticeNumbers),
]
class UserNotice(Sequence):
_fields = [
('notice_ref', NoticeReference, {'optional': True}),
('explicit_text', DisplayText, {'optional': True}),
]
class PolicyQualifierId(ObjectIdentifier):
_map = {
'1.3.6.1.5.5.7.2.1': 'certification_practice_statement',
'1.3.6.1.5.5.7.2.2': 'user_notice',
}
class PolicyQualifierInfo(Sequence):
_fields = [
('policy_qualifier_id', PolicyQualifierId),
('qualifier', Any),
]
_oid_pair = ('policy_qualifier_id', 'qualifier')
_oid_specs = {
'certification_practice_statement': IA5String,
'user_notice': UserNotice,
}
class PolicyQualifierInfos(SequenceOf):
_child_spec = PolicyQualifierInfo
class PolicyIdentifier(ObjectIdentifier):
_map = {
'2.5.29.32.0': 'any_policy',
}
class PolicyInformation(Sequence):
_fields = [
('policy_identifier', PolicyIdentifier),
('policy_qualifiers', PolicyQualifierInfos, {'optional': True})
]
class CertificatePolicies(SequenceOf):
_child_spec = PolicyInformation
class PolicyMapping(Sequence):
_fields = [
('issuer_domain_policy', PolicyIdentifier),
('subject_domain_policy', PolicyIdentifier),
]
class PolicyMappings(SequenceOf):
_child_spec = PolicyMapping
class PolicyConstraints(Sequence):
_fields = [
('require_explicit_policy', Integer, {'tag_type': 'implicit', 'tag': 0, 'optional': True}),
('inhibit_policy_mapping', Integer, {'tag_type': 'implicit', 'tag': 1, 'optional': True}),
]
class KeyPurposeId(ObjectIdentifier):
_map = {
# https://tools.ietf.org/html/rfc5280#page-45
'2.5.29.37.0': 'any_extended_key_usage',
'1.3.6.1.5.5.7.3.1': 'server_auth',
'1.3.6.1.5.5.7.3.2': 'client_auth',
'1.3.6.1.5.5.7.3.3': 'code_signing',
'1.3.6.1.5.5.7.3.4': 'email_protection',
'1.3.6.1.5.5.7.3.5': 'ipsec_end_system',
'1.3.6.1.5.5.7.3.6': 'ipsec_tunnel',
'1.3.6.1.5.5.7.3.7': 'ipsec_user',
'1.3.6.1.5.5.7.3.8': 'time_stamping',
'1.3.6.1.5.5.7.3.9': 'ocsp_signing',
# http://tools.ietf.org/html/rfc3029.html#page-9
'1.3.6.1.5.5.7.3.10': 'dvcs',
# http://tools.ietf.org/html/rfc6268.html#page-16
'1.3.6.1.5.5.7.3.13': 'eap_over_ppp',
'1.3.6.1.5.5.7.3.14': 'eap_over_lan',
# https://tools.ietf.org/html/rfc5055#page-76
'1.3.6.1.5.5.7.3.15': 'scvp_server',
'1.3.6.1.5.5.7.3.16': 'scvp_client',
# https://tools.ietf.org/html/rfc4945#page-31
'1.3.6.1.5.5.7.3.17': 'ipsec_ike',
# https://tools.ietf.org/html/rfc5415#page-38
'1.3.6.1.5.5.7.3.18': 'capwap_ac',
'1.3.6.1.5.5.7.3.19': 'capwap_wtp',
# https://tools.ietf.org/html/rfc5924#page-8
'1.3.6.1.5.5.7.3.20': 'sip_domain',
# https://tools.ietf.org/html/rfc6187#page-7
'1.3.6.1.5.5.7.3.21': 'secure_shell_client',
'1.3.6.1.5.5.7.3.22': 'secure_shell_server',
# https://tools.ietf.org/html/rfc6494#page-7
'1.3.6.1.5.5.7.3.23': 'send_router',
'1.3.6.1.5.5.7.3.24': 'send_proxied_router',
'1.3.6.1.5.5.7.3.25': 'send_owner',
'1.3.6.1.5.5.7.3.26': 'send_proxied_owner',
# https://tools.ietf.org/html/rfc6402#page-10
'1.3.6.1.5.5.7.3.27': 'cmc_ca',
'1.3.6.1.5.5.7.3.28': 'cmc_ra',
'1.3.6.1.5.5.7.3.29': 'cmc_archive',
# https://tools.ietf.org/html/draft-ietf-sidr-bgpsec-pki-profiles-15#page-6
'1.3.6.1.5.5.7.3.30': 'bgpspec_router',
# https://msdn.microsoft.com/en-us/library/windows/desktop/aa378132(v=vs.85).aspx
# and https://support.microsoft.com/en-us/kb/287547
'1.3.6.1.4.1.311.10.3.1': 'microsoft_trust_list_signing',
'1.3.6.1.4.1.311.10.3.2': 'microsoft_time_stamp_signing',
'1.3.6.1.4.1.311.10.3.3': 'microsoft_server_gated',
'1.3.6.1.4.1.311.10.3.3.1': 'microsoft_serialized',
'1.3.6.1.4.1.311.10.3.4': 'microsoft_efs',
'1.3.6.1.4.1.311.10.3.4.1': 'microsoft_efs_recovery',
'1.3.6.1.4.1.311.10.3.5': 'microsoft_whql',
'1.3.6.1.4.1.311.10.3.6': 'microsoft_nt5',
'1.3.6.1.4.1.311.10.3.7': 'microsoft_oem_whql',
'1.3.6.1.4.1.311.10.3.8': 'microsoft_embedded_nt',
'1.3.6.1.4.1.311.10.3.9': 'microsoft_root_list_signer',
'1.3.6.1.4.1.311.10.3.10': 'microsoft_qualified_subordination',
'1.3.6.1.4.1.311.10.3.11': 'microsoft_key_recovery',
'1.3.6.1.4.1.311.10.3.12': 'microsoft_document_signing',
'1.3.6.1.4.1.311.10.3.13': 'microsoft_lifetime_signing',
'1.3.6.1.4.1.311.10.3.14': 'microsoft_mobile_device_software',
# https://opensource.apple.com/source
# - /Security/Security-57031.40.6/Security/libsecurity_keychain/lib/SecPolicy.cpp
# - /libsecurity_cssm/libsecurity_cssm-36064/lib/oidsalg.c
'1.2.840.113635.100.1.2': 'apple_x509_basic',
'1.2.840.113635.100.1.3': 'apple_ssl',
'1.2.840.113635.100.1.4': 'apple_local_cert_gen',
'1.2.840.113635.100.1.5': 'apple_csr_gen',
'1.2.840.113635.100.1.6': 'apple_revocation_crl',
'1.2.840.113635.100.1.7': 'apple_revocation_ocsp',
'1.2.840.113635.100.1.8': 'apple_smime',
'1.2.840.113635.100.1.9': 'apple_eap',
'1.2.840.113635.100.1.10': 'apple_software_update_signing',
'1.2.840.113635.100.1.11': 'apple_ipsec',
'1.2.840.113635.100.1.12': 'apple_ichat',
'1.2.840.113635.100.1.13': 'apple_resource_signing',
'1.2.840.113635.100.1.14': 'apple_pkinit_client',
'1.2.840.113635.100.1.15': 'apple_pkinit_server',
'1.2.840.113635.100.1.16': 'apple_code_signing',
'1.2.840.113635.100.1.17': 'apple_package_signing',
'1.2.840.113635.100.1.18': 'apple_id_validation',
'1.2.840.113635.100.1.20': 'apple_time_stamping',
'1.2.840.113635.100.1.21': 'apple_revocation',
'1.2.840.113635.100.1.22': 'apple_passbook_signing',
'1.2.840.113635.100.1.23': 'apple_mobile_store',
'1.2.840.113635.100.1.24': 'apple_escrow_service',
'1.2.840.113635.100.1.25': 'apple_profile_signer',
'1.2.840.113635.100.1.26': 'apple_qa_profile_signer',
'1.2.840.113635.100.1.27': 'apple_test_mobile_store',
'1.2.840.113635.100.1.28': 'apple_otapki_signer',
'1.2.840.113635.100.1.29': 'apple_test_otapki_signer',
'1.2.840.113625.100.1.30': 'apple_id_validation_record_signing_policy',
'1.2.840.113625.100.1.31': 'apple_smp_encryption',
'1.2.840.113625.100.1.32': 'apple_test_smp_encryption',
'1.2.840.113635.100.1.33': 'apple_server_authentication',
'1.2.840.113635.100.1.34': 'apple_pcs_escrow_service',
}
class ExtKeyUsageSyntax(SequenceOf):
_child_spec = KeyPurposeId
class AccessMethod(ObjectIdentifier):
_map = {
'1.3.6.1.5.5.7.48.1': 'ocsp',
'1.3.6.1.5.5.7.48.2': 'ca_issuers',
'1.3.6.1.5.5.7.48.3': 'time_stamping',
'1.3.6.1.5.5.7.48.5': 'ca_repository',
}
class AccessDescription(Sequence):
_fields = [
('access_method', AccessMethod),
('access_location', GeneralName),
]
class AuthorityInfoAccessSyntax(SequenceOf):
_child_spec = AccessDescription
class SubjectInfoAccessSyntax(SequenceOf):
_child_spec = AccessDescription
# https://tools.ietf.org/html/rfc7633
class Features(SequenceOf):
_child_spec = Integer
class EntrustVersionInfo(Sequence):
_fields = [
('entrust_vers', GeneralString),
('entrust_info_flags', BitString)
]
class NetscapeCertificateType(BitString):
_map = {
0: 'ssl_client',
1: 'ssl_server',
2: 'email',
3: 'object_signing',
4: 'reserved',
5: 'ssl_ca',
6: 'email_ca',
7: 'object_signing_ca',
}
class ExtensionId(ObjectIdentifier):
_map = {
'2.5.29.9': 'subject_directory_attributes',
'2.5.29.14': 'key_identifier',
'2.5.29.15': 'key_usage',
'2.5.29.16': 'private_key_usage_period',
'2.5.29.17': 'subject_alt_name',
'2.5.29.18': 'issuer_alt_name',
'2.5.29.19': 'basic_constraints',
'2.5.29.30': 'name_constraints',
'2.5.29.31': 'crl_distribution_points',
'2.5.29.32': 'certificate_policies',
'2.5.29.33': 'policy_mappings',
'2.5.29.35': 'authority_key_identifier',
'2.5.29.36': 'policy_constraints',
'2.5.29.37': 'extended_key_usage',
'2.5.29.46': 'freshest_crl',
'2.5.29.54': 'inhibit_any_policy',
'1.3.6.1.5.5.7.1.1': 'authority_information_access',
'1.3.6.1.5.5.7.1.11': 'subject_information_access',
# https://tools.ietf.org/html/rfc7633
'1.3.6.1.5.5.7.1.24': 'tls_feature',
'1.3.6.1.5.5.7.48.1.5': 'ocsp_no_check',
'1.2.840.113533.7.65.0': 'entrust_version_extension',
'2.16.840.1.113730.1.1': 'netscape_certificate_type',
}
class Extension(Sequence):
_fields = [
('extn_id', ExtensionId),
('critical', Boolean, {'default': False}),
('extn_value', ParsableOctetString),
]
_oid_pair = ('extn_id', 'extn_value')
_oid_specs = {
'subject_directory_attributes': Attributes,
'key_identifier': OctetString,
'key_usage': KeyUsage,
'private_key_usage_period': PrivateKeyUsagePeriod,
'subject_alt_name': GeneralNames,
'issuer_alt_name': GeneralNames,
'basic_constraints': BasicConstraints,
'name_constraints': NameConstraints,
'crl_distribution_points': CRLDistributionPoints,
'certificate_policies': CertificatePolicies,
'policy_mappings': PolicyMappings,
'authority_key_identifier': AuthorityKeyIdentifier,
'policy_constraints': PolicyConstraints,
'extended_key_usage': ExtKeyUsageSyntax,
'freshest_crl': CRLDistributionPoints,
'inhibit_any_policy': Integer,
'authority_information_access': AuthorityInfoAccessSyntax,
'subject_information_access': SubjectInfoAccessSyntax,
'tls_feature': Features,
'ocsp_no_check': Null,
'entrust_version_extension': EntrustVersionInfo,
'netscape_certificate_type': NetscapeCertificateType,
}
class Extensions(SequenceOf):
_child_spec = Extension
class Version(Integer):
_map = {
0: 'v1',
1: 'v2',
2: 'v3',
}
class TbsCertificate(Sequence):
_fields = [
('version', Version, {'tag_type': 'explicit', 'tag': 0, 'default': 'v1'}),
('serial_number', Integer),
('signature', SignedDigestAlgorithm),
('issuer', Name),
('validity', Validity),
('subject', Name),
('subject_public_key_info', PublicKeyInfo),
('issuer_unique_id', OctetBitString, {'tag_type': 'implicit', 'tag': 1, 'optional': True}),
('subject_unique_id', OctetBitString, {'tag_type': 'implicit', 'tag': 2, 'optional': True}),
('extensions', Extensions, {'tag_type': 'explicit', 'tag': 3, 'optional': True}),
]
class Certificate(Sequence):
_fields = [
('tbs_certificate', TbsCertificate),
('signature_algorithm', SignedDigestAlgorithm),
('signature_value', OctetBitString),
]
_processed_extensions = False
_critical_extensions = None
_subject_directory_attributes = None
_key_identifier_value = None
_key_usage_value = None
_subject_alt_name_value = None
_issuer_alt_name_value = None
_basic_constraints_value = None
_name_constraints_value = None
_crl_distribution_points_value = None
_certificate_policies_value = None
_policy_mappings_value = None
_authority_key_identifier_value = None
_policy_constraints_value = None
_freshest_crl_value = None
_inhibit_any_policy_value = None
_extended_key_usage_value = None
_authority_information_access_value = None
_subject_information_access_value = None
_tls_feature_value = None
_ocsp_no_check_value = None
_issuer_serial = None
_authority_issuer_serial = False
_crl_distribution_points = None
_delta_crl_distribution_points = None
_valid_domains = None
_valid_ips = None
_self_issued = None
_self_signed = 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_certificate']['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 subject_directory_attributes_value(self):
"""
This extension is used to contain additional identification attributes
about the subject.
:return:
None or an Attributes object
"""
if not self._processed_extensions:
self._set_extensions()
return self._key_identifier_value
@property
def key_identifier_value(self):
"""
This extension is used to help in creating certificate validation paths.
It contains an identifier that should generally, but is not guaranteed
to, be unique.
:return:
None or an OctetString object
"""
if not self._processed_extensions:
self._set_extensions()
return self._key_identifier_value
@property
def key_usage_value(self):
"""
This extension is used to define the purpose of the public key
contained within the certificate.
:return:
None or a KeyUsage
"""
if not self._processed_extensions:
self._set_extensions()
return self._key_usage_value
@property
def subject_alt_name_value(self):
"""
This extension allows for additional names to be associate with the
subject of the certificate. While it may contain a whole host of
possible names, it is usually used to allow certificates to be used
with multiple different domain names.
:return:
None or a GeneralNames object
"""
if not self._processed_extensions:
self._set_extensions()
return self._subject_alt_name_value
@property
def issuer_alt_name_value(self):
"""
This extension allows associating one or more alternative names with
the issuer of the certificate.
:return:
None or an x509.GeneralNames object
"""
if not self._processed_extensions:
self._set_extensions()
return self._issuer_alt_name_value
@property
def basic_constraints_value(self):
"""
This extension is used to determine if the subject of the certificate
is a CA, and if so, what the maximum number of intermediate CA certs
after this are, before an end-entity certificate is found.
:return:
None or a BasicConstraints object
"""
if not self._processed_extensions:
self._set_extensions()
return self._basic_constraints_value
@property
def name_constraints_value(self):
"""
This extension is used in CA certificates, and is used to limit the
possible names of certificates issued.
:return:
None or a NameConstraints object
"""
if not self._processed_extensions:
self._set_extensions()
return self._name_constraints_value
@property
def crl_distribution_points_value(self):
"""
This extension is used to help in locating the CRL for this certificate.
:return:
None or a CRLDistributionPoints object
extension
"""
if not self._processed_extensions:
self._set_extensions()
return self._crl_distribution_points_value
@property
def certificate_policies_value(self):
"""
This extension defines policies in CA certificates under which
certificates may be issued. In end-entity certificates, the inclusion
of a policy indicates the issuance of the certificate follows the
policy.
:return:
None or a CertificatePolicies object
"""
if not self._processed_extensions:
self._set_extensions()
return self._certificate_policies_value
@property
def policy_mappings_value(self):
"""
This extension allows mapping policy OIDs to other OIDs. This is used
to allow different policies to be treated as equivalent in the process
of validation.
:return:
None or a PolicyMappings object
"""
if not self._processed_extensions:
self._set_extensions()
return self._policy_mappings_value
@property
def authority_key_identifier_value(self):
"""
This extension helps in identifying the public key with which to
validate the authenticity of the certificate.
:return:
None or an AuthorityKeyIdentifier object
"""
if not self._processed_extensions:
self._set_extensions()
return self._authority_key_identifier_value
@property
def policy_constraints_value(self):
"""
This extension is used to control if policy mapping is allowed and
when policies are required.
:return:
None or a PolicyConstraints object
"""
if not self._processed_extensions:
self._set_extensions()
return self._policy_constraints_value
@property
def freshest_crl_value(self):
"""
This extension is used to help locate any available delta CRLs
:return:
None or an CRLDistributionPoints object
"""
if not self._processed_extensions:
self._set_extensions()
return self._freshest_crl_value
@property
def inhibit_any_policy_value(self):
"""
This extension is used to prevent mapping of the any policy to
specific requirements
:return:
None or a Integer object
"""
if not self._processed_extensions:
self._set_extensions()
return self._inhibit_any_policy_value
@property
def extended_key_usage_value(self):
"""
This extension is used to define additional purposes for the public key
beyond what is contained in the basic constraints.
:return:
None or an ExtKeyUsageSyntax object
"""
if not self._processed_extensions:
self._set_extensions()
return self._extended_key_usage_value
@property
def authority_information_access_value(self):
"""
This extension is used to locate the CA certificate used to sign this
certificate, or the OCSP responder for this certificate.
:return:
None or an AuthorityInfoAccessSyntax object
"""
if not self._processed_extensions:
self._set_extensions()
return self._authority_information_access_value
@property
def subject_information_access_value(self):
"""
This extension is used to access information about the subject of this
certificate.
:return:
None or a SubjectInfoAccessSyntax object
"""
if not self._processed_extensions:
self._set_extensions()
return self._subject_information_access_value
@property
def tls_feature_value(self):
"""
This extension is used to list the TLS features a server must respond
with if a client initiates a request supporting them.
:return:
None or a Features object
"""
if not self._processed_extensions:
self._set_extensions()
return self._tls_feature_value
@property
def ocsp_no_check_value(self):
"""
This extension is used on certificates of OCSP responders, indicating
that revocation information for the certificate should never need to
be verified, thus preventing possible loops in path validation.
:return:
None or a Null object (if present)
"""
if not self._processed_extensions:
self._set_extensions()
return self._ocsp_no_check_value
@property
def signature(self):
"""
:return:
A byte string of the signature
"""
return self['signature_value'].native
@property
def signature_algo(self):
"""
:return:
A unicode string of "rsassa_pkcs1v15", "rsassa_pss", "dsa", "ecdsa"
"""
return self['signature_algorithm'].signature_algo
@property
def hash_algo(self):
"""
:return:
A unicode string of "md2", "md5", "sha1", "sha224", "sha256",
"sha384", "sha512", "sha512_224", "sha512_256"
"""
return self['signature_algorithm'].hash_algo
@property
def public_key(self):
"""
:return:
The PublicKeyInfo object for this certificate
"""
return self['tbs_certificate']['subject_public_key_info']
@property
def subject(self):
"""
:return:
The Name object for the subject of this certificate
"""
return self['tbs_certificate']['subject']
@property
def issuer(self):
"""
:return:
The Name object for the issuer of this certificate
"""
return self['tbs_certificate']['issuer']
@property
def serial_number(self):
"""
:return:
An integer of the certificate's serial number
"""
return self['tbs_certificate']['serial_number'].native
@property
def key_identifier(self):
"""
:return:
None or a byte string of the certificate's key identifier from the
key identifier extension
"""
if not self.key_identifier_value:
return None
return self.key_identifier_value.native
@property
def issuer_serial(self):
"""
:return:
A byte string of the SHA-256 hash of the issuer concatenated with
the ascii character ":", concatenated with the serial number as
an ascii string
"""
if self._issuer_serial is None:
self._issuer_serial = self.issuer.sha256 + b':' + str_cls(self.serial_number).encode('ascii')
return self._issuer_serial
@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 authority_issuer_serial(self):
"""
:return:
None or a byte string of the SHA-256 hash of the isser from the
authority key identifier extension concatenated with the ascii
character ":", concatenated with the serial number from the
authority key identifier extension as an ascii string
"""
if self._authority_issuer_serial is False:
akiv = self.authority_key_identifier_value
if akiv and akiv['authority_cert_issuer'].native:
issuer = self.authority_key_identifier_value['authority_cert_issuer'][0].chosen
# We untag the element since it is tagged via being a choice from GeneralName
issuer = issuer.untag()
authority_serial = self.authority_key_identifier_value['authority_cert_serial_number'].native
self._authority_issuer_serial = issuer.sha256 + b':' + str_cls(authority_serial).encode('ascii')
else:
self._authority_issuer_serial = None
return self._authority_issuer_serial
@property
def crl_distribution_points(self):
"""
Returns complete CRL URLs - does not include delta CRLs
:return:
A list of zero or more DistributionPoint objects
"""
if self._crl_distribution_points is None:
self._crl_distribution_points = self._get_http_crl_distribution_points(self.crl_distribution_points_value)
return self._crl_distribution_points
@property
def delta_crl_distribution_points(self):
"""
Returns delta CRL URLs - does not include complete CRLs
:return:
A list of zero or more DistributionPoint objects
"""
if self._delta_crl_distribution_points is None:
self._delta_crl_distribution_points = self._get_http_crl_distribution_points(self.freshest_crl_value)
return self._delta_crl_distribution_points
def _get_http_crl_distribution_points(self, crl_distribution_points):
"""
Fetches the DistributionPoint object for non-relative, HTTP CRLs
referenced by the certificate
:param crl_distribution_points:
A CRLDistributionPoints object to grab the DistributionPoints from
:return:
A list of zero or more DistributionPoint objects
"""
output = []
if crl_distribution_points is None:
return []
for distribution_point in crl_distribution_points:
distribution_point_name = distribution_point['distribution_point']
if distribution_point_name is VOID:
continue
# 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':
output.append(distribution_point)
return output
@property
def ocsp_urls(self):
"""
:return:
A list of zero or more unicode strings of the OCSP URLs for this
cert
"""
if not self.authority_information_access_value:
return []
output = []
for entry in self.authority_information_access_value:
if entry['access_method'].native == 'ocsp':
location = entry['access_location']
if location.name != 'uniform_resource_identifier':
continue
url = location.native
if url.lower()[0:7] == 'http://':
output.append(url)
return output
@property
def valid_domains(self):
"""
:return:
A list of unicode strings of valid domain names for the certificate.
Wildcard certificates will have a domain in the form: *.example.com
"""
if self._valid_domains is None:
self._valid_domains = []
# For the subject alt name extension, we can look at the name of
# the choice selected since it distinguishes between domain names,
# email addresses, IPs, etc
if self.subject_alt_name_value:
for general_name in self.subject_alt_name_value:
if general_name.name == 'dns_name' and general_name.native not in self._valid_domains:
self._valid_domains.append(general_name.native)
# If there was no subject alt name extension, and the common name
# in the subject looks like a domain, that is considered the valid
# list. This is done because according to
# https://tools.ietf.org/html/rfc6125#section-6.4.4, the common
# name should not be used if the subject alt name is present.
else:
pattern = re.compile('^(\\*\\.)?(?:[a-zA-Z0-9](?:[a-zA-Z0-9\\-]*[a-zA-Z0-9])?\\.)+[a-zA-Z]{2,}$')
for rdn in self.subject.chosen:
for name_type_value in rdn:
if name_type_value['type'].native == 'common_name':
value = name_type_value['value'].native
if pattern.match(value):
self._valid_domains.append(value)
return self._valid_domains
@property
def valid_ips(self):
"""
:return:
A list of unicode strings of valid IP addresses for the certificate
"""
if self._valid_ips is None:
self._valid_ips = []
if self.subject_alt_name_value:
for general_name in self.subject_alt_name_value:
if general_name.name == 'ip_address':
self._valid_ips.append(general_name.native)
return self._valid_ips
@property
def ca(self):
"""
:return;
A boolean - if the certificate is marked as a CA
"""
return self.basic_constraints_value and self.basic_constraints_value['ca'].native
@property
def max_path_length(self):
"""
:return;
None or an integer of the maximum path length
"""
if not self.ca:
return None
return self.basic_constraints_value['path_len_constraint'].native
@property
def self_issued(self):
"""
:return:
A boolean - if the certificate is self-issued, as defined by RFC
5280
"""
if self._self_issued is None:
self._self_issued = self.subject == self.issuer
return self._self_issued
@property
def self_signed(self):
"""
:return:
A unicode string of "yes", "no" or "maybe". The "maybe" result will
be returned if the certificate does not contain a key identifier
extension, but is issued by the subject. In this case the
certificate signature will need to be verified using the subject
public key to determine a "yes" or "no" answer.
"""
if self._self_signed is None:
self._self_signed = 'no'
if self.self_issued:
if self.key_identifier:
if not self.authority_key_identifier:
self._self_signed = 'yes'
elif self.authority_key_identifier == self.key_identifier:
self._self_signed = 'yes'
else:
self._self_signed = 'maybe'
return self._self_signed
@property
def sha1(self):
"""
:return:
The SHA-1 hash of the DER-encoded bytes of this complete certificate
"""
if self._sha1 is None:
self._sha1 = hashlib.sha1(self.dump()).digest()
return self._sha1
@property
def sha1_fingerprint(self):
"""
:return:
A unicode string of the SHA-1 hash, formatted using hex encoding
with a space between each pair of characters, all uppercase
"""
return ' '.join('%02X' % c for c in bytes_to_list(self.sha1))
@property
def sha256(self):
"""
:return:
The SHA-256 hash of the DER-encoded bytes of this complete
certificate
"""
if self._sha256 is None:
self._sha256 = hashlib.sha256(self.dump()).digest()
return self._sha256
def is_valid_domain_ip(self, domain_ip):
"""
Check if a domain name or IP address is valid according to the
certificate
:param domain_ip:
A unicode string of a domain name or IP address
:return:
A boolean - if the domain or IP is valid for the certificate
"""
if not isinstance(domain_ip, str_cls):
raise TypeError(unwrap(
'''
domain_ip must be a unicode string, not %s
''',
type_name(domain_ip)
))
encoded_domain_ip = domain_ip.encode('idna').decode('ascii').lower()
is_ipv6 = encoded_domain_ip.find(':') != -1
is_ipv4 = not is_ipv6 and re.match('^\\d+\\.\\d+\\.\\d+\\.\\d+$', encoded_domain_ip)
is_domain = not is_ipv6 and not is_ipv4
# Handle domain name checks
if is_domain:
if not self.valid_domains:
return False
domain_labels = encoded_domain_ip.split('.')
for valid_domain in self.valid_domains:
encoded_valid_domain = valid_domain.encode('idna').decode('ascii').lower()
valid_domain_labels = encoded_valid_domain.split('.')
# The domain must be equal in label length to match
if len(valid_domain_labels) != len(domain_labels):
continue
if valid_domain_labels == domain_labels:
return True
is_wildcard = self._is_wildcard_domain(encoded_valid_domain)
if is_wildcard and self._is_wildcard_match(domain_labels, valid_domain_labels):
return True
return False
# Handle IP address checks
if not self.valid_ips:
return False
family = socket.AF_INET if is_ipv4 else socket.AF_INET6
normalized_ip = inet_pton(family, encoded_domain_ip)
for valid_ip in self.valid_ips:
valid_family = socket.AF_INET if valid_ip.find('.') != -1 else socket.AF_INET6
normalized_valid_ip = inet_pton(valid_family, valid_ip)
if normalized_valid_ip == normalized_ip:
return True
return False
def _is_wildcard_domain(self, domain):
"""
Checks if a domain is a valid wildcard according to
https://tools.ietf.org/html/rfc6125#section-6.4.3
:param domain:
A unicode string of the domain name, where any U-labels from an IDN
have been converted to A-labels
:return:
A boolean - if the domain is a valid wildcard domain
"""
# The * character must be present for a wildcard match, and if there is
# most than one, it is an invalid wildcard specification
if domain.count('*') != 1:
return False
labels = domain.lower().split('.')
if not labels:
return False
# Wildcards may only appear in the left-most label
if labels[0].find('*') == -1:
return False
# Wildcards may not be embedded in an A-label from an IDN
if labels[0][0:4] == 'xn--':
return False
return True
def _is_wildcard_match(self, domain_labels, valid_domain_labels):
"""
Determines if the labels in a domain are a match for labels from a
wildcard valid domain name
:param domain_labels:
A list of unicode strings, with A-label form for IDNs, of the labels
in the domain name to check
:param valid_domain_labels:
A list of unicode strings, with A-label form for IDNs, of the labels
in a wildcard domain pattern
:return:
A boolean - if the domain matches the valid domain
"""
first_domain_label = domain_labels[0]
other_domain_labels = domain_labels[1:]
wildcard_label = valid_domain_labels[0]
other_valid_domain_labels = valid_domain_labels[1:]
# The wildcard is only allowed in the first label, so if
# The subsequent labels are not equal, there is no match
if other_domain_labels != other_valid_domain_labels:
return False
if wildcard_label == '*':
return True
wildcard_regex = re.compile('^' + wildcard_label.replace('*', '.*') + '$')
if wildcard_regex.match(first_domain_label):
return True
return False
# The structures are taken from the OpenSSL source file x_x509a.c, and specify
# extra information that is added to X.509 certificates to store trust
# information about the certificate.
class KeyPurposeIdentifiers(SequenceOf):
_child_spec = KeyPurposeId
class SequenceOfAlgorithmIdentifiers(SequenceOf):
_child_spec = AlgorithmIdentifier
class CertificateAux(Sequence):
_fields = [
('trust', KeyPurposeIdentifiers, {'optional': True}),
('reject', KeyPurposeIdentifiers, {'tag_type': 'implicit', 'tag': 0, 'optional': True}),
('alias', UTF8String, {'optional': True}),
('keyid', OctetString, {'optional': True}),
('other', SequenceOfAlgorithmIdentifiers, {'tag_type': 'implicit', 'tag': 1, 'optional': True}),
]
class TrustedCertificate(Concat):
_child_specs = [Certificate, CertificateAux]