Skip to content

Conversation

@SgtCoDFish
Copy link
Contributor

@SgtCoDFish SgtCoDFish commented Jan 19, 2026

Summary

Add RSA-based envelope encryption capabilities to secure secrets sent from disco-agent to the DisCo backend. This PR introduces the internal/envelope package which provides envelope encryption types and an initial implementation using RSA

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/envelope Package

Defines some basic types for the kinds of encryption we're doing. Leaves space for us to use other algorithms + schemes down the road (e.g. HPKE).

internal/envelope/rsa Package

Implements envelope encryption using RSA for key wrapping and AES-256-GCM for data encryption:

  • Encryptor:

    • Generates random AES-256 key for each encryption operation
    • Encrypts data with AES-256-GCM (authenticated encryption)
    • Wraps AES key using RSA-OAEP-SHA256
    • Minimum 2048-bit RSA key requirement for security
  • Key Management:

    • Helpers for loading RSA public keys from PEM format
    • Support for both PKIX (PUBLIC KEY) and PKCS1 (RSA PUBLIC KEY) formats
    • File-based and byte-based key loading
  • Data Structures:

    • EncryptedData containing encrypted key, encrypted data, and nonce
    • Clean separation of encryption components for transmission

Dependencies

No new external dependencies required - uses Go standard library crypto packages.

Technical Details

Envelope Encryption Flow

  1. Generate random AES-256 key
  2. Generate random appropriately sized nonce
  3. Encrypt plaintext using AES-256-GCM with the AES key → ciphertext
  4. Encrypt AES key using RSA-OAEP-SHA256 with RSA public key → encrypted key
  5. Return {encryptedKey, encryptedData, nonce}

Design Choices

  • AES-256-GCM: Industry-standard authenticated encryption
  • RSA-OAEP-SHA256: Secure RSA encryption padding with SHA-256
  • 32-byte AES keys: AES-256 for strong symmetric encryption
  • standard nonces: Standard GCM nonce size.
    • NB: Nonce sizes can be security critical. Reusing a nonce with the same key breaks AES-256 GCM completely.
    • Due to the birthday paradox, the risk of reusing (randomly-generated) nonces can be quite high with standard 12-byte nonces when a key is used many times
    • But this is not an issue for us - we generate a new AES key and a new nonce for each upload
  • 2048-bit minimum RSA key size: Industry standard. We ultimately will get a key from DisCo so we don't choose the size.

Testing

Run tests:

go test ./internal/envelope/...

Next Steps

  • Integration with disco-agent to use these encryption primitives when sending secrets
  • Key distribution mechanism (retrieving RSA public keys from DisCo endpoint)

@SgtCoDFish SgtCoDFish force-pushed the envelope branch 2 times, most recently from c4f2051 to ef671fe Compare January 20, 2026 14:39
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 <ashley.davis@cyberark.com>
@SgtCoDFish SgtCoDFish changed the title wip: envelope Add initial (unused) RSA envelope encryption Jan 20, 2026
@maelvls
Copy link
Member

maelvls commented Jan 20, 2026

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 😅

@SgtCoDFish
Copy link
Contributor Author

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...

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.

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...

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.

Copy link
Member

@wallrj-cyberark wallrj-cyberark left a 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:

// Nonce is the 12-byte nonce used for AES-GCM encryption.
// This is intentionally plaintext.
Nonce []byte `json:"nonce"`
}
Copy link
Member

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.

Copy link
Contributor Author

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.

Mostly this focuses on documentation to make the purpose
of the chosen functions clear

Also moves Encryptor to a better home

Signed-off-by: Ashley Davis <ashley.davis@cyberark.com>
Signed-off-by: Ashley Davis <ashley.davis@cyberark.com>
@SgtCoDFish
Copy link
Contributor Author

Great review @wallrj-cyberark thanks! I've addressed all comments, and also refactored this with a view towards the future where we could add a similar version supporting HPKE instead of using RSA.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants