New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add ability to encrypt CA private key at rest #386
Add ability to encrypt CA private key at rest #386
Conversation
0c2a0b5
to
5fbd77f
Compare
Just updated this - I was focused on the encryption aspect and forgot to mask the password on entry... switched to the |
6d610db
to
93bf146
Compare
So I was trying to figure out how best to store the parameters for Argon2id... I am familiar with the syntax PHP's
Furthermore, there are other libraries which use this format, e.g. https://github.com/alexedwards/argon2id so I'll look into implementing it this way. |
93bf146
to
bd804ad
Compare
Just pushed another commit to store the encryption algorithm and KDF parameters in a protobuf message. Feel free to rebase before merge if preferred. This should offer more flexibility for supporting additional algorithms in the future (both because additional fields can be added to the message in a backwards compatible manner, and because the encryption algorithm itself is now stored.) This is my first time using protobufs though so if I'm missing something here, please let me know! For some reason, running I did not update tests yet, hope to get around to it sometime tomorrow. The main points of consideration at this point I think are:
I will update the pull request description to clarify the current state of this diff. |
cert/cert.proto
Outdated
message RawNebulaEncryptedDataMetadata { | ||
string EncryptionAlgorithm = 1; | ||
string KeyDerivationParameters = 2; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I kinda waffled on whether to store parameters & the KDF algorithm separately. By storing them together, I am able to reuse the format seen in the Argon2 reference implementation, PHP's password_hash, etc.
Nice feature. Of course this could probably be done via vault agent or other means right now. |
49e8bf4
to
d09418c
Compare
Just added tests for the CLI commands themselves and rebased on master, so this should be ready for review.
Interesting idea. I think rotating the password would only be valuable if you could ensure that no old copies of the encrypted (or decrypted) CA key exist. (edit to clarify - If any copies of the key exist, "rotating" the password is a little misleading, since you're adding a new passphrase to unlock the key, but the existing key(s) still work with old copies. I think that this has different security properties than truly rotating the CA certificate.)
I wondered if you couldn't just use a plaintext private key and have Hashicorp Vault encrypt it at rest, foregoing this feature? I haven't used Vault before though so am not sure how one would go about this about this. One other related consideration from the pull request description:
If these features were implemented, it would allow for rotation of the passphrase itself - though I am unsure of the value it provides for the reasons stated above. It may be safer to simply create new CAs - a node can trust multiple CAs at once making rotation a bit easier. |
After talking over the pull request out-of-band with @nbrownus, I wanted to add some notes for followup:
I did forget to ask, any preference on rolling back the |
This is the best documented PR in the history of the project. I hope someday we merge it. :) |
Hah - I apologize for all the text. It's largely for my own documentation / note-taking as I work through this, as well as a hope that they could act as a weak form of documentation for others reviewing this code now or later. (I will admit - I've tried to I haven't made progress on this since my last comment, but I did find two other projects using Argon2 as a KDF for AES which I wanted to share my findings on -
It is worth noting that while LUKS2 defaults to Argon2i, this default can be overridden at both compile-time and runtime. Furthermore, Argon2id is also offered as a KDF, but Argon2d is not. The following quote from the Argon2 RFC (currently rev. 13) is used to justify LUKS2's decision to utilize Argon2i as opposed to Argon2d:
In contrast, KeePass justifies their decision to use Argon2d:
Outside of this RFC, recommendations are made to use Argon2id in all circumstances unless you have a deep understanding of the tradeoffs and are sure you do not need the benefits of the other. Furthermore, the RFC gives recommendations for both Argon2d (specifically in the context of cryptocurrencies) and Argon2id, but offers no such recommendations for Argon2i. In any case, Argon2id is resistant to both side-channel and GPU attacks, and I still believe its usage is appropriate in this pull request unless a crypto expert says otherwise. As a data point, the default parameters for Argon2i as compiled into my Arch Linux copy of
KeePass instead suggests the following procedure for choosing parameters:
|
I updated I performed some benchmarking on some devices I had readily available, the results of which can be seen here: https://gist.github.com/JohnMaguire/7a2f2be89b736a5b3d6323d0828eb140 If you check an earlier revision, you'll see ns/op. The current revision has been converted to s/op (though I didn't update the units, sorry!) Small sample size of 2 laptops and a home NAS. If anyone would like to run these benchmarks themselves, please feel free. I ran them via My goal is to find a sane default that will work for most users from an ordinary laptop/workstation device. I am thinking that targeting roughly 2 seconds - 3 seconds may be a reasonable goal. Per the RFC...
However, t=1 (iterations = 1) does not attain a 2 - 3 second key derivation time without a very high parallelism factor. This seems somewhat wasteful, so I spent some time tweaking the iterations. A subset of tests from the slowest device (AMD FX-4300, 4 threads) around the target range:
Let's see what those numbers look like on the fastest device (Intel i9-9880H, 16 threads)...
It's worth noting at this point that increasing parallelism does two things: (a) It changes the resulting derived key, (b) It increases the number of threads required to compute a single hash, so it is helpful in avoiding parallelized hash cracking. [But on systems with more threads, it also decreases the time to compute a single hash.] I am thinking that 1 GiB, parallelism = 4, iterations = 4 may be the sweet spot here...
I continue to be open to suggestions. Or a security review ;) Still need to add a way to override the defaults and look into getting rid of the stringification of the Argon2 parameters. |
Diff is updated with some default parameters mentioned by the Argon2 RFC - this seems likely to be the best recommendation for Argon2 at the moment. Argon2 parameters can now be overridden. Encryption is opt-in. Pull request description is updated. TODO: Un-stringify the KDF params and just use protobuf for serialization. |
3942a2a
to
4738535
Compare
Would it be possible to extend this to the client keys, not just the CA key? (Or, does this need to be a separate request, once this is merged?) |
You're aware that this means the client needs to enter the password every time the application is started, right? |
e3c7a10
to
ba84d09
Compare
ba84d09
to
3e4e886
Compare
3e4e886
to
c953d36
Compare
c953d36
to
faf5634
Compare
Fixes slackhq#8. This commit causes `nebula-cert ca` to prompt for a passphrase by default. If a passphrase is entered, the private key will be encrypted using AES-256 and the Argon2id key derivation function. A new private key banner has been added for encrypted private keys. A flag `-no-encryption` can also be passed to skip this prompt (e.g. for scripting.) Calls to `nebula-cert sign` which point to an encrypted CA private key will now prompt for a passphrase. Unencrypted CA private keys will not be prompted for a passphrase.
Adds two additional protobuf messages: - `RawNebulaEncryptedData`, a wrapper containing `Ciphertext` and `EncryptedMetadata` - `RawNebulaEncryptedDataMetadata`, which contains strings denoting the `EncryptionAlgorithm` and the `KeyDerivationParameters` The commit uses the existing encoding format for Argon2id parameters, as I believe this format should be fairly flexible for handling other KDFs as well. The main improvement in this diff is that it offers more flexibility in modifying the structure of metadata in the future, as well as storing the encryption algorithm used.
- Creates PasswordReader interface to support testing - Uses a StdinPasswordReader for passphrases in production code - Uses a StubPasswordReader to test passphrases and errors in tests
The sshd package is still using /x/crypto/ssh/terminal - I opted not to update it as it's outside of the scope of this pull request, which is already somewhat large.
This commit makes a few changes: 1. It uses parameters recommended by the Argon2 RFC.[1] 2. It turns encryption off by default, so that `-encrypt` must be passed manually in order to encrypt the private key. 3. It allows the user to override the Argon2 parameters using the CLI flags `-argon-memory`, `-argon-parallelism` and `-argon-iterations` If we decide we don't want KDF parameter defaults, we can simply update the CLI flag defaults to `0` in the `nebula-cert/ca.go` file. [1] https://datatracker.ietf.org/doc/draft-irtf-cfrg-argon2/?include_text=1 > We recommend the following procedure to select the type and the > parameters for practical use of Argon2. > 1. If you are OK with a uniformly safe option, but not tailored to > your application or hardware, select Argon2id with t=1 > iteration, p=4 lanes and m=2^(21) (2 GiB of RAM), 128-bit salt, > 256-bit tag size. This is the FIRST RECOMMENDED option. > 2. If much less memory is available, a uniformly safe option is > Argon2id with t=3 iterations, p=4 lanes and m=2^(16) (64 MiB of > RAM), 128-bit salt, 256-bit tag size. This is the SECOND > RECOMMENDED option.
Removes code that was serializing to string / deserializing from string. Now we just use protobuf fields. The concern was that this wouldn't be flexible for the future, but we can use `oneof` then to allow for other types of parameters.
faf5634
to
1309220
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
Fixes slackhq#8. `nebula-cert ca` now supports encrypting the CA's private key with a passphrase. Pass `-encrypt` in order to be prompted for a passphrase. Encryption is performed using AES-256-GCM and Argon2id for KDF. KDF parameters default to RFC recommendations, but can be overridden via CLI flags `-argon-memory`, `-argon-parallelism`, and `-argon-iterations`.
Fixes #8.
This pull request allows for the encryption of the CA private key by passing a
-encrypt
flag when generating the keypair. The private key is encrypted with AES-256-GCM and Argon2id. The Argon2id factors from the Argon2 RFC[1] are used by default. These parameters may be overridden via the CLI flags-argon-memory
,-argon-parallelism
and-argon-iterations
.[1] https://datatracker.ietf.org/doc/draft-irtf-cfrg-argon2/?include_text=1
Calls to
nebula-cert sign
which point to an encrypted CA private key will now also prompt for a passphrase. Unencrypted CA private keys will not be prompted for a passphrase.Some examples: