def parse_signature(cls, signature, signature_elm): """Read signature information from the <Signature> element.""" if signature_elm is None: return cm_elm = find(signature_elm, 'SignedInfo/CanonicalizationMethod') if cm_elm is not None: signature.canonicalization_method = cm_elm.attrib.get('Algorithm') sm_elm = find(signature_elm, 'SignedInfo/SignatureMethod') if sm_elm is not None: signature.algorithm = sm_elm.attrib.get('Algorithm') dm_elm = find(signature_elm, 'SignedInfo/Reference/DigestMethod') if dm_elm is not None: signature.digest_algorithm = dm_elm.attrib.get('Algorithm') issuer = find(signature_elm, 'KeyInfo/X509Data/X509IssuerSerial') if issuer is not None: signature.issuer = findtext(issuer, 'X509IssuerName') signature.serial = findtext(issuer, 'X509SerialNumber') certificate = findbin( signature_elm, 'KeyInfo/X509Data/X509Certificate') if certificate: certificate = base64.b64encode(certificate) signature.certificate = b'\n'.join( [b'-----BEGIN CERTIFICATE-----'] + [certificate[i:i + 64] for i in range(0, len(certificate), 64)] + [b'-----END CERTIFICATE-----'])
def parse_document(cls, pskc, container): """Read information from the provided <KeyContainer> tree.""" remove_namespaces(container) if container.tag not in ('KeyContainer', 'SecretContainer'): raise ParseError('Missing KeyContainer') # the version of the PSKC schema pskc.version = container.get('Version') or container.get('version') if (container.tag == 'KeyContainer' and pskc.version and pskc.version not in ('1', '1.0')): raise ParseError('Unsupported version %r' % pskc.version) # unique identifier for the container pskc.id = ( container.get('Id') or container.get('ID') or container.get('id')) # handle EncryptionKey entries cls.parse_encryption(pskc.encryption, find( container, 'EncryptionKey', 'EncryptionMethod')) # handle MACMethod entries cls.parse_mac_method(pskc.mac, find( container, 'MACMethod', 'DigestMethod')) # fall back to MACAlgorithm mac_algorithm = findtext(container, 'MACAlgorithm') if mac_algorithm: pskc.mac.algorithm = mac_algorithm # handle KeyPackage entries for key_package in findall(container, 'KeyPackage', 'Device'): cls.parse_key_package(pskc.add_device(), key_package) # handle Signature entries cls.parse_signature(pskc.signature, find(container, 'Signature'))
def parse(self, key_package): """Read key information from the provided <KeyPackage> tree.""" from pskc.xml import find, findtext, findtime, getint, getbool if key_package is None: return key = find(key_package, "pskc:Key") if key is not None: self.id = key.get("Id") self.algorithm = key.get("Algorithm") data = find(key_package, "pskc:Key/pskc:Data") if data is not None: self._secret.parse(find(data, "pskc:Secret")) self._counter.parse(find(data, "pskc:Counter")) self._time_offset.parse(find(data, "pskc:Time")) self._time_interval.parse(find(data, "pskc:TimeInterval")) self._time_drift.parse(find(data, "pskc:TimeDrift")) self.issuer = findtext(key_package, "pskc:Key/pskc:Issuer") self.key_profile = findtext(key_package, "pskc:Key/pskc:KeyProfileId") self.key_reference = findtext(key_package, "pskc:Key/pskc:KeyReference") self.friendly_name = findtext(key_package, "pskc:Key/pskc:FriendlyName") # TODO: support multi-language values of <FriendlyName> self.key_userid = findtext(key_package, "pskc:Key/pskc:UserId") self.manufacturer = findtext(key_package, "pskc:DeviceInfo/pskc:Manufacturer") self.serial = findtext(key_package, "pskc:DeviceInfo/pskc:SerialNo") self.model = findtext(key_package, "pskc:DeviceInfo/pskc:Model") self.issue_no = findtext(key_package, "pskc:DeviceInfo/pskc:IssueNo") self.device_binding = findtext(key_package, "pskc:DeviceInfo/pskc:DeviceBinding") self.start_date = findtime(key_package, "pskc:DeviceInfo/pskc:StartDate") self.expiry_date = findtime(key_package, "pskc:DeviceInfo/pskc:ExpiryDate") self.device_userid = findtext(key_package, "pskc:DeviceInfo/pskc:UserId") self.crypto_module = findtext(key_package, "pskc:CryptoModuleInfo/pskc:Id") self.algorithm_suite = findtext(key_package, "pskc:Key/pskc:AlgorithmParameters/pskc:Suite") challenge_format = find(key_package, "pskc:Key/pskc:AlgorithmParameters/pskc:ChallengeFormat") if challenge_format is not None: self.challenge_encoding = challenge_format.get("Encoding") self.challenge_min_length = getint(challenge_format, "Min") self.challenge_max_length = getint(challenge_format, "Max") self.challenge_check = getbool(challenge_format, "CheckDigits") response_format = find(key_package, "pskc:Key/pskc:AlgorithmParameters/pskc:ResponseFormat") if response_format is not None: self.response_encoding = response_format.get("Encoding") self.response_length = getint(response_format, "Length") self.response_check = getbool(response_format, "CheckDigits") self.policy.parse(find(key_package, "pskc:Key/pskc:Policy"))
def parse_encrypted_value(cls, encrypted_value): """Read encryption value from <EncryptedValue> element.""" algorithm = None cipher_value = findbin(encrypted_value, 'CipherData/CipherValue') encryption_method = find(encrypted_value, 'EncryptionMethod') if encryption_method is not None: algorithm = encryption_method.attrib.get('Algorithm') encryption_scheme = find( encrypted_value, 'EncryptionMethod/EncryptionScheme') if encryption_scheme is not None: algorithm = encryption_scheme.attrib.get('Algorithm') or algorithm return (algorithm, cipher_value)
def parse(self, element): """Read information from the provided element. The element is expected to contain <PlainValue>, <EncryptedValue> and/or ValueMAC elements that contain information on the actual value.""" from pskc.xml import find, findtext if element is None: return value = findtext(element, "pskc:PlainValue") if value is not None: self.value = self.from_text(value) self.encrypted_value.parse(find(element, "pskc:EncryptedValue")) self.value_mac.parse(find(element, "pskc:ValueMAC"))
def parse_encryption(cls, encryption, key_info): """Read encryption information from the <EncryptionKey> XML tree.""" if key_info is None: return encryption.id = key_info.get('Id') encryption.algorithm = ( key_info.get('Algorithm') or key_info.get('algorithm') or encryption.algorithm) for name in findall(key_info, 'KeyName', 'DerivedKey/MasterKeyName', 'DerivedKey/CarriedKeyName'): encryption.key_names.append(findtext(name, '.')) encryption.iv = findbin(key_info, 'IV') or encryption.iv cls.parse_key_derivation(encryption.derivation, find( key_info, 'DerivedKey/KeyDerivationMethod')) encryption.derivation.pbkdf2_salt = ( findbin(key_info, 'PBESalt') or encryption.derivation.pbkdf2_salt) encryption.derivation.pbkdf2_iterations = ( findint(key_info, 'PBEIterationCount') or encryption.derivation.pbkdf2_iterations) algorithm = ( key_info.get('Algorithm') or key_info.get('algorithm') or '') if (algorithm.lower().startswith('pbe') and not encryption.derivation.algorithm): encryption.derivation.algorithm = 'pbkdf2' encryption.derivation.pbkdf2_key_length = ( encryption.derivation.pbkdf2_key_length or encryption.algorithm_key_lengths[0])
def parse(self, mac_method): """Read MAC information from the <MACMethod> XML tree.""" from pskc.xml import find, findtext if mac_method is None: return self.algorithm = mac_method.get('Algorithm') self._mac_key.parse(find(mac_method, 'pskc:MACKey')) mac_key_reference = findtext(mac_method, 'pskc:MACKeyReference')
def parse_key_derivation(cls, derivation, key_derivation): """Read derivation parameters from a <KeyDerivationMethod> element.""" if key_derivation is None: return derivation.algorithm = key_derivation.get('Algorithm') # PBKDF2 properties pbkdf2 = find(key_derivation, 'PBKDF2-params') if pbkdf2 is not None: # get used salt derivation.pbkdf2_salt = findbin(pbkdf2, 'Salt/Specified') # required number of iterations derivation.pbkdf2_iterations = findint(pbkdf2, 'IterationCount') # key length derivation.pbkdf2_key_length = findint(pbkdf2, 'KeyLength') # pseudorandom function used prf = find(pbkdf2, 'PRF') if prf is not None: derivation.pbkdf2_prf = prf.get('Algorithm')
def parse(self, encrypted_value): """Read encrypted data from the <EncryptedValue> XML tree.""" from pskc.xml import find, findbin if encrypted_value is None: return encryption_method = find(encrypted_value, 'xenc:EncryptionMethod') if encryption_method is not None: self.algorithm = encryption_method.attrib.get('Algorithm') self.cipher_value = findbin( encrypted_value, 'xenc:CipherData/xenc:CipherValue')
def parse_mac_method(cls, mac, mac_method): """Read MAC information from the <MACMethod> XML tree.""" if mac_method is None: return mac.algorithm = ( mac_method.get('Algorithm') or mac_method.get('algorithm')) mac_key = find(mac_method, 'MACKey') if mac_key is not None: algorithm, cipher_value = cls.parse_encrypted_value(mac_key) mac.key = EncryptedValue(cipher_value, None, algorithm)
def parse(self, container): """Read information from the provided <KeyContainer> tree.""" from pskc.exceptions import ParseError from pskc.key import Key from pskc.xml import find, findall if not container.tag.endswith('KeyContainer'): raise ParseError('Missing KeyContainer') # the version of the PSKC schema self.version = container.get('Version') if self.version != '1.0': raise ParseError('Unsupported version %r' % self.version) # unique identifier for the container self.id = container.get('Id') # handle EncryptionKey entries self.encryption.parse(find(container, 'pskc:EncryptionKey')) # handle MACMethod entries self.mac.parse(find(container, 'pskc:MACMethod')) # handle KeyPackage entries for key_package in findall(container, 'pskc:KeyPackage'): self.keys.append(Key(self, key_package))
def parse(self, key_info): """Read encryption information from the <EncryptionKey> XML tree.""" from pskc.xml import find, findall, findtext if key_info is None: return self.id = key_info.get('Id') for name in findall(key_info, 'ds:KeyName'): self.key_names.append(findtext(name, '.')) for name in findall( key_info, 'xenc11:DerivedKey/xenc11:MasterKeyName'): self.key_names.append(findtext(name, '.')) self.derivation.parse(find( key_info, 'xenc11:DerivedKey/xenc11:KeyDerivationMethod'))
def make_xml(self, key, tag): from pskc.xml import find, mk_elem # skip empty values value = self.get_value() if value is None: return # find the data tag and create our tag under it data = find(key, "pskc:Data") if data is None: data = mk_elem(key, "pskc:Data", empty=True) element = mk_elem(data, tag, empty=True) mk_elem(element, "pskc:PlainValue", self.to_text(self.value))
def parse(self, key_deriviation): """Read derivation parameters from a <KeyDerivationMethod> element.""" from pskc.xml import find, findint, findbin if key_deriviation is None: return self.algorithm = key_deriviation.get('Algorithm') # PBKDF2 properties pbkdf2 = find( key_deriviation, 'xenc11:PBKDF2-params', 'pkcs5:PBKDF2-params') if pbkdf2 is not None: # get used salt self.pbkdf2_salt = findbin( pbkdf2, 'Salt/Specified', 'xenc11:Salt/xenc11:Specified') # required number of iterations self.pbkdf2_iterations = findint( pbkdf2, 'IterationCount', 'xenc11:IterationCount') # key length self.pbkdf2_key_length = findint( pbkdf2, 'KeyLength', 'xenc11:KeyLength') # pseudorandom function used prf = find(pbkdf2, 'PRF', 'xenc11:PRF') if prf is not None: self.pbkdf2_prf = prf.get('Algorithm')
def parse_data(cls, key, field, element): """Read information from the provided element. The element is expected to contain <PlainValue>, <EncryptedValue> and/or <ValueMAC> elements that contain information on the actual value. """ if element is None: return pskc = key.device.pskc plain_value = None cipher_value = None algorithm = None # get the plain2value function and encryption storage if field == 'secret': plain2value = base64.b64decode encrypted_value_cls = EncryptedValue else: plain2value = plain2int encrypted_value_cls = EncryptedIntegerValue # read plaintext value from <PlainValue> plain_value = findtext(element, 'PlainValue') if plain_value is not None: plain_value = plain2value(plain_value) # read encrypted data from <EncryptedValue> encrypted_value = find(element, 'EncryptedValue') if encrypted_value is not None: algorithm, cipher_value = cls.parse_encrypted_value( encrypted_value) # store the found algorithm in the pskc.encryption property if not pskc.encryption.algorithm and algorithm: pskc.encryption.algorithm = algorithm # read MAC information from <ValueMAC> mac_value = findbin(element, 'ValueMAC', 'ValueDigest') # read legacy <Value> elements (can be plain or encrypted) value = findtext(element, 'Value') if value is not None: if pskc.encryption.algorithm and mac_value: cipher_value = findbin(element, 'Value') else: plain_value = plain2value(value) # store the found information if plain_value is not None: setattr(key, field, plain_value) elif cipher_value: setattr(key, field, encrypted_value_cls(cipher_value, mac_value, algorithm))
def serialise_data(cls, key, field, key_elm, tag): """Provide an XML structure for the key material.""" value = getattr(key, '_%s' % field, None) pskc = key.device.pskc # skip empty values if value in (None, ''): return # get the value2text and encryption storage if field == 'secret': value2text = my_b64encode encrypted_value_cls = EncryptedValue else: value2text = str encrypted_value_cls = EncryptedIntegerValue # find the data tag and create our tag under it data = find(key_elm, 'pskc:Data') if data is None: data = mk_elem(key_elm, 'pskc:Data', empty=True) element = mk_elem(data, tag, empty=True) # see if we should encrypt the value if field in pskc.encryption.fields and not hasattr(value, 'get_value'): value = encrypted_value_cls.create(pskc, value) # write out value if not hasattr(value, 'get_value'): # unencrypted value mk_elem(element, 'pskc:PlainValue', value2text(value)) else: # encrypted value algorithm = value.algorithm or pskc.encryption.algorithm cipher_value = value.cipher_value if pskc.encryption.iv: cipher_value = pskc.encryption.iv + cipher_value encrypted_value = mk_elem(element, 'pskc:EncryptedValue', empty=True) mk_elem(encrypted_value, 'xenc:EncryptionMethod', Algorithm=algorithm) cipher_data = mk_elem(encrypted_value, 'xenc:CipherData', empty=True) mk_elem(cipher_data, 'xenc:CipherValue', base64.b64encode(cipher_value).decode()) if value.mac_value: mk_elem(element, 'pskc:ValueMAC', base64.b64encode(value.mac_value).decode())
def parse_key_package(cls, device, key_package): """Read key information from the provided <KeyPackage> tree.""" # find basic device information info = find(key_package, 'DeviceInfo', 'DeviceId') if info is not None: device.manufacturer = findtext(info, 'Manufacturer') device.serial = findtext(info, 'SerialNo') device.model = findtext(info, 'Model') device.issue_no = findtext(info, 'IssueNo') device.device_binding = findtext(info, 'DeviceBinding') device.start_date = findtime(info, 'StartDate') device.expiry_date = findtime(info, 'ExpiryDate', 'Expiry') device.device_userid = findtext(info, 'UserId') # find crypto module info device.crypto_module = findtext(key_package, 'CryptoModuleInfo/Id') # find keys for device for key_elm in findall(key_package, 'Key', 'Secret'): cls.parse_key(device.add_key(), key_elm)
def serialise_data(cls, key, field, key_elm, tag): """Provide an XML structure for the key material.""" value = getattr(key, '_%s' % field, None) pskc = key.device.pskc # skip empty values if value in (None, ''): return # get the value2text and encryption storage if field == 'secret': value2text = my_b64encode encrypted_value_cls = EncryptedValue else: value2text = str encrypted_value_cls = EncryptedIntegerValue # find the data tag and create our tag under it data = find(key_elm, 'pskc:Data') if data is None: data = mk_elem(key_elm, 'pskc:Data', empty=True) element = mk_elem(data, tag, empty=True) # see if we should encrypt the value if field in pskc.encryption.fields and not hasattr( value, 'get_value'): value = encrypted_value_cls.create(pskc, value) # write out value if not hasattr(value, 'get_value'): # unencrypted value mk_elem(element, 'pskc:PlainValue', value2text(value)) else: # encrypted value algorithm = value.algorithm or pskc.encryption.algorithm cipher_value = value.cipher_value if pskc.encryption.iv: cipher_value = pskc.encryption.iv + cipher_value encrypted_value = mk_elem( element, 'pskc:EncryptedValue', empty=True) mk_elem(encrypted_value, 'xenc:EncryptionMethod', Algorithm=algorithm) cipher_data = mk_elem( encrypted_value, 'xenc:CipherData', empty=True) mk_elem(cipher_data, 'xenc:CipherValue', base64.b64encode(cipher_value).decode()) if value.mac_value: mk_elem(element, 'pskc:ValueMAC', base64.b64encode(value.mac_value).decode())
def parse_policy(cls, policy, policy_elm): """Read key policy information from the provided <Policy> tree.""" if policy_elm is None: return policy.start_date = findtime(policy_elm, 'StartDate') policy.expiry_date = findtime(policy_elm, 'ExpiryDate') policy.number_of_transactions = findint( policy_elm, 'NumberOfTransactions') for key_usage in findall(policy_elm, 'KeyUsage'): policy.key_usage.append(findtext(key_usage, '.')) pin_policy_elm = find(policy_elm, 'PINPolicy') if pin_policy_elm is not None: policy.pin_key_id = pin_policy_elm.get('PINKeyId') policy.pin_usage = pin_policy_elm.get('PINUsageMode') policy.pin_max_failed_attempts = getint( pin_policy_elm, 'MaxFailedAttempts') policy.pin_min_length = getint(pin_policy_elm, 'MinLength') policy.pin_max_length = getint(pin_policy_elm, 'MaxLength') policy.pin_encoding = pin_policy_elm.get('PINEncoding') # check for child elements if list(pin_policy_elm): policy.unknown_policy_elements = True # check for unknown attributes known_attributes = set([ 'PINKeyId', 'PINUsageMode', 'MaxFailedAttempts', 'MinLength', 'MaxLength', 'PINEncoding']) if set(pin_policy_elm.keys()) - known_attributes: policy.unknown_policy_elements = True # check for other child elements known_children = set([ 'StartDate', 'ExpiryDate', 'NumberOfTransactions', 'KeyUsage', 'PINPolicy']) for child in policy_elm: if child.tag not in known_children: policy.unknown_policy_elements = True
def parse(self, policy): """Read key policy information from the provided <Policy> tree.""" from pskc.xml import ( find, findall, findtext, findint, findtime, getint) if policy is None: return self.start_date = findtime(policy, 'pskc:StartDate') self.expiry_date = findtime(policy, 'pskc:ExpiryDate') self.number_of_transactions = findint( policy, 'pskc:NumberOfTransactions') for key_usage in findall(policy, 'pskc:KeyUsage'): self.key_usage.append(findtext(key_usage, '.')) pin_policy = find(policy, 'pskc:PINPolicy') if pin_policy is not None: self.pin_key_id = pin_policy.get('PINKeyId') self.pin_usage = pin_policy.get('PINUsageMode') self.pin_max_failed_attemtps = getint( pin_policy, 'MaxFailedAttempts') self.pin_min_length = getint(pin_policy, 'MinLength') self.pin_max_length = getint(pin_policy, 'MaxLength') self.pin_encoding = pin_policy.get('PINEncoding')
def serialise_datatype(cls, dt, key_elm, tag, field): # skip empty values if dt.value in (None, '') and not dt.cipher_value: return # find the data tag and create our tag under it data = find(key_elm, 'pskc:Data') if data is None: data = mk_elem(key_elm, 'pskc:Data', empty=True) element = mk_elem(data, tag, empty=True) # see if we should encrypt if field in dt.pskc.encryption.fields and not dt.cipher_value: dt.cipher_value = dt.pskc.encryption.encrypt_value( dt._to_bin(dt.value)) dt.algorithm = dt.pskc.encryption.algorithm dt.value = None # write out value if dt.cipher_value: encrypted_value = mk_elem( element, 'pskc:EncryptedValue', empty=True) mk_elem( encrypted_value, 'xenc:EncryptionMethod', Algorithm=dt.algorithm) cipher_data = mk_elem( encrypted_value, 'xenc:CipherData', empty=True) mk_elem( cipher_data, 'xenc:CipherValue', base64.b64encode(dt.cipher_value).decode()) if dt.value_mac: mk_elem(element, 'pskc:ValueMAC', base64.b64encode( dt.value_mac).decode()) elif dt.pskc.mac.algorithm: mk_elem(element, 'pskc:ValueMAC', base64.b64encode( dt.pskc.mac.generate_mac(dt.cipher_value) ).decode()) else: mk_elem(element, 'pskc:PlainValue', dt._to_text(dt.value))
def parse_key(cls, key, key_elm): """Read key information from the provided <KeyPackage> tree.""" # get key basic information key.id = ( key_elm.get('Id') or key_elm.get('KeyId') or key_elm.get('SecretId')) key.algorithm = ( key_elm.get('Algorithm') or key_elm.get('KeyAlgorithm') or key_elm.get('SecretAlgorithm')) # parse data section with possibly encrypted data data = find(key_elm, 'Data') if data is not None: cls.parse_data(key, 'secret', find(data, 'Secret')) cls.parse_data(key, 'counter', find(data, 'Counter')) cls.parse_data(key, 'time_offset', find(data, 'Time')) cls.parse_data(key, 'time_interval', find(data, 'TimeInterval')) cls.parse_data(key, 'time_drift', find(data, 'TimeDrift')) # parse legacy data elements with name attribute for data in findall(key_elm, 'Data'): name = data.get('Name') if name: cls.parse_data(key, dict( secret='secret', counter='counter', time='time_offset', time_interval='time_interval', ).get(name.lower()), data) # parse more basic key properties key.issuer = findtext(key_elm, 'Issuer') key.key_profile = findtext(key_elm, 'KeyProfileId') key.key_reference = findtext(key_elm, 'KeyReference') key.friendly_name = findtext(key_elm, 'FriendlyName') # TODO: support multi-language values of <FriendlyName> key.key_userid = findtext(key_elm, 'UserId') key.algorithm_suite = findtext( key_elm, 'AlgorithmParameters/Suite') # parse challenge format challenge_format = find( key_elm, 'AlgorithmParameters/ChallengeFormat', 'Usage/ChallengeFormat') if challenge_format is not None: key.challenge_encoding = ( challenge_format.get('Encoding') or challenge_format.get('Format') or challenge_format.get('format')) key.challenge_min_length = ( getint(challenge_format, 'Min') or getint(challenge_format, 'min')) key.challenge_max_length = ( getint(challenge_format, 'Max') or getint(challenge_format, 'max')) key.challenge_check = getbool( challenge_format, 'CheckDigits', getbool( challenge_format, 'CheckDigit')) # parse response format response_format = find( key_elm, 'AlgorithmParameters/ResponseFormat', 'Usage/ResponseFormat') if response_format is not None: key.response_encoding = ( response_format.get('Encoding') or response_format.get('Format') or response_format.get('format')) key.response_length = ( getint(response_format, 'Length') or getint(response_format, 'length')) key.response_check = getbool( response_format, 'CheckDigits', getbool( response_format, 'CheckDigit')) # parse key policy information cls.parse_policy(key.policy, find(key_elm, 'Policy')) # parse key usage information usage = find(key_elm, 'Usage') if usage is not None: for att in ('OTP', 'CR', 'Integrity', 'Encrypt', 'Unlock'): if getbool(usage, att): key.policy.key_usage.append(att) key.policy.start_date = ( findtime(key_elm, 'StartDate') or key.policy.start_date) key.policy.expiry_date = ( findtime(key_elm, 'ExpiryDate') or key.policy.expiry_date)