diff --git a/libpkpass/commands/arguments.py b/libpkpass/commands/arguments.py index e596cc0..e08e587 100644 --- a/libpkpass/commands/arguments.py +++ b/libpkpass/commands/arguments.py @@ -264,4 +264,20 @@ "help": "verbose output (repeat for increased verbosity)", }, }, + "SCBackend": { + "args": ["--scbackend"], + "kwargs": { + "type": str, + "default": "opensc", + "help": "SC backend to use: opensc or yubi", + }, + }, + "PKCS11_module_path": { + "args": ["--PKCS11-module-path"], + "kwargs": { + "type": str, + "default": "/usr/local/lib/libykcs11.dylib", + "help": "Path to yubi PKCS11 module", + }, + }, } diff --git a/libpkpass/commands/card.py b/libpkpass/commands/card.py index 53523ed..1aded89 100644 --- a/libpkpass/commands/card.py +++ b/libpkpass/commands/card.py @@ -22,6 +22,7 @@ def _run_command_execution(self): 2, self.args["color"], self.args["theme_map"], + self.args["SCBackend"], ) def _validate_args(self): diff --git a/libpkpass/commands/clip.py b/libpkpass/commands/clip.py index 1b3e62f..c5dd02c 100644 --- a/libpkpass/commands/clip.py +++ b/libpkpass/commands/clip.py @@ -40,6 +40,8 @@ def _run_command_execution(self): identity=self.iddb.id, passphrase=self.passphrase, card_slot=self.args["card_slot"], + SCBackend=self.args["SCBackend"], + PKCS11_module_path=self.args["PKCS11_module_path"], ) if not self.args["noverify"]: result = password.verify_entry( diff --git a/libpkpass/commands/command.py b/libpkpass/commands/command.py index d6c2a5b..c00be19 100644 --- a/libpkpass/commands/command.py +++ b/libpkpass/commands/command.py @@ -73,6 +73,7 @@ def _passphrase_check(self): self.args["verbosity"], self.args["color"], self.args["theme_map"], + self.args["SCBackend"], ): LOGGER.info(mesg) self.passphrase = getpass.getpass("Enter Pin/Passphrase: ") @@ -161,6 +162,8 @@ def update_pass(self, pass_value): card_slot=self.args["card_slot"], escrow_users=self.args["escrow_users"], minimum=self.args["min_escrow"], + SCBackend=self.args["SCBackend"], + PKCS11_module_path=self.args["PKCS11_module_path"], ) pass_entry["recipients"][self.args["identity"]] = swap_pass["recipients"][ self.args["identity"] @@ -196,6 +199,8 @@ def create_pass(self, password1, description, authorizer, recipient_list=None): card_slot=self.args["card_slot"], escrow_users=self.args["escrow_users"], minimum=self.args["min_escrow"], + SCBackend=self.args["SCBackend"], + PKCS11_module_path=self.args["PKCS11_module_path"], ) password.write_password_data( diff --git a/libpkpass/commands/distribute.py b/libpkpass/commands/distribute.py index 79a82fc..1b1a2ea 100644 --- a/libpkpass/commands/distribute.py +++ b/libpkpass/commands/distribute.py @@ -50,6 +50,8 @@ def _run_command_execution(self): self.iddb.id, passphrase=self.passphrase, card_slot=self.args["card_slot"], + SCBackend=self.args["SCBackend"], + PKCS11_module_path=self.args["PKCS11_module_path"], ) password.add_recipients( secret=plaintext_pw, @@ -60,6 +62,8 @@ def _run_command_execution(self): card_slot=self.args["card_slot"], escrow_users=self.args["escrow_users"], minimum=self.args["min_escrow"], + SCBackend=self.args["SCBackend"], + PKCS11_module_path=self.args["PKCS11_module_path"], ) password.write_password_data(dist_pass) diff --git a/libpkpass/commands/export.py b/libpkpass/commands/export.py index 52c5438..3e747c2 100644 --- a/libpkpass/commands/export.py +++ b/libpkpass/commands/export.py @@ -47,6 +47,8 @@ def _iterate_pdb(self, passworddb, crypt_pass=False): identity=self.iddb.id, passphrase=self.passphrase, card_slot=self.args["card_slot"], + SCBackend=self.args["SCBackend"], + PKCS11_module_path=self.args["PKCS11_module_path"], ) password.recipients[uid]["encrypted_secret"] = plaintext_pw.encode("UTF-8") password.write_password_data( diff --git a/libpkpass/commands/populate.py b/libpkpass/commands/populate.py index e975b40..ab5ac1c 100644 --- a/libpkpass/commands/populate.py +++ b/libpkpass/commands/populate.py @@ -176,6 +176,8 @@ def _decrypt_password_entry(self, password): identity=self.iddb.id, passphrase=self.passphrase, card_slot=self.args["card_slot"], + SCBackend=self.args["SCBackend"], + PKCS11_module_path=self.args["PKCS11_module_path"], ) distributor = password.recipients[self.iddb.id["name"]]["distributor"] if not self.args["noverify"]: diff --git a/libpkpass/commands/rename.py b/libpkpass/commands/rename.py index 7365aa8..ef433ad 100644 --- a/libpkpass/commands/rename.py +++ b/libpkpass/commands/rename.py @@ -43,6 +43,8 @@ def _run_command_execution(self): identity=self.iddb.id, passphrase=self.passphrase, card_slot=self.args["card_slot"], + SCBackend=self.args["SCBackend"], + PKCS11_module_path=self.args["PKCS11_module_path"], ) self._confirmation(plaintext_pw) else: diff --git a/libpkpass/commands/show.py b/libpkpass/commands/show.py index 6df616b..5fc5c98 100644 --- a/libpkpass/commands/show.py +++ b/libpkpass/commands/show.py @@ -75,6 +75,8 @@ def _behalf_prep(self, password): identity=self.iddb.id, passphrase=self.passphrase, card_slot=self.args["card_slot"], + SCBackend=self.args["SCBackend"], + PKCS11_module_path=self.args["PKCS11_module_path"], ) with open(temp_key, "w", encoding="ASCII") as fname: fname.write( @@ -154,6 +156,8 @@ def _decrypt_password_entry(self, password, distributor): identity=self.iddb.id, passphrase=self.passphrase, card_slot=self.args["card_slot"], + SCBackend=self.args["SCBackend"], + PKCS11_module_path=self.args["PKCS11_module_path"], ) dist_obj = ( self.iddb.session.query(Recipient) diff --git a/libpkpass/crypto.py b/libpkpass/crypto.py index 172ca48..1f5bf39 100755 --- a/libpkpass/crypto.py +++ b/libpkpass/crypto.py @@ -2,7 +2,7 @@ """This Module handles the crypto functions i.e. encryption and decryption""" from base64 import urlsafe_b64decode, urlsafe_b64encode from tempfile import NamedTemporaryFile -from os import unlink +from os import unlink, environ from hashlib import sha256 from shutil import get_terminal_size from subprocess import Popen, PIPE, STDOUT, DEVNULL @@ -16,6 +16,8 @@ DecryptionError, SignatureCreationError, X509CertificateError, + BadBackendError, + PKPassError, ) @@ -62,27 +64,47 @@ def pk_encrypt_string(plaintext_string, certificate): ) -def get_card_info(): - command = ["pkcs11-tool", "-L"] - with Popen(command, stdout=PIPE, stdin=PIPE, stderr=STDOUT) as proc: - stdout, _ = proc.communicate() - return (handle_python_strings(stdout).split(b"Slot"), stdout) +def get_card_info(SCBackend="opensc"): + if SCBackend == "opensc": + command = ["pkcs11-tool", "-L"] + with Popen(command, stdout=PIPE, stdin=PIPE, stderr=STDOUT) as proc: + stdout, _ = proc.communicate() + return (handle_python_strings(stdout).split(b"Slot"), stdout) + elif SCBackend == "yubi": + command = ["yubico-piv-tool", "-a", "list-readers"] + with Popen(command, stdout=PIPE, stdin=PIPE, stderr=STDOUT) as proc: + stdout, _ = proc.communicate() + return (handle_python_strings(stdout).splitlines(), stdout) + raise BadBackendError(SCBackend) -def print_card_info(card_slot, identity, verbosity, color, theme_map): +def print_card_info(card_slot, identity, verbosity, color, theme_map, SCBackend): #################################################################### """Inform the user what card is selected""" #################################################################### if "key" not in identity or not identity["key"]: - out_list, stdout = get_card_info() + out_list, stdout = get_card_info(SCBackend) if verbosity > 1: yield print_all_slots(stdout, color, theme_map) - for out in out_list[1:]: - stripped = out.decode("UTF-8").strip() - if int(stripped[0]) == int(card_slot): - verbosity = verbosity + 1 if verbosity < 2 else 2 - stripped = "Using Slot" + ("\n").join(stripped.split("\n")[:verbosity]) - yield f"{color_prepare(stripped, 'info', color, theme_map)}" + if SCBackend == "opensc": + for out in out_list[1:]: + stripped = out.decode("UTF-8").strip() + if int(stripped[0]) == int(card_slot): + verbosity = verbosity + 1 if verbosity < 2 else 2 + stripped = "Using Slot" + ("\n").join(stripped.split("\n")[:verbosity]) + yield f"{color_prepare(stripped, 'info', color, theme_map)}" + elif SCBackend == "yubi": + for out in out_list: + stripped = out.decode("UTF-8").strip() + if "Yubico" not in stripped: + # exit(1) + raise PKPassError("Unsupported SC type for backend yubi.\nYubico not in:\n" + stripped) + if int(stripped.split('CCID')[1] or 0) == int(card_slot): + verbosity = verbosity + 1 if verbosity < 2 else 2 + stripped = "Using Slot" + ("\n").join(stripped.split("\n")[:verbosity]) + yield f"{color_prepare(stripped, 'info', color, theme_map)}" + else: + raise BadBackendError(SCBackend) def print_all_slots(slot_info, color, theme_map): @@ -98,14 +120,14 @@ def print_all_slots(slot_info, color, theme_map): def pk_decrypt_string( - ciphertext_string, ciphertext_derived_key, identity, passphrase, card_slot=None + ciphertext_string, ciphertext_derived_key, identity, passphrase, SCBackend="opensc", card_slot=None, PKCS11_module_path="/usr/local/lib/libykcs11.dylib" ): #################################################################### """Decrypt a base64 encoded string for the provided identity""" #################################################################### ciphertext_derived_key = handle_python_strings(ciphertext_derived_key) if "key" in identity and identity["key"]: - command = ["openssl", "pkeyutl", "-decrypt", "-inkey", identity["key"], "-pkeyopt", "rsa_padding_mode:pkcs1"] + command = ["openssl", "pkeyutl", "-decrypt", "-inkey", identity["key"], "-pkeyopt", "rsa_padding_mode:pkcs1"] with Popen(command, stdout=PIPE, stdin=PIPE, stderr=STDOUT) as proc: stdout, _ = proc.communicate( input=urlsafe_b64decode(ciphertext_derived_key) @@ -113,30 +135,57 @@ def pk_decrypt_string( returncode = proc.returncode plaintext_derived_key = stdout else: - # We've got to use pkcs15-crypt for PIV cards, and it only supports pin on - # command line (insecure) or via stdin. So, we have to put ciphertext into - # a file for pkcs15-crypt to read. YUCK! - with NamedTemporaryFile(delete=False) as fname: - fname.write(urlsafe_b64decode(ciphertext_derived_key)) - command = [ - "pkcs15-crypt", - "--decipher", - "--raw", - "--pkcs", - "--input", - fname.name, - ] - if card_slot is not None: - command.extend(["--reader", str(card_slot)]) - command.extend(["--pin", "-"]) - with Popen(command, stdout=PIPE, stdin=PIPE, stderr=DEVNULL) as proc: - stdout, _ = proc.communicate(input=passphrase.encode("UTF-8")) - unlink(fname.name) - try: - plaintext_derived_key = stdout - except IndexError as err: - raise DecryptionError(stdout) from err - returncode = proc.returncode + if SCBackend == "opensc": + # We've got to use pkcs15-crypt for PIV cards, and it only supports pin on + # command line (insecure) or via stdin. So, we have to put ciphertext into + # a file for pkcs15-crypt to read. YUCK! + with NamedTemporaryFile(delete=False) as fname: + fname.write(urlsafe_b64decode(ciphertext_derived_key)) + command = [ + "pkcs15-crypt", + "--decipher", + "--raw", + "--pkcs", + "--input", + fname.name, + ] + if card_slot is not None: + command.extend(["--reader", str(card_slot)]) + command.extend(["--pin", "-"]) + with Popen(command, stdout=PIPE, stdin=PIPE, stderr=DEVNULL) as proc: + stdout, _ = proc.communicate(input=passphrase.encode("UTF-8")) + unlink(fname.name) + try: + plaintext_derived_key = stdout + except IndexError as err: + raise DecryptionError(stdout) from err + returncode = proc.returncode + elif SCBackend == "yubi": + # todo: this can be improved to not use temp files + # https://developers.yubico.com/yubico-piv-tool/YKCS11/Supported_applications/openssl_engine.html + with NamedTemporaryFile(delete=False) as fname: + fname.write(urlsafe_b64decode(ciphertext_derived_key)) + command = [ + "openssl", + "pkeyutl", + "-decrypt", + "-engine", "pkcs11", + "-keyform", "engine", + "-inkey", "pkcs11:type=private;pin-value=" + passphrase + ";serial=" + get_card_serial(card_slot), + "-pkeyopt", "rsa_padding_mode:pkcs1", + "-in", fname.name + ] + with Popen(command, stdout=PIPE, stdin=PIPE, stderr=STDOUT, env=dict(environ, PKCS11_MODULE_PATH=PKCS11_module_path)) as proc: + stdout, _ = proc.communicate( + input=urlsafe_b64decode(ciphertext_derived_key) + ) + try: + plaintext_derived_key = str(stdout).split("\\n")[1] + except IndexError as err: + raise DecryptionError(stdout) from err + returncode = proc.returncode + else: + raise BadBackendError(SCBackend) if returncode != 0: raise DecryptionError(stdout) @@ -148,7 +197,7 @@ def pk_decrypt_string( ) -def pk_sign_string(string, identity, passphrase, card_slot=None): +def pk_sign_string(string, identity, passphrase, SCBackend="opensc", card_slot=None, PKCS11_module_path="/usr/local/lib/libykcs11.dylib"): #################################################################### """Compute the hash of string and create a digital signature""" #################################################################### @@ -160,31 +209,58 @@ def pk_sign_string(string, identity, passphrase, card_slot=None): signature = urlsafe_b64encode(handle_python_strings(stdout)) returncode = proc.returncode else: - # We've got to use pkcs15-crypt for PIV cards, and it only supports pin on - # command line (insecure) or via stdin. So, we have to put signature text - # into a file for pkcs15-crypt to read. YUCK! - with NamedTemporaryFile(delete=False) as fname: - fname.write(stringhash.encode("UTF-8")) - with NamedTemporaryFile(delete=False) as out: - command = [ - "pkcs15-crypt", - "--sign", - "-i", - fname.name, - "-o", - out.name, - "--pkcs1", - "--raw", - ] - if card_slot is not None: - command.extend(["--reader", str(card_slot)]) - command.extend(["--pin", "-"]) - with Popen(command, stdout=PIPE, stdin=PIPE, stderr=STDOUT) as proc: - stdout, _ = proc.communicate(input=passphrase.encode("UTF-8")) - returncode = proc.returncode - - with open(out.name, "rb") as sigfile: - signature = urlsafe_b64encode(handle_python_strings(sigfile.read())) + if SCBackend == "opensc": + # We've got to use pkcs15-crypt for PIV cards, and it only supports pin on + # command line (insecure) or via stdin. So, we have to put signature text + # into a file for pkcs15-crypt to read. YUCK! + with NamedTemporaryFile(delete=False) as fname: + fname.write(stringhash.encode("UTF-8")) + with NamedTemporaryFile(delete=False) as out: + command = [ + "pkcs15-crypt", + "--sign", + "-i", + fname.name, + "-o", + out.name, + "--pkcs1", + "--raw", + ] + if card_slot is not None: + command.extend(["--reader", str(card_slot)]) + command.extend(["--pin", "-"]) + with Popen(command, stdout=PIPE, stdin=PIPE, stderr=STDOUT) as proc: + stdout, _ = proc.communicate(input=passphrase.encode("UTF-8")) + returncode = proc.returncode + + with open(out.name, "rb") as sigfile: + signature = urlsafe_b64encode(handle_python_strings(sigfile.read())) + elif SCBackend == "yubi": + # todo: this can be improved to not use temp files + # https://developers.yubico.com/yubico-piv-tool/YKCS11/Supported_applications/openssl_engine.html + with NamedTemporaryFile(delete=False) as fname: + fname.write(stringhash.encode("UTF-8")) + with NamedTemporaryFile(delete=False) as out: + command = [ + "openssl", + "pkeyutl", + "-sign", + "-engine", "pkcs11", + "-keyform", "engine", + "-inkey", "pkcs11:type=private;pin-value=" + passphrase + ";serial=" + get_card_serial(card_slot), + "-pkeyopt", "rsa_padding_mode:pkcs1", + "-in", fname.name, + "-out", out.name + ] + with Popen(command, stdout=PIPE, stdin=PIPE, stderr=STDOUT, env=dict(environ, PKCS11_MODULE_PATH=PKCS11_module_path)) as proc: + stdout, _ = proc.communicate( + input=stringhash.encode("UTF-8") + ) + returncode = proc.returncode + with open(out.name, "rb") as sigfile: + signature = urlsafe_b64encode(handle_python_strings(sigfile.read())) + else: + raise BadBackendError(SCBackend) unlink(fname.name) unlink(out.name) @@ -289,13 +365,20 @@ def get_cert_element(cert, element): raise X509CertificateError(stdout) from err -def get_card_element(element, card_slot=None): +def get_card_element(element, SCBackend, card_slot=None): #################################################################### """Return an arbitrary element of a pcks15 capable device""" #################################################################### - command = ["pkcs15-tool", "--read-certificate", "1"] - if card_slot is not None: - command.extend(["--reader", str(card_slot)]) + if SCBackend == "opensc": + command = ["pkcs15-tool", "--read-certificate", "1"] + if card_slot is not None: + command.extend(["--reader", str(card_slot)]) + elif SCBackend == "yubi": + command = ["yubico-piv-tool", "-a", "read-certificate", "-s", "9a"] + if card_slot is not None and card_slot != 0: + command.extend(["-r", str(card_slot)]) + else: + raise BadBackendError(SCBackend) with Popen(command, stdout=PIPE, stdin=PIPE, stderr=STDOUT) as proc: stdout, _ = proc.communicate() if stdout.decode("utf-8").strip().lower() == "no smart card readers found.": @@ -303,56 +386,74 @@ def get_card_element(element, card_slot=None): return get_cert_element(stdout, element) -def get_card_fingerprint(card_slot=None): +def get_card_fingerprint(SCBackend, card_slot=None): #################################################################### """Return the modulus of the x509 certificate of the identity""" #################################################################### # SHA1 Fingerprint=F9:9D:71:54:55:BE:99:24:6A:5E:E0:BB:48:F9:63:AE:A2:05:54:98 - return get_card_element("fingerprint", card_slot=card_slot).split("=")[1] + return get_card_element("fingerprint", SCBackend, card_slot=card_slot).split("=")[1] -def get_card_subject(card_slot=None): +def get_card_subject(SCBackend, card_slot=None): #################################################################### """Return the subject DN of the x509 certificate of the identity""" #################################################################### # subject= /C=US/O=Entrust/OU=Certification Authorities/OU=Entrust Managed Services SSP CA - return " ".join(get_card_element("subject", card_slot=card_slot).split(" ")[1:]) + return " ".join(get_card_element("subject", SCBackend, card_slot=card_slot).split(" ")[1:]) -def get_card_issuer(card_slot=None): +def get_card_issuer(SCBackend, card_slot=None): #################################################################### """Return the issuer DN of the x509 certificate of the identity""" #################################################################### # issuer= /C=US/O=Entrust/OU=Certification Authorities/OU=Entrust Managed Services SSP CA - return " ".join(get_card_element("issuer", card_slot=card_slot).split(" ")[1:]) + return " ".join(get_card_element("issuer", SCBackend, card_slot=card_slot).split(" ")[1:]) -def get_card_startdate(card_slot=None): +def get_card_startdate(SCBackend, card_slot=None): #################################################################### """Return the issuer DN of the x509 certificate of the identity""" #################################################################### - return get_card_element("startdate", card_slot=card_slot).split("=")[1] + return get_card_element("startdate", SCBackend, card_slot=card_slot).split("=")[1] -def get_card_enddate(card_slot=None): +def get_card_enddate(SCBackend, card_slot=None): #################################################################### """Return the issuer DN of the x509 certificate of the identity""" #################################################################### - return get_card_element("enddate", card_slot=card_slot).split("=")[1] + return get_card_element("enddate", SCBackend, card_slot=card_slot).split("=")[1] -def get_card_issuerhash(card_slot=None): +def get_card_issuerhash(SCBackend, card_slot=None): #################################################################### """Return the issuer DN of the x509 certificate of the identity""" #################################################################### - return get_card_element("issuer_hash", card_slot=card_slot) + return get_card_element("issuer_hash", SCBackend, card_slot=card_slot) -def get_card_subjecthash(card_slot=None): +def get_card_subjecthash(SCBackend, card_slot=None): #################################################################### """Return the issuer DN of the x509 certificate of the identity""" #################################################################### - return get_card_element("subject_hash", card_slot=card_slot) + return get_card_element("subject_hash", SCBackend, card_slot=card_slot) + + +def get_card_serial(card_slot=None): + #################################################################### + """Return the serial element of a yubico card""" + #################################################################### + command = ["yubico-piv-tool", "-a", "status"] + if card_slot is not None and card_slot != 0: + command.extend(["-r", str(card_slot)]) + with Popen(command, stdout=PIPE, stdin=PIPE, stderr=STDOUT) as proc: + stdout, _ = proc.communicate() + if stdout.decode("utf-8").strip().lower() == "no smart card readers found.": + raise X509CertificateError("Smartcard not detected") + for line in stdout.decode("utf-8").strip().lower().splitlines(): + if "serial number:" in line: + return line.split(":")[1].replace(" ", "").replace("\t", "") + raise X509CertificateError("Smartcard not detected") + return None def sk_encrypt_string(plaintext_string, key): diff --git a/libpkpass/errors.py b/libpkpass/errors.py index 48404fc..c0f71ec 100755 --- a/libpkpass/errors.py +++ b/libpkpass/errors.py @@ -31,6 +31,11 @@ class EncryptionError(PKPassError): pass +class BadBackendError(PKPassError): + def __init__(self, value): + self.msg = f"Invalid value for SCBackend: {value}" + + class EscrowError(PKPassError): def __init__(self, field, constant, value): self.msg = f"{field}, must be greater than {constant}, current value: {value}" diff --git a/libpkpass/password.py b/libpkpass/password.py index ccbe14f..2e31df8 100644 --- a/libpkpass/password.py +++ b/libpkpass/password.py @@ -83,6 +83,8 @@ def process_escrow_map( card_slot=None, escrow_users=None, minimum=None, + SCBackend="opensc", + PKCS11_module_path="/usr/local/lib/libykcs11.dylib", ): #################################################################### """Process the escrow user map into escrow users""" @@ -102,6 +104,8 @@ def process_escrow_map( encryption_algorithm, passphrase, card_slot, + SCBackend, + PKCS11_module_path, ) i += 1 @@ -122,6 +126,8 @@ def add_recipients( card_slot=None, escrow_users=None, minimum=None, + SCBackend="opensc", + PKCS11_module_path="/usr/local/lib/libykcs11.dylib", ): #################################################################### """Add recipients to the recipient list of this password object""" @@ -138,6 +144,8 @@ def add_recipients( encryption_algorithm=encryption_algorithm, passphrase=passphrase, card_slot=card_slot, + SCBackend=SCBackend, + PKCS11_module_path=PKCS11_module_path, ) for r in tqdm(recipients, leave=False) } @@ -166,17 +174,19 @@ def add_recipients( card_slot=card_slot, escrow_users=escrow_users, minimum=minimum, + SCBackend=SCBackend, + PKCS11_module_path=PKCS11_module_path, ) except ValueError as err: print(f"Warning cannot create escrow shares, reason: {err}") print("Your password has been created without escrow capabilities") - def _get_distributor(self, session, distributor): + def _get_distributor(self, session, distributor, SCBackend=None): distributor = ( session.query(Recipient).filter(Recipient.name == distributor).first() ) try: - distributor_hash = get_card_subjecthash() + distributor_hash = get_card_subjecthash(SCBackend) except X509CertificateError: distributor_hash = ( session.query(Cert) @@ -214,6 +224,8 @@ def _add_recipient( encryption_algorithm="rsautl", passphrase=None, card_slot=None, + SCBackend="opensc", + PKCS11_module_path="/usr/local/lib/libykcs11.dylib", ): #################################################################### """Add recipient or sharer to list""" @@ -222,7 +234,7 @@ def _add_recipient( encrypted_secrets = self._build_encrypted_secrets( session, recipient, encryption_algorithm, secret ) - distributor, distributor_hash = self._get_distributor(session, distributor) + distributor, distributor_hash = self._get_distributor(session, distributor, SCBackend) recipient_entry = { "encrypted_secrets": encrypted_secrets, "encryption_algorithm": encryption_algorithm, @@ -234,7 +246,9 @@ def _add_recipient( self._create_signable_string(recipient_entry), dict(distributor), passphrase, + SCBackend, card_slot, + PKCS11_module_path, ) return recipient_entry @@ -243,7 +257,7 @@ def _add_recipient( f"Identity '{recipient}' is not on the recipient list for password '{self.metadata['name']}'" ) from err - def decrypt_entry(self, identity=None, passphrase=None, card_slot=None): + def decrypt_entry(self, identity=None, passphrase=None, card_slot=None, SCBackend=None, PKCS11_module_path="/usr/local/lib/libykcs11.dylib"): #################################################################### """Decrypt this password entry for a particular identity (usually the user)""" @@ -262,7 +276,9 @@ def decrypt_entry(self, identity=None, passphrase=None, card_slot=None): recipient_entry["derived_key"], identity, passphrase, + SCBackend, card_slot, + PKCS11_module_path, ) except KeyError: try: @@ -274,9 +290,12 @@ def decrypt_entry(self, identity=None, passphrase=None, card_slot=None): value["derived_key"], identity, passphrase, + SCBackend, + card_slot, + PKCS11_module_path, ) else: - cert_key = get_card_fingerprint(card_slot=card_slot) + cert_key = get_card_fingerprint(SCBackend, card_slot=card_slot) return pk_decrypt_string( recipient_entry["encrypted_secrets"][cert_key][ "encrypted_secret" @@ -284,10 +303,12 @@ def decrypt_entry(self, identity=None, passphrase=None, card_slot=None): recipient_entry["encrypted_secrets"][cert_key]["derived_key"], identity, passphrase, + SCBackend, card_slot, + PKCS11_module_path, ) except DecryptionError as err: - msg = create_error_message(recipient_entry["timestamp"], card_slot) + msg = create_error_message(recipient_entry["timestamp"], card_slot, SCBackend, err) raise DecryptionError( f"Error decrypting password named '{self.metadata['name']}'. {msg}" ) from err @@ -296,7 +317,7 @@ def decrypt_entry(self, identity=None, passphrase=None, card_slot=None): f"Error decrypting password named '{self.metadata['name']}'. Appropriate private key not found" ) from err except DecryptionError as err: - msg = create_error_message(recipient_entry["timestamp"], card_slot) + msg = create_error_message(recipient_entry["timestamp"], card_slot, SCBackend, err) raise DecryptionError( f"Error decrypting password named '{self.metadata['name']}'. {msg}" ) from err @@ -392,16 +413,18 @@ def write_password_data( raise PasswordIOError(f"Error creating '{filename}'") from error -def create_error_message(recipient_timestamp, card_slot): - card_start = get_card_startdate() +def create_error_message(recipient_timestamp, card_slot, SCBackend="opensc", err=None): + card_start = get_card_startdate(SCBackend) card_start = datetime.timestamp(parser.parse(card_start)) distribute_time = float(recipient_timestamp) # Slots are indexed at 0 so when enumerating you add 1 - # There is also an additional information line so add 1 again - if int(card_slot) + 2 > len(get_card_info()[0]): + # For opensc there is also an additional information line so add 1 again + if (int(card_slot) + 2 > len(get_card_info(SCBackend)[0]) and SCBackend == "opensc") or (int(card_slot) + 1 > len(get_card_info(SCBackend)[0]) and SCBackend == "yubi"): msg = "Attempting to use card slot that is not connected" elif distribute_time < card_start: msg = "Password distributed before this certificate was created" + elif err is not None and "dlopen" in str(err.msg): + msg = "Perhaps a bad path in PKCS11_module_path" else: msg = "Perhaps a bad pin/passphrase?" return msg diff --git a/libpkpass/util.py b/libpkpass/util.py index 36f4cc8..79e9cfe 100644 --- a/libpkpass/util.py +++ b/libpkpass/util.py @@ -189,6 +189,8 @@ def collect_args(parsedargs): "theme_map": None, "color": True, "verbosity": 0, + "SCBackend": "opensc", + "PKCS11_module_path": "/usr/local/lib/libykcs11.dylib", } cli_args = parsedargs if isinstance(parsedargs, dict) else vars(parsedargs) config_args = get_config_args(cli_args["config"], cli_args)