-
Notifications
You must be signed in to change notification settings - Fork 0
feat: add AES-256 secret key generation with secure memory #149
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
Merged
Merged
Changes from 3 commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
8a2ef60
feat: add AES-256 secret key generation with secure memory
jithinkunjachan d8e403e
docs: add godoc comments to AES-256-GCM cryptor exports
jithinkunjachan 4395963
refactor: remove request
jithinkunjachan 8415db5
remove error check
jithinkunjachan File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,72 @@ | ||
| package cryptor | ||
|
|
||
| import ( | ||
| "context" | ||
| "crypto/rand" | ||
| "errors" | ||
| "fmt" | ||
|
|
||
| "github.com/openkcm/krypton/internal/securemem" | ||
| ) | ||
|
|
||
| const secretKey = "secretKey" | ||
|
|
||
| // GenerateSecretResponse holds the generated secret stored in secure (mlock'd) memory. | ||
| // The caller is responsible for calling Secret.Destroy() when the key is no longer needed. | ||
| type GenerateSecretResponse struct { | ||
| Secret *securemem.Data | ||
| } | ||
|
|
||
| // SecretGenerator generates cryptographic secret keys into secure memory. | ||
| type SecretGenerator interface { | ||
| GenerateSecret(ctx context.Context) (*GenerateSecretResponse, error) | ||
| } | ||
|
|
||
| // ErrAllocatedSecretNotFound indicates that the secret key generated and allocated in the vault cannot be found. | ||
| var ErrAllocatedSecretNotFound = errors.New("allocated secret not found in vault") | ||
|
|
||
| // AES256SecretGenerator generates 256-bit AES keys using crypto/rand. | ||
| // The generated key material is stored in mlock'd memory and never touches the Go heap. | ||
| type AES256SecretGenerator struct{} | ||
|
|
||
| var _ SecretGenerator = &AES256SecretGenerator{} | ||
|
|
||
| // NewAES256SecretGenerator returns a ready-to-use secret generator. | ||
| func NewAES256SecretGenerator() *AES256SecretGenerator { | ||
| return &AES256SecretGenerator{} | ||
| } | ||
|
|
||
| // GenerateSecret generates a new AES-256 secret key and stores it in mlock'd memory. | ||
| // The caller must call resp.Secret.Destroy() when the key is no longer needed. | ||
| func (a *AES256SecretGenerator) GenerateSecret(ctx context.Context) (*GenerateSecretResponse, error) { | ||
| resp, err := securemem.Run(ctx, func(ctx context.Context, hReq *securemem.HandlerRequest) error { | ||
| b, err := hReq.PersistentVault().Reserve(secretKey, 32) | ||
| if err != nil { | ||
| return fmt.Errorf("failed to allocate new securemem bytes: %w", err) | ||
| } | ||
|
|
||
| _, err = rand.Read(b) | ||
| if err != nil { | ||
| return fmt.Errorf("failed to generate random bytes: %w", err) | ||
| } | ||
|
|
||
| return nil | ||
| }) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
|
|
||
| secret, ok := resp.MemVault().Get(secretKey) | ||
| if !ok { | ||
| // This should never happen since we just reserved this memory, but if it does, destroy all vault data to be safe. | ||
| err = resp.MemVault().DestroyAll() | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
| return nil, fmt.Errorf("allocated secret not found in vault after generation: %w", ErrAllocatedSecretNotFound) | ||
| } | ||
|
|
||
| return &GenerateSecretResponse{ | ||
| Secret: secret, | ||
| }, nil | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,55 @@ | ||
| package cryptor_test | ||
|
|
||
| import ( | ||
| "testing" | ||
|
|
||
| "github.com/stretchr/testify/assert" | ||
| "github.com/stretchr/testify/require" | ||
|
|
||
| "github.com/openkcm/krypton/internal/cryptor" | ||
| ) | ||
|
|
||
| func TestAES256SecretGen(t *testing.T) { | ||
| // given | ||
| ctx := t.Context() | ||
| subj := cryptor.NewAES256SecretGenerator() | ||
|
|
||
| t.Run("should generate secret successfully", func(t *testing.T) { | ||
| // when | ||
| res, err := subj.GenerateSecret(ctx) | ||
| t.Cleanup(func() { | ||
| if res != nil { | ||
| _ = res.Secret.Destroy() | ||
| } | ||
| }) | ||
|
|
||
| // then | ||
| assert.NoError(t, err) | ||
| require.NotNil(t, res) | ||
| assert.Len(t, res.Secret.SecureBytes(), 32) | ||
| assert.Equal(t, "secretKey", res.Secret.Name()) | ||
| }) | ||
|
|
||
| t.Run("should generate secret randomly", func(t *testing.T) { | ||
| foundKeys := make(map[string]struct{}) | ||
|
|
||
| for range 1000 { | ||
| // when | ||
| res, err := subj.GenerateSecret(ctx) | ||
| t.Cleanup(func() { | ||
| if res != nil { | ||
| _ = res.Secret.Destroy() | ||
| } | ||
| }) | ||
|
|
||
| // then | ||
| assert.NoError(t, err) | ||
| require.NotNil(t, res) | ||
| assert.Equal(t, "secretKey", res.Secret.Name()) | ||
|
|
||
| key := string(res.Secret.SecureBytes()) | ||
| assert.NotContains(t, foundKeys, key) | ||
| foundKeys[key] = struct{}{} | ||
| } | ||
| }) | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
Technically rand.Read never returns an error. The signature is chosen like this only to satisfy
io.Readerhttps://pkg.go.dev/crypto/rand#Read
So we could make it shorter and add a comment about the no-error behaviour