Source code for hsslms.pershss

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Sat Jan  1 20:00:55 2022

@author: mvr
"""
import os
import pickle
from .restricted_unpickler import restricted_loads
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
from cryptography.exceptions import InvalidTag
from .hss import HSS_Priv
from .utils import FAILURE
from . import __version__


[docs]def kdf(salt, password): return PBKDF2HMAC(algorithm=hashes.SHA256(), length=32, salt=salt, iterations=390000).derive(password)
[docs]class PersHSS_Priv(HSS_Priv): """A class derived from HSS_Priv. It is used to generate the private key and derive the public key of a Hierarchical Signature System (HSS) The private key is signed and stored in an encrypted file. Args: lmstypecodes: List of LMS_ALGORITHM_TYPE otstypecode: LMOTS_ALGORITHM_TYPE filename (str): holds the name of the file to store the key password (bytes): password to sign and encrypt the file frequence (int): frequnce at which the key is stored to a file """ FILEHEADER = b'PersHSS_Priv_v\x00' + __version__.encode('utf-8') def __init__(self, lmstypecodes, otstypecode, filename, password, frequence, num_cores): super().__init__(lmstypecodes, otstypecode, num_cores) self.filename = filename self.frequence = frequence self.sign_count = 0 self.salt = os.urandom(16); self.key = kdf(self.salt, password)
[docs] def sign(self, message): """Signs the message with the private key associated with the class. The key is automatically stored to disk after frequnce signatures. Args: message (bytes, BufferedReader): Message to be signed Raises: FAILURE: If a signature has already been computed, or for other technical reason Returns: bytes: The signature to `message`. """ signature = super().sign(message) self.sign_count += 1 if self.sign_count % self.frequence == 0: self.save() return signature
[docs] def save(self): """The key is saved. """ try: os.rename(self.filename, self.filename + '.bak') except FileNotFoundError: pass data = pickle.dumps(self) aesgcm = AESGCM(self.key) nonce = os.urandom(12) try: with open(self.filename, 'wb') as fout: fout.write(PersHSS_Priv.FILEHEADER) fout.write(self.salt) fout.write(nonce) fout.write(aesgcm.encrypt(nonce, data, PersHSS_Priv.FILEHEADER)) except IOError: raise FAILURE("File %s cannot be saved." % self.filename) try: os.remove(self.filename + '.bak') except OSError: pass
[docs] def from_file(filename, password): """A key, HSS_Priv, is loaded from a password-protected file. Frequnce signatures are skipped to ensure that no private key is used more than once. Args: filename (str): name of the file password (bytes): password of the file Raises: FAILURE: if the key cannot be loaded Returns: HSS_Priv """ try: with open(filename, 'rb') as fin: fh = fin.read(len(PersHSS_Priv.FILEHEADER)) if fh != PersHSS_Priv.FILEHEADER: raise FAILURE("Invalid file type.") salt = fin.read(16) if len(salt) < 16: raise FAILURE("Invalid file.") key = kdf(salt, password) aesgcm = AESGCM(key) nonce = fin.read(12) if len(nonce) < 12: raise FAILURE("Invalid file.") data = aesgcm.decrypt(nonce, fin.read(), fh) sk = restricted_loads(data) if not type(sk) is PersHSS_Priv: raise FAILURE("Wrong Object Type.") except InvalidTag: raise FAILURE("Wrong password.") except IOError: raise FAILURE("File %s cannot be read." % filename) except pickle.PickleError as e: print(e) raise FAILURE("Cannot load private key.") # skip next signatures for _ in range(sk.frequence-1): sk.sign(b'') return sk