From 4bf537a506f60da06627656d9bf9d05411a90787 Mon Sep 17 00:00:00 2001 From: CBAI <7985170+carljbai@users.noreply.github.com> Date: Mon, 7 Aug 2023 11:54:06 -0400 Subject: [PATCH 01/40] add arg to util --- libpkpass/util.py | 1 + 1 file changed, 1 insertion(+) diff --git a/libpkpass/util.py b/libpkpass/util.py index 36f4cc8..2668e52 100644 --- a/libpkpass/util.py +++ b/libpkpass/util.py @@ -189,6 +189,7 @@ def collect_args(parsedargs): "theme_map": None, "color": True, "verbosity": 0, + "SCBackend": "opensc", } cli_args = parsedargs if isinstance(parsedargs, dict) else vars(parsedargs) config_args = get_config_args(cli_args["config"], cli_args) From c17981887f59a3a8b750e01179e637b78b466d1f Mon Sep 17 00:00:00 2001 From: CBAI <7985170+carljbai@users.noreply.github.com> Date: Mon, 7 Aug 2023 11:59:51 -0400 Subject: [PATCH 02/40] add new error for invalid SC backends --- libpkpass/errors.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/libpkpass/errors.py b/libpkpass/errors.py index 48404fc..3ebc14f 100755 --- a/libpkpass/errors.py +++ b/libpkpass/errors.py @@ -31,6 +31,10 @@ 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}" From 877e057c722bd60a56763ee1e6fe29e79f43d9b7 Mon Sep 17 00:00:00 2001 From: CBAI <7985170+carljbai@users.noreply.github.com> Date: Mon, 7 Aug 2023 12:00:45 -0400 Subject: [PATCH 03/40] whitespace --- libpkpass/errors.py | 1 + 1 file changed, 1 insertion(+) diff --git a/libpkpass/errors.py b/libpkpass/errors.py index 3ebc14f..c0f71ec 100755 --- a/libpkpass/errors.py +++ b/libpkpass/errors.py @@ -35,6 +35,7 @@ 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}" From f2ee3c9f1af9937c104e5a5206d4a8cbbb882297 Mon Sep 17 00:00:00 2001 From: CBAI <7985170+carljbai@users.noreply.github.com> Date: Mon, 7 Aug 2023 12:02:20 -0400 Subject: [PATCH 04/40] add error to crypto --- libpkpass/crypto.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libpkpass/crypto.py b/libpkpass/crypto.py index 172ca48..15e05b9 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,7 @@ DecryptionError, SignatureCreationError, X509CertificateError, + BadBackendError, ) From 10c653453f5891deaf9dced71dc4f632b25cd88e Mon Sep 17 00:00:00 2001 From: CBAI <7985170+carljbai@users.noreply.github.com> Date: Mon, 7 Aug 2023 12:10:01 -0400 Subject: [PATCH 05/40] draft crypo get_card_info --- libpkpass/crypto.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/libpkpass/crypto.py b/libpkpass/crypto.py index 15e05b9..b5ca6ae 100755 --- a/libpkpass/crypto.py +++ b/libpkpass/crypto.py @@ -63,11 +63,18 @@ 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): From 4d098976050891325ac14ccaf1856036cd23bff9 Mon Sep 17 00:00:00 2001 From: CBAI <7985170+carljbai@users.noreply.github.com> Date: Mon, 7 Aug 2023 14:05:39 -0400 Subject: [PATCH 06/40] draft crypto print_card_info --- libpkpass/crypto.py | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/libpkpass/crypto.py b/libpkpass/crypto.py index b5ca6ae..9780f84 100755 --- a/libpkpass/crypto.py +++ b/libpkpass/crypto.py @@ -77,20 +77,33 @@ def get_card_info(SCBackend="opensc"): 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: + print("unsupported SC Type") + exit(1) + if int(stripped.split('CCID') 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): From a94d3a94d74c1cb5d68390c7734097a8918f93c4 Mon Sep 17 00:00:00 2001 From: CBAI <7985170+carljbai@users.noreply.github.com> Date: Mon, 7 Aug 2023 14:06:52 -0400 Subject: [PATCH 07/40] typo --- libpkpass/crypto.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libpkpass/crypto.py b/libpkpass/crypto.py index 9780f84..a7d1d61 100755 --- a/libpkpass/crypto.py +++ b/libpkpass/crypto.py @@ -97,8 +97,9 @@ def print_card_info(card_slot, identity, verbosity, color, theme_map, SCBackend) stripped = out.decode("UTF-8").strip() if "Yubico" not in stripped: print("unsupported SC Type") + # todo: better handling exit(1) - if int(stripped.split('CCID') or 0) == int(card_slot): + 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)}" From f42064a002e6e0cb357ad3ed3d485e8ae56eb575 Mon Sep 17 00:00:00 2001 From: CBAI <7985170+carljbai@users.noreply.github.com> Date: Mon, 7 Aug 2023 14:07:30 -0400 Subject: [PATCH 08/40] typo --- libpkpass/crypto.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libpkpass/crypto.py b/libpkpass/crypto.py index a7d1d61..dc6745f 100755 --- a/libpkpass/crypto.py +++ b/libpkpass/crypto.py @@ -103,7 +103,7 @@ def print_card_info(card_slot, identity, verbosity, color, theme_map, SCBackend) 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 + else: raise BadBackendError(SCBackend) From a8193c7ec7646a60c9b43bc7f78bdbd96b9ac168 Mon Sep 17 00:00:00 2001 From: CBAI <7985170+carljbai@users.noreply.github.com> Date: Mon, 7 Aug 2023 14:17:05 -0400 Subject: [PATCH 09/40] draft crypto get_card_serial --- libpkpass/crypto.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/libpkpass/crypto.py b/libpkpass/crypto.py index dc6745f..0d59c62 100755 --- a/libpkpass/crypto.py +++ b/libpkpass/crypto.py @@ -386,6 +386,25 @@ def sk_encrypt_string(plaintext_string, key): return urlsafe_b64encode(handle_python_strings(encrypted_string)) +def get_card_serial(card_slot=None): + #################################################################### + """Return the serial element of a 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", "") + # todo: fix this + raise X509CertificateError("Smartcard not detected") + return None + + def sk_decrypt_string(ciphertext_string, key): #################################################################### """Symmetrically Decrypt a base64 encoded string using the provided key""" From 647645ae3c05d81a878711331214b03b747f1683 Mon Sep 17 00:00:00 2001 From: CBAI <7985170+carljbai@users.noreply.github.com> Date: Mon, 7 Aug 2023 14:20:29 -0400 Subject: [PATCH 10/40] move --- libpkpass/crypto.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/libpkpass/crypto.py b/libpkpass/crypto.py index 0d59c62..3667642 100755 --- a/libpkpass/crypto.py +++ b/libpkpass/crypto.py @@ -377,15 +377,6 @@ def get_card_subjecthash(card_slot=None): return get_card_element("subject_hash", card_slot=card_slot) -def sk_encrypt_string(plaintext_string, key): - #################################################################### - """Symmetrically Encrypt and return a base 64 encoded string using the provided secret""" - #################################################################### - fern = Fernet(hash_password(key)) - encrypted_string = fern.encrypt(plaintext_string.encode("UTF-8")) - return urlsafe_b64encode(handle_python_strings(encrypted_string)) - - def get_card_serial(card_slot=None): #################################################################### """Return the serial element of a card""" @@ -405,6 +396,15 @@ def get_card_serial(card_slot=None): return None +def sk_encrypt_string(plaintext_string, key): + #################################################################### + """Symmetrically Encrypt and return a base 64 encoded string using the provided secret""" + #################################################################### + fern = Fernet(hash_password(key)) + encrypted_string = fern.encrypt(plaintext_string.encode("UTF-8")) + return urlsafe_b64encode(handle_python_strings(encrypted_string)) + + def sk_decrypt_string(ciphertext_string, key): #################################################################### """Symmetrically Decrypt a base64 encoded string using the provided key""" From 1e381f6f59689e72eca946b82ee68ac3d15ed4f0 Mon Sep 17 00:00:00 2001 From: CBAI <7985170+carljbai@users.noreply.github.com> Date: Mon, 7 Aug 2023 15:41:49 -0400 Subject: [PATCH 11/40] draft crypto pk_decrypt_string --- libpkpass/crypto.py | 78 ++++++++++++++++++++++++++++++--------------- 1 file changed, 53 insertions(+), 25 deletions(-) diff --git a/libpkpass/crypto.py b/libpkpass/crypto.py index 3667642..c7bff2e 100755 --- a/libpkpass/crypto.py +++ b/libpkpass/crypto.py @@ -120,7 +120,7 @@ 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, card_slot=None ): #################################################################### """Decrypt a base64 encoded string for the provided identity""" @@ -135,30 +135,58 @@ 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: fix this + # 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 + ] + # todo: make path an option + with Popen(command, stdout=PIPE, stdin=PIPE, stderr=DEVNULL, env=dict(environ, PKCS11_MODULE_PATH="/usr/local/lib/libykcs11.dylib")) 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) From 385a13a1a6d6d2a7871eaa3a1898f9c16e606694 Mon Sep 17 00:00:00 2001 From: CBAI <7985170+carljbai@users.noreply.github.com> Date: Mon, 7 Aug 2023 15:47:32 -0400 Subject: [PATCH 12/40] typo --- libpkpass/crypto.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libpkpass/crypto.py b/libpkpass/crypto.py index c7bff2e..59120e1 100755 --- a/libpkpass/crypto.py +++ b/libpkpass/crypto.py @@ -127,7 +127,7 @@ def pk_decrypt_string( #################################################################### 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) From ededd5aeafeeb5099ea5e0d0aba4d0ef849ca354 Mon Sep 17 00:00:00 2001 From: CBAI <7985170+carljbai@users.noreply.github.com> Date: Mon, 7 Aug 2023 16:06:13 -0400 Subject: [PATCH 13/40] draft crypto pk_sign_string --- libpkpass/crypto.py | 77 ++++++++++++++++++++++++++++++--------------- 1 file changed, 52 insertions(+), 25 deletions(-) diff --git a/libpkpass/crypto.py b/libpkpass/crypto.py index 59120e1..366d43b 100755 --- a/libpkpass/crypto.py +++ b/libpkpass/crypto.py @@ -198,7 +198,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): #################################################################### """Compute the hash of string and create a digital signature""" #################################################################### @@ -210,31 +210,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 + 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())) + with open(out.name, "rb") as sigfile: + signature = urlsafe_b64encode(handle_python_strings(sigfile.read())) + elif SCBackend == "yubi": + # todo: fix this + # 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", + "-inkey", "pkcs11:type=private;pin-value=" + passphrase + ";serial=" + get_card_serial(card_slot), + "-pkeyopt", "rsa_padding_mode:pkcs1", + "-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.dynlib")) 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) From 9f4337e1a7e63e53edc1a1c9947217d46cad7a26 Mon Sep 17 00:00:00 2001 From: CBAI <7985170+carljbai@users.noreply.github.com> Date: Mon, 7 Aug 2023 16:08:35 -0400 Subject: [PATCH 14/40] add engine --- libpkpass/crypto.py | 1 + 1 file changed, 1 insertion(+) diff --git a/libpkpass/crypto.py b/libpkpass/crypto.py index 366d43b..fb98d78 100755 --- a/libpkpass/crypto.py +++ b/libpkpass/crypto.py @@ -247,6 +247,7 @@ def pk_sign_string(string, identity, passphrase, SCBackend="opensc", card_slot=N "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, From 2cf3241f4637726baaeba5823e56604a89d81717 Mon Sep 17 00:00:00 2001 From: CBAI <7985170+carljbai@users.noreply.github.com> Date: Mon, 7 Aug 2023 16:09:52 -0400 Subject: [PATCH 15/40] dylib --- libpkpass/crypto.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libpkpass/crypto.py b/libpkpass/crypto.py index fb98d78..9ac729e 100755 --- a/libpkpass/crypto.py +++ b/libpkpass/crypto.py @@ -254,7 +254,7 @@ def pk_sign_string(string, identity, passphrase, SCBackend="opensc", card_slot=N "-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.dynlib")) as proc: + with Popen(command, stdout=PIPE, stdin=PIPE, stderr=STDOUT, env=dict(environ, PKCS11_MODULE_PATH="/usr/local/lib/libykcs11.dylib")) as proc: stdout, _ = proc.communicate( input=stringhash.encode("UTF-8") ) From a1c5b215e0b76dd54ea348b68cd4210a0109102b Mon Sep 17 00:00:00 2001 From: CBAI <7985170+carljbai@users.noreply.github.com> Date: Mon, 7 Aug 2023 16:10:59 -0400 Subject: [PATCH 16/40] whitespace --- libpkpass/crypto.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libpkpass/crypto.py b/libpkpass/crypto.py index 9ac729e..6be538e 100755 --- a/libpkpass/crypto.py +++ b/libpkpass/crypto.py @@ -257,12 +257,12 @@ def pk_sign_string(string, identity, passphrase, SCBackend="opensc", card_slot=N with Popen(command, stdout=PIPE, stdin=PIPE, stderr=STDOUT, env=dict(environ, PKCS11_MODULE_PATH="/usr/local/lib/libykcs11.dylib")) as proc: stdout, _ = proc.communicate( input=stringhash.encode("UTF-8") - ) - returncode = proc.returncode + ) + returncode = proc.returncode with open(out.name, "rb") as sigfile: signature = urlsafe_b64encode(handle_python_strings(sigfile.read())) else: - raise BadBackendError(SCBackend) + raise BadBackendError(SCBackend) unlink(fname.name) unlink(out.name) From 22f00268a35a63bf126f39ba307f70dd7b8eb1db Mon Sep 17 00:00:00 2001 From: CBAI <7985170+carljbai@users.noreply.github.com> Date: Mon, 7 Aug 2023 16:17:41 -0400 Subject: [PATCH 17/40] draft crypto get_card_element --- libpkpass/crypto.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/libpkpass/crypto.py b/libpkpass/crypto.py index 6be538e..2c04934 100755 --- a/libpkpass/crypto.py +++ b/libpkpass/crypto.py @@ -367,13 +367,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.": From f364c9b2a89ef95ad132be6e9d0a6f479d2cd4ad Mon Sep 17 00:00:00 2001 From: CBAI <7985170+carljbai@users.noreply.github.com> Date: Mon, 7 Aug 2023 16:19:32 -0400 Subject: [PATCH 18/40] draft crypto get_card_fingerprint --- libpkpass/crypto.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libpkpass/crypto.py b/libpkpass/crypto.py index 2c04934..190194b 100755 --- a/libpkpass/crypto.py +++ b/libpkpass/crypto.py @@ -388,12 +388,12 @@ def get_card_element(element, SCBackend, 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): From 837a0358f30dda667dc82682cdefedce12eedde0 Mon Sep 17 00:00:00 2001 From: CBAI <7985170+carljbai@users.noreply.github.com> Date: Mon, 7 Aug 2023 16:20:59 -0400 Subject: [PATCH 19/40] draft crypto SCBackend --- libpkpass/crypto.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/libpkpass/crypto.py b/libpkpass/crypto.py index 190194b..82ee972 100755 --- a/libpkpass/crypto.py +++ b/libpkpass/crypto.py @@ -396,48 +396,48 @@ def get_card_fingerprint(SCBackend, card_slot=None): 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): From a19b2d001ca38bd4f47565056b8471184f2ee388 Mon Sep 17 00:00:00 2001 From: CBAI <7985170+carljbai@users.noreply.github.com> Date: Mon, 7 Aug 2023 16:27:03 -0400 Subject: [PATCH 20/40] draft pass passwords --- libpkpass/password.py | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/libpkpass/password.py b/libpkpass/password.py index ccbe14f..2365413 100644 --- a/libpkpass/password.py +++ b/libpkpass/password.py @@ -83,6 +83,7 @@ def process_escrow_map( card_slot=None, escrow_users=None, minimum=None, + SCBackend=None, ): #################################################################### """Process the escrow user map into escrow users""" @@ -102,6 +103,7 @@ def process_escrow_map( encryption_algorithm, passphrase, card_slot, + SCBackend, ) i += 1 @@ -122,6 +124,7 @@ def add_recipients( card_slot=None, escrow_users=None, minimum=None, + SCBackend=None, ): #################################################################### """Add recipients to the recipient list of this password object""" @@ -138,6 +141,7 @@ def add_recipients( encryption_algorithm=encryption_algorithm, passphrase=passphrase, card_slot=card_slot, + SCBackend=SCBackend, ) for r in tqdm(recipients, leave=False) } @@ -166,17 +170,18 @@ def add_recipients( card_slot=card_slot, escrow_users=escrow_users, minimum=minimum, + SCBackend=SCBackend ) 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 +219,7 @@ def _add_recipient( encryption_algorithm="rsautl", passphrase=None, card_slot=None, + SCBackend=None ): #################################################################### """Add recipient or sharer to list""" @@ -222,7 +228,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,6 +240,7 @@ def _add_recipient( self._create_signable_string(recipient_entry), dict(distributor), passphrase, + SCBackend card_slot, ) @@ -243,7 +250,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): #################################################################### """Decrypt this password entry for a particular identity (usually the user)""" @@ -262,6 +269,7 @@ def decrypt_entry(self, identity=None, passphrase=None, card_slot=None): recipient_entry["derived_key"], identity, passphrase, + SCBackend, card_slot, ) except KeyError: @@ -274,9 +282,10 @@ def decrypt_entry(self, identity=None, passphrase=None, card_slot=None): value["derived_key"], identity, passphrase, + SCBackend, ) 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 +293,11 @@ def decrypt_entry(self, identity=None, passphrase=None, card_slot=None): recipient_entry["encrypted_secrets"][cert_key]["derived_key"], identity, passphrase, + SCBackend, card_slot, ) except DecryptionError as err: - msg = create_error_message(recipient_entry["timestamp"], card_slot) + msg = create_error_message(recipient_entry["timestamp"], card_slot, SCBackend) raise DecryptionError( f"Error decrypting password named '{self.metadata['name']}'. {msg}" ) from err @@ -296,7 +306,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) raise DecryptionError( f"Error decrypting password named '{self.metadata['name']}'. {msg}" ) from err @@ -392,13 +402,13 @@ 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"): + 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]): + if int(card_slot) + 2 > len(get_card_info(SCBackend)[0]): msg = "Attempting to use card slot that is not connected" elif distribute_time < card_start: msg = "Password distributed before this certificate was created" From 78a254847dfb15e2c1455844d3119e2a2c3a9d18 Mon Sep 17 00:00:00 2001 From: CBAI <7985170+carljbai@users.noreply.github.com> Date: Mon, 7 Aug 2023 16:27:59 -0400 Subject: [PATCH 21/40] typo --- libpkpass/password.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libpkpass/password.py b/libpkpass/password.py index 2365413..2776900 100644 --- a/libpkpass/password.py +++ b/libpkpass/password.py @@ -170,7 +170,7 @@ def add_recipients( card_slot=card_slot, escrow_users=escrow_users, minimum=minimum, - SCBackend=SCBackend + SCBackend=SCBackend, ) except ValueError as err: print(f"Warning cannot create escrow shares, reason: {err}") @@ -219,7 +219,7 @@ def _add_recipient( encryption_algorithm="rsautl", passphrase=None, card_slot=None, - SCBackend=None + SCBackend=None, ): #################################################################### """Add recipient or sharer to list""" @@ -240,7 +240,7 @@ def _add_recipient( self._create_signable_string(recipient_entry), dict(distributor), passphrase, - SCBackend + SCBackend, card_slot, ) From a75640010d698fcc6fabfa6aab5325ff622a7c02 Mon Sep 17 00:00:00 2001 From: CBAI <7985170+carljbai@users.noreply.github.com> Date: Mon, 7 Aug 2023 16:30:07 -0400 Subject: [PATCH 22/40] draft show --- libpkpass/commands/show.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libpkpass/commands/show.py b/libpkpass/commands/show.py index 6df616b..bf7aa83 100644 --- a/libpkpass/commands/show.py +++ b/libpkpass/commands/show.py @@ -75,6 +75,7 @@ def _behalf_prep(self, password): identity=self.iddb.id, passphrase=self.passphrase, card_slot=self.args["card_slot"], + SCBackend=self.args["SCBackend"], ) with open(temp_key, "w", encoding="ASCII") as fname: fname.write( @@ -154,6 +155,7 @@ def _decrypt_password_entry(self, password, distributor): identity=self.iddb.id, passphrase=self.passphrase, card_slot=self.args["card_slot"], + SCBackend=self.args["SCBackend"], ) dist_obj = ( self.iddb.session.query(Recipient) From 3a78522deb4fed8d6c0b91bbab19117c1b908a05 Mon Sep 17 00:00:00 2001 From: CBAI <7985170+carljbai@users.noreply.github.com> Date: Mon, 7 Aug 2023 16:34:43 -0400 Subject: [PATCH 23/40] draft rest of commands --- libpkpass/commands/card.py | 1 + libpkpass/commands/clip.py | 1 + libpkpass/commands/command.py | 3 +++ libpkpass/commands/distribute.py | 2 ++ libpkpass/commands/export.py | 1 + libpkpass/commands/populate.py | 1 + libpkpass/commands/rename.py | 1 + 7 files changed, 10 insertions(+) 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..7be970e 100644 --- a/libpkpass/commands/clip.py +++ b/libpkpass/commands/clip.py @@ -40,6 +40,7 @@ def _run_command_execution(self): identity=self.iddb.id, passphrase=self.passphrase, card_slot=self.args["card_slot"], + SCBackend=self.args["SCBackend"], ) if not self.args["noverify"]: result = password.verify_entry( diff --git a/libpkpass/commands/command.py b/libpkpass/commands/command.py index d6c2a5b..2a345bc 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,7 @@ 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"], ) pass_entry["recipients"][self.args["identity"]] = swap_pass["recipients"][ self.args["identity"] @@ -196,6 +198,7 @@ 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"], ) password.write_password_data( diff --git a/libpkpass/commands/distribute.py b/libpkpass/commands/distribute.py index 79a82fc..4199688 100644 --- a/libpkpass/commands/distribute.py +++ b/libpkpass/commands/distribute.py @@ -50,6 +50,7 @@ def _run_command_execution(self): self.iddb.id, passphrase=self.passphrase, card_slot=self.args["card_slot"], + SCBackend=self.args["SCBackend"], ) password.add_recipients( secret=plaintext_pw, @@ -60,6 +61,7 @@ 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"], ) password.write_password_data(dist_pass) diff --git a/libpkpass/commands/export.py b/libpkpass/commands/export.py index 52c5438..67a48d1 100644 --- a/libpkpass/commands/export.py +++ b/libpkpass/commands/export.py @@ -47,6 +47,7 @@ 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"], ) 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..7f6f91d 100644 --- a/libpkpass/commands/populate.py +++ b/libpkpass/commands/populate.py @@ -176,6 +176,7 @@ def _decrypt_password_entry(self, password): identity=self.iddb.id, passphrase=self.passphrase, card_slot=self.args["card_slot"], + SCBackend=self.args["SCBackend"], ) 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..42ebea9 100644 --- a/libpkpass/commands/rename.py +++ b/libpkpass/commands/rename.py @@ -43,6 +43,7 @@ def _run_command_execution(self): identity=self.iddb.id, passphrase=self.passphrase, card_slot=self.args["card_slot"], + SCBackend=self.args["SCBackend"], ) self._confirmation(plaintext_pw) else: From 88162e4b6eb4385c6fe7666f910945b029489f36 Mon Sep 17 00:00:00 2001 From: CBAI <7985170+carljbai@users.noreply.github.com> Date: Mon, 7 Aug 2023 16:40:20 -0400 Subject: [PATCH 24/40] draft args --- libpkpass/commands/arguments.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/libpkpass/commands/arguments.py b/libpkpass/commands/arguments.py index e596cc0..dfbd871 100644 --- a/libpkpass/commands/arguments.py +++ b/libpkpass/commands/arguments.py @@ -264,4 +264,12 @@ "help": "verbose output (repeat for increased verbosity)", }, }, + "SCBackend": { + "args": ["--scbackend"], + "kwargs": { + "type": str, + "default": "opensc", + "help": "SC backend to use: opensc or yubi", + } + } } From e58371f03a5f3b01016f597eb150921a9b89d710 Mon Sep 17 00:00:00 2001 From: CBAI <7985170+carljbai@users.noreply.github.com> Date: Mon, 7 Aug 2023 16:41:13 -0400 Subject: [PATCH 25/40] commas --- libpkpass/commands/arguments.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libpkpass/commands/arguments.py b/libpkpass/commands/arguments.py index dfbd871..b5833d3 100644 --- a/libpkpass/commands/arguments.py +++ b/libpkpass/commands/arguments.py @@ -270,6 +270,6 @@ "type": str, "default": "opensc", "help": "SC backend to use: opensc or yubi", - } - } + }, + }, } From 3b4ad80fcc23536c63e965608332b1195b9405c2 Mon Sep 17 00:00:00 2001 From: CBAI <7985170+carljbai@users.noreply.github.com> Date: Mon, 7 Aug 2023 16:49:55 -0400 Subject: [PATCH 26/40] set pw default backend --- libpkpass/password.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libpkpass/password.py b/libpkpass/password.py index 2776900..3618ae9 100644 --- a/libpkpass/password.py +++ b/libpkpass/password.py @@ -83,7 +83,7 @@ def process_escrow_map( card_slot=None, escrow_users=None, minimum=None, - SCBackend=None, + SCBackend="opensc", ): #################################################################### """Process the escrow user map into escrow users""" @@ -124,7 +124,7 @@ def add_recipients( card_slot=None, escrow_users=None, minimum=None, - SCBackend=None, + SCBackend="opensc", ): #################################################################### """Add recipients to the recipient list of this password object""" @@ -219,7 +219,7 @@ def _add_recipient( encryption_algorithm="rsautl", passphrase=None, card_slot=None, - SCBackend=None, + SCBackend="opensc", ): #################################################################### """Add recipient or sharer to list""" From 0c96e1d581e87036e76baeeef9105a0ff637b0f3 Mon Sep 17 00:00:00 2001 From: CBAI <7985170+carljbai@users.noreply.github.com> Date: Mon, 7 Aug 2023 17:04:11 -0400 Subject: [PATCH 27/40] typo --- libpkpass/crypto.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libpkpass/crypto.py b/libpkpass/crypto.py index 82ee972..1e4d94e 100755 --- a/libpkpass/crypto.py +++ b/libpkpass/crypto.py @@ -96,7 +96,7 @@ 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") + print("unsupported SC type") # todo: better handling exit(1) if int(stripped.split('CCID')[1] or 0) == int(card_slot): From c44e34dd53e3faba830db90fb4f84f5087bc2cfd Mon Sep 17 00:00:00 2001 From: CBAI <7985170+carljbai@users.noreply.github.com> Date: Mon, 7 Aug 2023 17:06:39 -0400 Subject: [PATCH 28/40] ? --- libpkpass/crypto.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libpkpass/crypto.py b/libpkpass/crypto.py index 1e4d94e..cc6552f 100755 --- a/libpkpass/crypto.py +++ b/libpkpass/crypto.py @@ -176,7 +176,7 @@ def pk_decrypt_string( "-in", fname.name ] # todo: make path an option - with Popen(command, stdout=PIPE, stdin=PIPE, stderr=DEVNULL, 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="/usr/local/lib/libykcs11.dylib")) as proc: stdout, _ = proc.communicate( input=urlsafe_b64decode(ciphertext_derived_key) ) From 3162a20d1b402475a2b93b5e1949f3bcbf2e6042 Mon Sep 17 00:00:00 2001 From: CBAI <7985170+carljbai@users.noreply.github.com> Date: Mon, 7 Aug 2023 17:08:35 -0400 Subject: [PATCH 29/40] default decryption backend --- libpkpass/crypto.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libpkpass/crypto.py b/libpkpass/crypto.py index cc6552f..662a87a 100755 --- a/libpkpass/crypto.py +++ b/libpkpass/crypto.py @@ -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, card_slot=None + ciphertext_string, ciphertext_derived_key, identity, passphrase, SCBackend="opensc", card_slot=None ): #################################################################### """Decrypt a base64 encoded string for the provided identity""" From 01137a75c13bd55388175a481f7b66b4b4329663 Mon Sep 17 00:00:00 2001 From: CBAI <7985170+carljbai@users.noreply.github.com> Date: Mon, 14 Aug 2023 10:57:50 -0400 Subject: [PATCH 30/40] first cleanup pass --- libpkpass/crypto.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/libpkpass/crypto.py b/libpkpass/crypto.py index 662a87a..d88d83d 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]) @@ -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)) @@ -237,7 +237,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")) @@ -442,7 +442,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 +454,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 From d9c2bee718d8fc0c6e5c3a053fcce7c2ccf2d383 Mon Sep 17 00:00:00 2001 From: CBAI <7985170+carljbai@users.noreply.github.com> Date: Mon, 14 Aug 2023 11:05:33 -0400 Subject: [PATCH 31/40] make PKCS11_module_path an arg --- libpkpass/crypto.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/libpkpass/crypto.py b/libpkpass/crypto.py index d88d83d..1e18f1c 100755 --- a/libpkpass/crypto.py +++ b/libpkpass/crypto.py @@ -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""" @@ -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""" #################################################################### @@ -254,7 +253,7 @@ def pk_sign_string(string, identity, passphrase, SCBackend="opensc", card_slot=N "-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") ) From 1a6c9b04e480d19dca4c477ca4624bdfcd2806fb Mon Sep 17 00:00:00 2001 From: CBAI <7985170+carljbai@users.noreply.github.com> Date: Mon, 14 Aug 2023 11:18:00 -0400 Subject: [PATCH 32/40] add PKCS11_module_path to args --- libpkpass/commands/command.py | 2 ++ libpkpass/crypto.py | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) 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/crypto.py b/libpkpass/crypto.py index 1e18f1c..1f5bf39 100755 --- a/libpkpass/crypto.py +++ b/libpkpass/crypto.py @@ -252,7 +252,6 @@ 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=PKCS11_module_path)) as proc: stdout, _ = proc.communicate( input=stringhash.encode("UTF-8") From 6d0a73a92af662c44e9100598bf039478b7bb7a3 Mon Sep 17 00:00:00 2001 From: CBAI <7985170+carljbai@users.noreply.github.com> Date: Mon, 14 Aug 2023 11:21:04 -0400 Subject: [PATCH 33/40] add pkcs11 module arg --- libpkpass/commands/arguments.py | 8 ++++++++ 1 file changed, 8 insertions(+) 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", + }, + }, } From f6c72e3260ae743c885ab680e4008a94e9674557 Mon Sep 17 00:00:00 2001 From: CBAI <7985170+carljbai@users.noreply.github.com> Date: Mon, 14 Aug 2023 11:26:26 -0400 Subject: [PATCH 34/40] args --- libpkpass/util.py | 1 + 1 file changed, 1 insertion(+) 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) From f63cfa77f2ff9b327af390ded411d73a0cca724f Mon Sep 17 00:00:00 2001 From: CBAI <7985170+carljbai@users.noreply.github.com> Date: Mon, 14 Aug 2023 11:26:54 -0400 Subject: [PATCH 35/40] add self args for pkcs11 module --- libpkpass/commands/clip.py | 1 + libpkpass/commands/distribute.py | 2 ++ libpkpass/commands/export.py | 1 + libpkpass/commands/populate.py | 1 + libpkpass/commands/rename.py | 1 + libpkpass/commands/show.py | 1 + 6 files changed, 7 insertions(+) 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/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..f6210c2 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( From 41dd524697713bb39256dd4b3021279180543d7c Mon Sep 17 00:00:00 2001 From: CBAI <7985170+carljbai@users.noreply.github.com> Date: Mon, 14 Aug 2023 11:27:34 -0400 Subject: [PATCH 36/40] module path to passwords --- libpkpass/password.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/libpkpass/password.py b/libpkpass/password.py index 3618ae9..c26064e 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,6 +305,7 @@ 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) From 7502a0139e78fbc77aa1782c68ac6757b1d57a4f Mon Sep 17 00:00:00 2001 From: CBAI <7985170+carljbai@users.noreply.github.com> Date: Mon, 14 Aug 2023 11:48:26 -0400 Subject: [PATCH 37/40] typo --- libpkpass/commands/show.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libpkpass/commands/show.py b/libpkpass/commands/show.py index f6210c2..5fc5c98 100644 --- a/libpkpass/commands/show.py +++ b/libpkpass/commands/show.py @@ -76,7 +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"] + PKCS11_module_path=self.args["PKCS11_module_path"], ) with open(temp_key, "w", encoding="ASCII") as fname: fname.write( @@ -157,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) From b293a5cbb96dba09bb8bd54c1b51c7ed74efcffe Mon Sep 17 00:00:00 2001 From: CBAI <7985170+carljbai@users.noreply.github.com> Date: Mon, 14 Aug 2023 14:03:02 -0400 Subject: [PATCH 38/40] comment for opensc --- libpkpass/password.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libpkpass/password.py b/libpkpass/password.py index c26064e..e612c3f 100644 --- a/libpkpass/password.py +++ b/libpkpass/password.py @@ -418,7 +418,7 @@ def create_error_message(recipient_timestamp, card_slot, SCBackend="opensc"): 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 + # For opensc there is also an additional information line so add 1 again if int(card_slot) + 2 > len(get_card_info(SCBackend)[0]): msg = "Attempting to use card slot that is not connected" elif distribute_time < card_start: From 676afe90682596069aa92469fb2f31293d76b520 Mon Sep 17 00:00:00 2001 From: CBAI <7985170+carljbai@users.noreply.github.com> Date: Mon, 14 Aug 2023 14:05:10 -0400 Subject: [PATCH 39/40] fix password error create --- libpkpass/password.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libpkpass/password.py b/libpkpass/password.py index e612c3f..7dc8cdc 100644 --- a/libpkpass/password.py +++ b/libpkpass/password.py @@ -419,7 +419,7 @@ def create_error_message(recipient_timestamp, card_slot, SCBackend="opensc"): distribute_time = float(recipient_timestamp) # Slots are indexed at 0 so when enumerating you add 1 # For opensc there is also an additional information line so add 1 again - if int(card_slot) + 2 > len(get_card_info(SCBackend)[0]): + 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" From 22ae40d9baa71d35b0188b0020939a74044cfb6b Mon Sep 17 00:00:00 2001 From: CBAI <7985170+carljbai@users.noreply.github.com> Date: Mon, 14 Aug 2023 14:17:21 -0400 Subject: [PATCH 40/40] update passwd error to account for bad path --- libpkpass/password.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/libpkpass/password.py b/libpkpass/password.py index 7dc8cdc..2e31df8 100644 --- a/libpkpass/password.py +++ b/libpkpass/password.py @@ -308,7 +308,7 @@ def decrypt_entry(self, identity=None, passphrase=None, card_slot=None, SCBacken 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 @@ -317,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 @@ -413,7 +413,7 @@ 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) @@ -423,6 +423,8 @@ def create_error_message(recipient_timestamp, card_slot, SCBackend="opensc"): 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