# mac.py - module for checking value signatures # coding: utf-8 # # Copyright (C) 2014-2016 Arthur de Jong # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301 USA """Module that provides message authentication for PSKC values. This module provides a MAC class that is used to store information about how the MAC should be calculated (including the MAC key) and a ValueMAC class that provides (H)MAC checking for PSKC key data. The MAC key is generated specifically for each PSKC file and encrypted with the PSKC encryption key. """ import re _hmac_url_re = re.compile(r'^.*#hmac-(?P[a-z0-9]+)$') def get_hash(algorithm): """Return the hash function for the specifies HMAC algorithm.""" import hashlib match = _hmac_url_re.search(algorithm) if match: return getattr(hashlib, match.group('hash'), None) def get_hmac(algorithm): """Return an HMAC function that takes a secret and a value and returns a digest.""" import hmac digestmod = get_hash(algorithm) if digestmod is not None: return lambda key, value: hmac.new(key, value, digestmod).digest() def get_mac(algorithm, key, value): """Generate the MAC value over the specified value.""" from pskc.exceptions import DecryptionError if algorithm is None: raise DecryptionError('No MAC algorithm set') hmacfn = get_hmac(algorithm) if hmacfn is None: raise DecryptionError( 'Unsupported MAC algorithm: %r' % algorithm) return hmacfn(key, value) class MAC(object): """Class describing the MAC algorithm to use and how to get the key. Instances of this class provide the following attributes: algorithm: the name of the HMAC to use (currently only HMAC_SHA1) key: the binary value of the MAC key if it can be decrypted """ def __init__(self, pskc): self.pskc = pskc self._algorithm = None self.key_plain_value = None self.key_cipher_value = None self.key_algorithm = None @property def key(self): """Provides access to the MAC key binary value if available.""" if self.key_plain_value: return self.key_plain_value elif self.key_cipher_value: return self.pskc.encryption.decrypt_value( self.key_cipher_value, self.key_algorithm) # fall back to encryption key return self.pskc.encryption.key @key.setter def key(self, value): self.key_plain_value = value self.key_cipher_value = None @property def algorithm(self): """Provide the MAC algorithm used.""" if self._algorithm: return self._algorithm @algorithm.setter def algorithm(self, value): from pskc.algorithms import normalise_algorithm self._algorithm = normalise_algorithm(value) @property def algorithm_key_length(self): """Recommended minimal key length in bytes for the set algorithm.""" # https://tools.ietf.org/html/rfc2104#section-3 # an HMAC key should be at least as long as the hash output length hashfn = get_hash(self.algorithm) if hashfn is not None: return int(hashfn().digest_size) else: return 16 def generate_mac(self, value): """Generate the MAC over the specified value.""" return get_mac(self.algorithm, self.key, value) def setup(self, key=None, algorithm=None): """Configure an encrypted MAC key. The following arguments may be supplied: key: the MAC key to use algorithm: MAC algorithm None of the arguments are required, reasonable defaults will be chosen for missing arguments. """ if key: self.key = key if algorithm: self.algorithm = algorithm # default to HMAC-SHA1 if not self.algorithm: self.algorithm = 'hmac-sha1' # generate an HMAC key if not self.key: from Crypto import Random self.key = Random.get_random_bytes(self.algorithm_key_length)