diff --git a/libpkpass/commands/arguments.py b/libpkpass/commands/arguments.py index b5833d3..e08e587 100644 --- a/libpkpass/commands/arguments.py +++ b/libpkpass/commands/arguments.py @@ -272,4 +272,12 @@ "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/clip.py b/libpkpass/commands/clip.py index 7be970e..c5dd02c 100644 --- a/libpkpass/commands/clip.py +++ b/libpkpass/commands/clip.py @@ -41,6 +41,7 @@ def _run_command_execution(self): 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 2a345bc..c00be19 100644 --- a/libpkpass/commands/command.py +++ b/libpkpass/commands/command.py @@ -163,6 +163,7 @@ def update_pass(self, pass_value): 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"] @@ -199,6 +200,7 @@ def create_pass(self, password1, description, authorizer, recipient_list=None): 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 4199688..1b1a2ea 100644 --- a/libpkpass/commands/distribute.py +++ b/libpkpass/commands/distribute.py @@ -51,6 +51,7 @@ def _run_command_execution(self): 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, @@ -62,6 +63,7 @@ def _run_command_execution(self): 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 67a48d1..3e747c2 100644 --- a/libpkpass/commands/export.py +++ b/libpkpass/commands/export.py @@ -48,6 +48,7 @@ def _iterate_pdb(self, passworddb, crypt_pass=False): 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 7f6f91d..ab5ac1c 100644 --- a/libpkpass/commands/populate.py +++ b/libpkpass/commands/populate.py @@ -177,6 +177,7 @@ def _decrypt_password_entry(self, password): 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 42ebea9..ef433ad 100644 --- a/libpkpass/commands/rename.py +++ b/libpkpass/commands/rename.py @@ -44,6 +44,7 @@ def _run_command_execution(self): 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 bf7aa83..5fc5c98 100644 --- a/libpkpass/commands/show.py +++ b/libpkpass/commands/show.py @@ -76,6 +76,7 @@ def _behalf_prep(self, password): 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( @@ -156,6 +157,7 @@ def _decrypt_password_entry(self, password, distributor): 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 662a87a..1f5bf39 100755 --- a/libpkpass/crypto.py +++ b/libpkpass/crypto.py @@ -17,6 +17,7 @@ SignatureCreationError, X509CertificateError, BadBackendError, + PKPassError, ) @@ -96,9 +97,8 @@ def print_card_info(card_slot, identity, verbosity, color, theme_map, SCBackend) for out in out_list: stripped = out.decode("UTF-8").strip() if "Yubico" not in stripped: - print("unsupported SC type") - # todo: better handling - exit(1) + # 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]) @@ -120,7 +120,7 @@ def print_all_slots(slot_info, color, theme_map): def pk_decrypt_string( - ciphertext_string, ciphertext_derived_key, identity, passphrase, SCBackend="opensc", 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""" @@ -161,7 +161,7 @@ def pk_decrypt_string( raise DecryptionError(stdout) from err returncode = proc.returncode elif SCBackend == "yubi": - # todo: fix this + # 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)) @@ -175,8 +175,7 @@ def pk_decrypt_string( "-pkeyopt", "rsa_padding_mode:pkcs1", "-in", fname.name ] - # todo: make path an option - with Popen(command, stdout=PIPE, stdin=PIPE, stderr=STDOUT, env=dict(environ, PKCS11_MODULE_PATH="/usr/local/lib/libykcs11.dylib")) as proc: + 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) ) @@ -198,7 +197,7 @@ def pk_decrypt_string( ) -def pk_sign_string(string, identity, passphrase, SCBackend="opensc", 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""" #################################################################### @@ -237,7 +236,7 @@ def pk_sign_string(string, identity, passphrase, SCBackend="opensc", card_slot=N with open(out.name, "rb") as sigfile: signature = urlsafe_b64encode(handle_python_strings(sigfile.read())) elif SCBackend == "yubi": - # todo: fix this + # 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")) @@ -253,8 +252,7 @@ def pk_sign_string(string, identity, passphrase, SCBackend="opensc", card_slot=N "-in", fname.name, "-out", out.name ] - # todo: make this an option - with Popen(command, stdout=PIPE, stdin=PIPE, stderr=STDOUT, env=dict(environ, PKCS11_MODULE_PATH="/usr/local/lib/libykcs11.dylib")) as proc: + 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") ) @@ -442,7 +440,7 @@ def get_card_subjecthash(SCBackend, card_slot=None): def get_card_serial(card_slot=None): #################################################################### - """Return the serial element of a card""" + """Return the serial element of a yubico card""" #################################################################### command = ["yubico-piv-tool", "-a", "status"] if card_slot is not None and card_slot != 0: @@ -454,7 +452,6 @@ def get_card_serial(card_slot=None): for line in stdout.decode("utf-8").strip().lower().splitlines(): if "serial number:" in line: return line.split(":")[1].replace(" ", "").replace("\t", "") - # todo: fix this raise X509CertificateError("Smartcard not detected") return None diff --git a/libpkpass/password.py b/libpkpass/password.py index 3618ae9..2e31df8 100644 --- a/libpkpass/password.py +++ b/libpkpass/password.py @@ -84,6 +84,7 @@ def process_escrow_map( escrow_users=None, minimum=None, SCBackend="opensc", + PKCS11_module_path="/usr/local/lib/libykcs11.dylib", ): #################################################################### """Process the escrow user map into escrow users""" @@ -104,6 +105,7 @@ def process_escrow_map( passphrase, card_slot, SCBackend, + PKCS11_module_path, ) i += 1 @@ -125,6 +127,7 @@ def add_recipients( 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""" @@ -142,6 +145,7 @@ def add_recipients( passphrase=passphrase, card_slot=card_slot, SCBackend=SCBackend, + PKCS11_module_path=PKCS11_module_path, ) for r in tqdm(recipients, leave=False) } @@ -171,6 +175,7 @@ def add_recipients( 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}") @@ -220,6 +225,7 @@ def _add_recipient( passphrase=None, card_slot=None, SCBackend="opensc", + PKCS11_module_path="/usr/local/lib/libykcs11.dylib", ): #################################################################### """Add recipient or sharer to list""" @@ -242,6 +248,7 @@ def _add_recipient( passphrase, SCBackend, card_slot, + PKCS11_module_path, ) return recipient_entry @@ -250,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, SCBackend=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)""" @@ -271,6 +278,7 @@ def decrypt_entry(self, identity=None, passphrase=None, card_slot=None, SCBacken passphrase, SCBackend, card_slot, + PKCS11_module_path, ) except KeyError: try: @@ -283,6 +291,8 @@ def decrypt_entry(self, identity=None, passphrase=None, card_slot=None, SCBacken identity, passphrase, SCBackend, + card_slot, + PKCS11_module_path, ) else: cert_key = get_card_fingerprint(SCBackend, card_slot=card_slot) @@ -295,9 +305,10 @@ def decrypt_entry(self, identity=None, passphrase=None, card_slot=None, SCBacken passphrase, SCBackend, card_slot, + PKCS11_module_path, ) except DecryptionError as err: - msg = create_error_message(recipient_entry["timestamp"], card_slot, SCBackend) + msg = create_error_message(recipient_entry["timestamp"], card_slot, SCBackend, err) raise DecryptionError( f"Error decrypting password named '{self.metadata['name']}'. {msg}" ) from err @@ -306,7 +317,7 @@ def decrypt_entry(self, identity=None, passphrase=None, card_slot=None, SCBacken 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, SCBackend) + msg = create_error_message(recipient_entry["timestamp"], card_slot, SCBackend, err) raise DecryptionError( f"Error decrypting password named '{self.metadata['name']}'. {msg}" ) from err @@ -402,16 +413,18 @@ def write_password_data( raise PasswordIOError(f"Error creating '{filename}'") from error -def create_error_message(recipient_timestamp, card_slot, SCBackend="opensc"): +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(SCBackend)[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 2668e52..79e9cfe 100644 --- a/libpkpass/util.py +++ b/libpkpass/util.py @@ -190,6 +190,7 @@ def collect_args(parsedargs): "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)