Skip to content

Deterministic Keystore#2701

Merged
KPrasch merged 27 commits intonucypher:mainfrom
KPrasch:keyring-v2
Jul 3, 2021
Merged

Deterministic Keystore#2701
KPrasch merged 27 commits intonucypher:mainfrom
KPrasch:keyring-v2

Conversation

@KPrasch
Copy link
Copy Markdown
Member

@KPrasch KPrasch commented May 19, 2021

Type of PR:
Feature & Rework

Required reviews:
3

What this does:

NuCypher Keystore
  • Support restoration of Keystore (formerly keyring) from mnemonic seed phrase
  • Use HKDF info strings to derive umbral private keys from secret material and info
  • Reworks keyring functionality to implement deterministic power derivation from secret
  • Simplified keystore file and metadata format for encrypted seed
  • Reduces Scrypt derivation difficulty (subject to further change)
  • Completely decouples ethereum account from keystore identification in favor of stamps
  • CLI interactions for mnemonic generation and recovery (ursula init & ursula recover, additional follow-up in Backup and restore Ursula config #2682)
  • Shorten minimum password length to 8 characters
TLS
  • Derive TLS private key instead of storing it to disk
  • Removes checksum addresses as TLS certificate pseudonyms and related utilities
  • Use IP addressing to index certificates and generate filenames
  • Revert self-signed certificate generation to each startup (needs follow up in [WIP] Update Ursulas certificate #2700)
Rename and reorg
  • Renames NucyperKeyring to Keystore
  • Creates new module nucypher.crypto.keystore
  • Creates new module nucypher.crypto.tls
  • Deprecates nucypher.crypto.api (moves contents to nuycpher.crypto.utils and nucypher.crypto.tls)
  • Corrects inaccurate Ursula configuration field checksum_address -> worker_address
Misc.
  • Index node metadata by stamp instead of checksum address (to accompany decoupling of checksum address from node storages)

Issues fixed/closed:

Notes for reviewers

The new version of keyring (now "keystore") is not presently backwards compatible with older versions of nucypher. If we need to preserve existing private keys, we can first expand the functionality of this PR by resolving #2655 and building a migration utility that implements it. However, given the volume of policies and the standing probationary period, I do not see much added value and suggest that we require all nodes to "re-init" thier nodes generating a fresh keystore and mnemonic.

Why it's needed:

@KPrasch KPrasch added Enhancement New or improved features API labels May 19, 2021
@KPrasch KPrasch requested a review from piotr-roslaniec May 19, 2021 06:01

def validate_keystore_password(password: str) -> List:
"""
NOTICE: Do not raise inside this function.
Copy link
Copy Markdown
Contributor

@piotr-roslaniec piotr-roslaniec May 26, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you expand on this comment a bit? Why we'd like to be careful here, to avoid leaking the password?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a fairly dated comment now, but it's still meant to describe two cases:

  • (Yes) Logging of the local scope's context to sentry, which includes the password.
  • Usage as a click interactive prompt validator which requires a boolean response.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Logging of the local scope's context to sentry, which includes the password.

Would it make sense to pass around the password in a container class with redefined str/repr, unpacking only right before usage? Or is it an overcomplication?

Copy link
Copy Markdown
Member Author

@KPrasch KPrasch Jun 16, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it make sense to pass around the password in a container class with redefined

Hmm not a bad idea, I wonder if this can effectively prevent unintentional transmission... Sounds like something that belongs in the passwords.py module from your recent PR, but perhaps in a follow-up. I'll open an issue about this after the review process advances a bit.

:return: bytes
"""
# TODO: Should we just use os.urandom or avoid the import w/ this?
return SYSTEM_RAND.getrandbits(num_bytes * 8).to_bytes(num_bytes, byteorder='big')
Copy link
Copy Markdown
Contributor

@piotr-roslaniec piotr-roslaniec May 26, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

os.random would be a bit more concise. For reference, SystemRandom is using os.urandom underneath, and both of those methods are not cross-platform compatible.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm somewhat compelled to use the secrets module as a source of randomness for added paranoia. https://docs.python.org/3/library/secrets.html

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Switched to the secrets version of SystemRandom.

@KPrasch KPrasch force-pushed the keyring-v2 branch 2 times, most recently from 97e3187 to b2518e2 Compare June 10, 2021 02:14
KPrasch added a commit to KPrasch/nucypher that referenced this pull request Jun 15, 2021
KPrasch added a commit to KPrasch/nucypher that referenced this pull request Jun 16, 2021
@KPrasch KPrasch changed the title [WIP] Deterministic Keystore Deterministic Keystore Jun 16, 2021
@KPrasch KPrasch marked this pull request as ready for review June 16, 2021 03:15
return canonical_address


def get_coordinates_as_bytes(point: Union[Point, UmbralPublicKey, SignatureStamp],
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it was removed in #2699

Copy link
Copy Markdown
Member Author

@KPrasch KPrasch Jun 18, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, it looks like the function above (canonical_address_from_umbral_key) which uses this function still has calls ans references. Perhaps it's not completely removed in main yet?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

canonical_address_from_umbral_key() is also modified to not require it. In fact, in #2612 crypto/utils.py got pretty small. See https://github.com/fjarri/nucypher/blob/7f36abde216d5b1e751e10a95d6f382d7e296474/nucypher/crypto/utils.py

return digest


def ecdsa_sign(message: bytes,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: it's being removed in #2612. If I'm not mistaken, it does the same as umbral's signing/verifying functions.

KPrasch added a commit to KPrasch/nucypher that referenced this pull request Jun 18, 2021
@KPrasch
Copy link
Copy Markdown
Member Author

KPrasch commented Jun 28, 2021

Rebased over cc578d9

if new_configuration.keyring != NO_KEYRING_ATTACHED:
maybe_public_key = bytes(new_configuration.keyring.signing_public_key).hex()
if new_configuration.keystore != NO_KEYSTORE_ATTACHED:
maybe_public_key = new_configuration.keystore.id
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems that now it's not a public key at all.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I admit the naming is a bit misleading here, but the Keystore's ID is set to the stamp's hex.

return signature_is_valid


def verify_ecdsa(message: bytes,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the difference between this function and Signature.verify()?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed ✔️

from nucypher.network.middleware import RestMiddleware
from nucypher.network.protocols import SuspiciousActivity
from nucypher.utilities.logging import Logger
from umbral.signing import Signature
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rebase artefact? Should be imported from umbral_adapter.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. I think I rebased this PR before realizing that I needed to import from umbral adapter. I'll make an update.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✔️

from cryptography.hazmat.primitives import hashes
from eth_utils import to_canonical_address, to_checksum_address
from typing import Optional, Tuple
from umbral.signing import Signature
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, needs umbral_adapter here

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✔️


class NotAPrivateKey:

fake_signature = Signature.from_bytes(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it a rebase thing, or it's actually used somewhere?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✔️

from eth_keys import KeyAPI as EthKeyAPI
from typing import Any, Union
from eth_utils.address import to_checksum_address
from umbral.signing import Signature
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

umbral_adapter here

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✔️

Copy link
Copy Markdown
Member

@cygnusv cygnusv left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some preliminary comments, so far looking good!

@@ -1,219 +0,0 @@
"""
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank goodness, the crypto.api module was too old and unspecific

__HKDF_HASH_ALGORITHM = BLAKE2B
__INFO_BASE = b'NuCypher/'
_VERIFYING_INFO = __INFO_BASE + b'verify'
_DECRYPTING_INFO = __INFO_BASE + b'encrypt'
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
_DECRYPTING_INFO = __INFO_BASE + b'encrypt'
_DECRYPTING_INFO = __INFO_BASE + b'decrypt'

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✔️

# HKDF
__HKDF_HASH_ALGORITHM = BLAKE2B
__INFO_BASE = b'NuCypher/'
_VERIFYING_INFO = __INFO_BASE + b'verify'
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than verifying, the angle here should be "signing". This is also more consistent with the Powers namespace (see line 268).

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✔️

if not salt and not info:
raise ValueError('Info or salt must be provided.')
info = info or bytes()
salt = salt or bytes()
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These 2 lines should be dropped. The first one is simply redundant, but the second is explicitly deviating us from the standard. I doubt this poses any security problem, but it's unnecessarily making future interoperability more difficult.
If info or salt are None, simply let HKDF handle that according to the specification.

(Update: it seems this method was dropped anyway in the yesterday's commit, so nothing to see here)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✔️

Copy link
Copy Markdown
Member Author

@KPrasch KPrasch left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR has three approvals - Feel free to continue reviewing but any additional RFCs will be completed in a follow-up PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Enhancement New or improved features

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Iteration on current password-based KDF Use deterministic key derivation to produce Keyring keys

6 participants