def open_pkcs11_session(lib_location, slot_no=None, token_label=None, user_pin=None) -> Session: """ Open a PKCS#11 session :param lib_location: Path to the PKCS#11 module. :param slot_no: Slot number to use. If not specified, the first slot containing a token labelled ``token_label`` will be used. :param token_label: Label of the token to use. If ``None``, there is no constraint. :param user_pin: User PIN to use. .. note:: Some PKCS#11 implementations do not require PIN when the token is opened, but will prompt for it out-of-band when signing. :return: An open PKCS#11 session object. """ lib = pkcs11_lib(lib_location) slots = lib.get_slots() token = None if slot_no is None: for slot in slots: try: token = slot.get_token() if token_label is None or token.label == token_label: break except PKCS11Error: continue if token is None: raise PKCS11Error( f'No token with label {token_label} found' if token_label is not None else 'No token found' ) else: token = slots[slot_no].get_token() if token_label is not None and token.label != token_label: raise PKCS11Error('Token in slot %d is not BELPIC.' % slot_no) kwargs = {} if user_pin is not None: kwargs['user_pin'] = user_pin return token.open(**kwargs)
def _pull_cert(pkcs11_session: Session, label: Optional[str] = None, cert_id: Optional[bytes] = None): query_params = {Attribute.CLASS: ObjectClass.CERTIFICATE} if label is not None: query_params[Attribute.LABEL] = label if cert_id is not None: query_params[Attribute.ID] = cert_id q = pkcs11_session.get_objects(query_params) # need to run through the full iterator to make sure the operation # terminates results = list(q) if len(results) == 1: cert_obj = results[0] return x509.Certificate.load(cert_obj[Attribute.VALUE]) else: info_strs = [] if label is not None: info_strs.append(f"label '{label}'") if cert_id is not None: info_strs.append( f"ID '{binascii.hexlify(cert_id).decode('ascii')}'") qualifier = f" with {', '.join(info_strs)}" if info_strs else "" if not results: err = f"Could not find cert{qualifier}." else: err = f"Found more than one cert{qualifier}." raise PKCS11Error(err)
def _load_objects(self): if self._loaded: return self._init_cert_registry() self._signing_cert = _pull_cert(self.pkcs11_session, self.cert_label) q = self.pkcs11_session.get_objects({ Attribute.LABEL: self.key_label, Attribute.CLASS: ObjectClass.PRIVATE_KEY }) try: kh, = list(q) except ValueError as e: raise PKCS11Error( "Could not determine private key handle." ) from e if not kh[Attribute.SIGN]: logger.warning( f"The PKCS#11 device reports that the key with label " f"{self.key_label} cannot be used for signing!" ) self._key_handle = kh self._loaded = True
def open_beid_session(lib_location, slot_no=None) -> Session: """ Open a PKCS#11 session :param lib_location: Path to the shared library file containing the eID PKCS#11 module. Usually, the file is named ``libbeidpkcs11.so``, ``libbeidpkcs11.dylib`` or ``beidpkcs11.dll``, depending on your operating system. :param slot_no: Slot number to use. If not specified, the first slot containing a token labelled ``BELPIC`` will be used. :return: An open PKCS#11 session object. """ lib = pkcs11_lib(lib_location) slots = lib.get_slots() token = None if slot_no is None: for slot in slots: try: token = slot.get_token() if token.label == 'BELPIC': break except PKCS11Error: continue if token is None: raise PKCS11Error('No BELPIC token found') else: token = slots[slot_no].get_token() if token.label != 'BELPIC': raise PKCS11Error('Token in slot %d is not BELPIC.' % slot_no) # the middleware will prompt for the user's PIN when we attempt # to sign later, so there's no need to specify it here return token.open()
def _pull_cert(pkcs11_session: Session, label: str): q = pkcs11_session.get_objects({ Attribute.LABEL: label, Attribute.CLASS: ObjectClass.CERTIFICATE }) # need to run through the full iterator to make sure the operation # terminates try: cert_obj, = list(q) except ValueError: raise PKCS11Error( f"Could not find (unique) cert with label '{label}'." ) return oskeys.parse_certificate(cert_obj[Attribute.VALUE])
def sign_raw(self, data: bytes, digest_algorithm: str, dry_run=False) \ -> bytes: if dry_run: # allocate 4096 bits for the fake signature return b'0' * 512 self._load_objects() from pkcs11 import Mechanism, SignMixin, MGF kh: SignMixin = self._key_handle kwargs = {} digest_algorithm = digest_algorithm.lower() signature_mechanism = self.get_signature_mechanism(digest_algorithm) signature_algo = signature_mechanism.signature_algo transform = None if signature_algo == 'rsassa_pkcs1v15': kwargs['mechanism'] = { 'sha1': Mechanism.SHA1_RSA_PKCS, 'sha256': Mechanism.SHA256_RSA_PKCS, 'sha384': Mechanism.SHA384_RSA_PKCS, 'sha512': Mechanism.SHA512_RSA_PKCS, }[digest_algorithm] elif signature_algo == 'ecdsa': # TODO test these, SoftHSM does not support these mechanisms # apparently (only raw ECDSA) kwargs['mechanism'] = { 'sha1': Mechanism.ECDSA_SHA1, 'sha256': Mechanism.ECDSA_SHA256, 'sha384': Mechanism.ECDSA_SHA384, 'sha512': Mechanism.ECDSA_SHA512, }[digest_algorithm] from pkcs11.util.ec import encode_ecdsa_signature transform = encode_ecdsa_signature elif signature_algo == 'rsassa_pss': params: RSASSAPSSParams = signature_mechanism['parameters'] assert digest_algorithm == \ params['hash_algorithm']['algorithm'].native # unpack PSS parameters into PKCS#11 language kwargs['mechanism'] = { 'sha1': Mechanism.SHA1_RSA_PKCS_PSS, 'sha256': Mechanism.SHA256_RSA_PKCS_PSS, 'sha384': Mechanism.SHA384_RSA_PKCS_PSS, 'sha512': Mechanism.SHA512_RSA_PKCS_PSS, }[digest_algorithm] pss_digest_param = { 'sha1': Mechanism.SHA_1, 'sha256': Mechanism.SHA256, 'sha384': Mechanism.SHA384, 'sha512': Mechanism.SHA512, }[digest_algorithm] pss_mgf_param = { 'sha1': MGF.SHA1, 'sha256': MGF.SHA256, 'sha384': MGF.SHA384, 'sha512': MGF.SHA512 }[params['mask_gen_algorithm']['parameters']['algorithm'].native] pss_salt_len = params['salt_length'].native kwargs['mechanism_param'] = ( pss_digest_param, pss_mgf_param, pss_salt_len ) else: raise PKCS11Error( f"Signature algorithm '{signature_algo}' is not supported." ) signature = kh.sign(data, **kwargs) if transform is not None: signature = transform(signature) return signature
async def async_sign_raw(self, data: bytes, digest_algorithm: str, dry_run=False) -> bytes: if dry_run: # allocate 4096 bits for the fake signature return b'0' * 512 await self.ensure_objects_loaded() from pkcs11 import MGF, Mechanism, SignMixin kh: SignMixin = self._key_handle kwargs = {} digest_algorithm = digest_algorithm.lower() signature_mechanism = self.get_signature_mechanism(digest_algorithm) signature_algo = signature_mechanism.signature_algo pre_sign_transform = None post_sign_transform = None if signature_algo == 'rsassa_pkcs1v15': if self.use_raw_mechanism: raise NotImplementedError( "RSASSA-PKCS1v15 not available in raw mode") kwargs['mechanism'] = { 'sha1': Mechanism.SHA1_RSA_PKCS, 'sha224': Mechanism.SHA224_RSA_PKCS, 'sha256': Mechanism.SHA256_RSA_PKCS, 'sha384': Mechanism.SHA384_RSA_PKCS, 'sha512': Mechanism.SHA512_RSA_PKCS, }[digest_algorithm] elif signature_algo == 'dsa': if self.use_raw_mechanism: raise NotImplementedError("DSA not available in raw mode") kwargs['mechanism'] = { 'sha1': Mechanism.DSA_SHA1, 'sha224': Mechanism.DSA_SHA224, 'sha256': Mechanism.DSA_SHA256, # These can't be used in CMS IIRC (since the key sizes required # to meaningfully use them are ridiculous), # but they're in the PKCS#11 spec, so let's add them for # completeness 'sha384': Mechanism.DSA_SHA384, 'sha512': Mechanism.DSA_SHA512, }[digest_algorithm] from pkcs11.util.dsa import encode_dsa_signature post_sign_transform = encode_dsa_signature elif signature_algo == 'ecdsa': if self.use_raw_mechanism: kwargs['mechanism'] = Mechanism.ECDSA pre_sign_transform = _hash_fully(digest_algorithm) else: # TODO test these (unsupported in SoftHSMv2 right now) kwargs['mechanism'] = { 'sha1': Mechanism.ECDSA_SHA1, 'sha224': Mechanism.ECDSA_SHA224, 'sha256': Mechanism.ECDSA_SHA256, 'sha384': Mechanism.ECDSA_SHA384, 'sha512': Mechanism.ECDSA_SHA512, }[digest_algorithm] from pkcs11.util.ec import encode_ecdsa_signature post_sign_transform = encode_ecdsa_signature elif signature_algo == 'rsassa_pss': if self.use_raw_mechanism: raise NotImplementedError( "RSASSA-PSS not available in raw mode") params: RSASSAPSSParams = signature_mechanism['parameters'] assert digest_algorithm == \ params['hash_algorithm']['algorithm'].native # unpack PSS parameters into PKCS#11 language kwargs['mechanism'] = { 'sha1': Mechanism.SHA1_RSA_PKCS_PSS, 'sha224': Mechanism.SHA224_RSA_PKCS_PSS, 'sha256': Mechanism.SHA256_RSA_PKCS_PSS, 'sha384': Mechanism.SHA384_RSA_PKCS_PSS, 'sha512': Mechanism.SHA512_RSA_PKCS_PSS, }[digest_algorithm] pss_digest_param = { 'sha1': Mechanism.SHA_1, 'sha224': Mechanism.SHA224, 'sha256': Mechanism.SHA256, 'sha384': Mechanism.SHA384, 'sha512': Mechanism.SHA512, }[digest_algorithm] pss_mgf_param = { 'sha1': MGF.SHA1, 'sha224': MGF.SHA224, 'sha256': MGF.SHA256, 'sha384': MGF.SHA384, 'sha512': MGF.SHA512 }[params['mask_gen_algorithm']['parameters']['algorithm'].native] pss_salt_len = params['salt_length'].native kwargs['mechanism_param'] = (pss_digest_param, pss_mgf_param, pss_salt_len) else: raise PKCS11Error( f"Signature algorithm '{signature_algo}' is not supported.") if pre_sign_transform is not None: data = pre_sign_transform(data) def _perform_signature(): signature = kh.sign(data, **kwargs) if post_sign_transform is not None: signature = post_sign_transform(signature) return signature loop = asyncio.get_running_loop() return await loop.run_in_executor(None, _perform_signature)