Nitrokey / pynitrokey

Python client for Nitrokey devices
Apache License 2.0
98 stars 27 forks source link

NetHSM: Impossible to add key mechanism interactively #452

Open q-nk opened 11 months ago

q-nk commented 11 months ago

It seems to be impossible to enter the mechanism for NetHSM interactively in nitropy.

nitropy nethsm --host [OMITTED] --no-verify-tls --username [OMITTED] --password [OMITTED] generate-key
Command line tool to interact with Nitrokey devices 0.4.40
Type (RSA, Curve25519, EC_P224, EC_P256, EC_P384, EC_P521, Generic): RSA
Length: 2048
Supported mechanisms for this key type:
  RSA_Decryption_RAW
  RSA_Decryption_PKCS1
  RSA_Decryption_OAEP_MD5
  RSA_Decryption_OAEP_SHA1
  RSA_Decryption_OAEP_SHA224
  RSA_Decryption_OAEP_SHA256
  RSA_Decryption_OAEP_SHA384
  RSA_Decryption_OAEP_SHA512
  RSA_Signature_PKCS1
  RSA_Signature_PSS_MD5
  RSA_Signature_PSS_SHA1
  RSA_Signature_PSS_SHA224
  RSA_Signature_PSS_SHA256
  RSA_Signature_PSS_SHA384
  RSA_Signature_PSS_SHA512
Please enter at least one mechanism.  Enter an empty string to finish the list of mechanisms.
Add mechanism: RSA_Signature_PKCS1
Add mechanism (or empty string to continue): 
Error: '' is not one of 'RSA_Decryption_RAW', 'RSA_Decryption_PKCS1', 'RSA_Decryption_OAEP_MD5', 'RSA_Decryption_OAEP_SHA1', 'RSA_Decryption_OAEP_SHA224', 'RSA_Decryption_OAEP_SHA256', 'RSA_Decryption_OAEP_SHA384', 'RSA_Decryption_OAEP_SHA512', 'RSA_Signature_PKCS1', 'RSA_Signature_PSS_MD5', 'RSA_Signature_PSS_SHA1', 'RSA_Signature_PSS_SHA224', 'RSA_Signature_PSS_SHA256', 'RSA_Signature_PSS_SHA384', 'RSA_Signature_PSS_SHA512'.
Add mechanism (or empty string to continue): 
Error: '' is not one of 'RSA_Decryption_RAW', 'RSA_Decryption_PKCS1', 'RSA_Decryption_OAEP_MD5', 'RSA_Decryption_OAEP_SHA1', 'RSA_Decryption_OAEP_SHA224', 'RSA_Decryption_OAEP_SHA256', 'RSA_Decryption_OAEP_SHA384', 'RSA_Decryption_OAEP_SHA512', 'RSA_Signature_PKCS1', 'RSA_Signature_PSS_MD5', 'RSA_Signature_PSS_SHA1', 'RSA_Signature_PSS_SHA224', 'RSA_Signature_PSS_SHA256', 'RSA_Signature_PSS_SHA384', 'RSA_Signature_PSS_SHA512'.
Add mechanism (or empty string to continue): 
Error: '' is not one of 'RSA_Decryption_RAW', 'RSA_Decryption_PKCS1', 'RSA_Decryption_OAEP_MD5', 'RSA_Decryption_OAEP_SHA1', 'RSA_Decryption_OAEP_SHA224', 'RSA_Decryption_OAEP_SHA256', 'RSA_Decryption_OAEP_SHA384', 'RSA_Decryption_OAEP_SHA512', 'RSA_Signature_PKCS1', 'RSA_Signature_PSS_MD5', 'RSA_Signature_PSS_SHA1', 'RSA_Signature_PSS_SHA224', 'RSA_Signature_PSS_SHA256', 'RSA_Signature_PSS_SHA384', 'RSA_Signature_PSS_SHA512'.
Add mechanism (or empty string to continue): ^CAborted!

The generate_key function https://github.com/Nitrokey/pynitrokey/blob/master/pynitrokey/cli/nethsm.py#L683

def generate_key(ctx, type, mechanisms, length, key_id):
    """Generate a key pair on the NetHSM.

    This command requires authentication as a user with the Administrator
    role."""
    mechanisms = list(mechanisms) or prompt_mechanisms(type)
    with connect(ctx) as nethsm:
        key_id = nethsm.generate_key(type, mechanisms, length, key_id)
        print(f"Key {key_id} generated on NetHSM {nethsm.host}")

indicates, that the error is in prompt_mechanisms. The function prompt_mechanisms https://github.com/Nitrokey/pynitrokey/blob/master/pynitrokey/cli/nethsm.py#L506.

def prompt_mechanisms(type):
    # We assume that key type X corresponds to the mechanisms starting with X.
    # This is no longer true for curves, so we have to adapt the type
    if type == nethsm_sdk.KeyType.CURVE25519.value:
        type = "EdDSA"
    elif type.startswith("EC_"):
        type = "ECDSA"

    available_mechanisms = []
    print("Supported mechanisms for this key type:")
    for mechanism in nethsm_sdk.KeyMechanism:
        if mechanism.value.startswith(type):
            available_mechanisms.append(mechanism.value)
            print(f"  {mechanism.value}")

    # If there is only one matching algorithm, we can choose it and don’t have
    # to ask the user.
    if len(available_mechanisms) == 1:
        print(f"Automatically selecting the key mechanism {available_mechanisms[0]}")
        return available_mechanisms

    print(
        "Please enter at least one mechanism.  Enter an empty string to "
        "finish the list of mechanisms."
    )

    mechanism_type = click.Choice(available_mechanisms, case_sensitive=False)
    mechanisms = []
    cont = True
    while cont:
        default = None
        prompt_text = "Add mechanism"
        if mechanisms:
            prompt_text += " (or empty string to continue)"
            default = ""
        mechanism = prompt(
            prompt_text,
            type=mechanism_type,
            default=default,
            show_choices=False,
            show_default=False,
        )
        if mechanism:
            mechanisms.append(mechanism)
        else:
            cont = False

    if not mechanisms:
        raise click.ClickException("No key mechanisms selected!")

    return mechanisms

I assume the error to be in the while loop. When entering an empty string to continue, after a mechanism was added, the prompt function fails, as empty string is not in mechanism_type. The prompt function then just asks the user again to input a valid mechanism. If a valid mechanism is entered, the mechanism is appended and the loop doesn't abort, leading to an infinite loop.