-
Notifications
You must be signed in to change notification settings - Fork 24
Add initial (unused) RSA envelope encryption #756
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
base: master
Are you sure you want to change the base?
Conversation
c4f2051 to
ef671fe
Compare
This commit adds support for envelope encryption using an RSA key. Note: This encryption is specifically for disco-agent and not for venafi-kubernetes-agent although the code here is not used in either agent yet. Signed-off-by: Ashley Davis <[email protected]>
|
I'm surprised we are relying on RSA for encryption rather than something faster and more robust (EC based like ECDH?). Is that because the DisCo backend is old or something? I thought the proper way to do hybrid public key encryption would be to use Go 1.26's ML-KEM support? Is that because the lib is too bleeding edge? The RFC is from 2022, I imagine we would be OK using it... Sorry about the dumb questions, I haven't looked at any of the design docs or anything related to this feature 😅 |
The agent is ultimately going to have to react to whatever public key it gets from the server. I don't think the backend is old - I think RSA is just the "first thing" getting implemented. There are discussions ongoing around what this looks like longer term, but RSA is secure enough today and the amount of data being encrypted with RSA is so small that performance shouldn't be a concern.
The gold standard today would be to use HPKE, which go will support in its stdlib from 1.26. We could absolutely use that instead, but the initial decision for now is RSA. |
wallrj-cyberark
left a comment
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.
Thanks @SgtCoDFish
It looks good, but I'd like a bit more documentation with references to the principles and best-practices of Envelope encryption. Perhaps a doc.go. I'm no crypto expert.
I'm happy to review followup PRs as you develop this further.
Here's the copilot review I referred to, above:
| if err != nil { | ||
| return nil, fmt.Errorf("failed to encrypt AES key with RSA: %w", err) | ||
| } | ||
|
|
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.
Copilot suggested zeroing the aesKey from memory, once it has been encrypted.
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.
:) Ash is waiting for the secret.Do() to become available in order to avoid manually zeroing the memory.
| return nil, fmt.Errorf("failed to generate nonce: %w", err) | ||
| } | ||
|
|
||
| encryptedData.EncryptedData = gcm.Seal(nil, encryptedData.Nonce, data, nil) |
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.
It'd help me, as a maintainer, to know what these nil parameters are. Maybe add a comment about why they're not needed here.
I found another library, called tink that does use supply these, but I don't really understand what I'm looking at:
| aesKey := make([]byte, aesKeySize) | ||
| if _, err := rand.Read(aesKey); err != nil { | ||
| return nil, fmt.Errorf("failed to generate AES key: %w", err) | ||
| } |
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.
Is this sometimes known as DEK (Data Encryption Key)? Is that a standard term? Would it be appropriate to use that standard terminology here, with reference to some open blueprint for envelope encryption:
That doc also talks about KEK Key Encryption Key, which I guess corresponds to the Encryptor.rsaPublicKey
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.
Yeah those are standard terms and we could use them for sure. Even if I just add a comment linking the terminology up, that would improve it. I'll have a think!
| rand.Reader, | ||
| e.rsaPublicKey, | ||
| aesKey, | ||
| nil, |
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.
Maybe add a comment about this nil parameter too.
| // Nonce is the 12-byte nonce used for AES-GCM encryption. | ||
| // This is intentionally plaintext. | ||
| Nonce []byte `json:"nonce"` | ||
| } |
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.
This is serializable to JSON, but does the EncryptedKey, EncryptedData, and nonce need to be base64 encoded first?
Maybe add a test to demonstrate that the json serialization works.
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.
stdlib encoding/json converts byte slices to b64 by default, so I don't think there's too much value in explicitly testing the serialization
From go doc json Marshal:
Array and slice values encode as JSON arrays, except that []byte encodes as
a base64-encoded string, and a nil slice encodes as the null JSON value.
| // Due to the birthday paradox, the risk of reusing (randomly-generated) nonces can be quite high. | ||
| // This package is assumed to be used in contexts where a new key is generated for each encryption operation, | ||
| // so the nonce size doesn't matter. | ||
| nonceSize = 12 |
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.
Copilot suggests
use gcm.NonceSize() rather than a package-level nonceSize constant
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.
Ahh, good catch!
Summary
Add RSA-based envelope encryption capabilities to secure secrets sent from disco-agent to the DisCo backend. This PR introduces the
internal/envelopepackage, which provides envelope encryption primitives.Motivation
The disco-agent needs to encrypt sensitive secrets before transmitting them to the DisCo backend. This PR provides the cryptographic building blocks to enable end-to-end encryption of secrets, ensuring that sensitive data is protected in transit and at rest.
Note: This encryption is specifically for disco-agent and not for venafi-kubernetes-agent although the code here is not used in either agent yet.
Changes
internal/envelopePackageImplements envelope encryption using RSA for key wrapping and AES-256-GCM for data encryption:
Encryptor (
internal/envelope/encryptor.go:24-91):Key Management (
internal/envelope/keys.go:14-57):PUBLIC KEY) and PKCS1 (RSA PUBLIC KEY) formatsData Structures (
internal/envelope/types.go:1-23):EncryptedDatacontaining encrypted key, encrypted data, and nonceDependencies
No new external dependencies required - uses Go standard library crypto packages.
Technical Details
Envelope Encryption Flow
Design Choices
Testing
Run tests:
go test ./internal/envelope/...Next Steps