Skip to content

Commit

Permalink
Add Transit BYOK wrapping key endpoint (#15271)
Browse files Browse the repository at this point in the history
* add wrapping key endpoint

* change how wrapping key is stored

* move wrapping key func to backend

* refactor wrapping key generation

* Initial unit tests for Transit wrapping key endpoint

* Wire up wrapping key unit tests to actual implementation.

* Clean up Transit BYOK wrapping key tests and imports.

* Fix Transit wrapping key endpoint formatting.

* Update transit wrapping key to use lock manager for safe concurrent use.

* Rename some Transit wrapping key variables. Ensure the Transit wrapping key is correctly typed and formatted in a unit test.

* Fix spacing issue in Transit wrapping key endpoint help string.

Co-authored-by: rculpepper <rculpepper@hashicorp.com>
  • Loading branch information
schultz-is and rculpepper authored May 11, 2022
1 parent d8bbaf0 commit 91e0bbe
Show file tree
Hide file tree
Showing 3 changed files with 154 additions and 0 deletions.
1 change: 1 addition & 0 deletions builtin/logical/transit/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ func Backend(ctx context.Context, conf *logical.BackendConfig) (*backend, error)
b.pathConfig(),
b.pathRotate(),
b.pathRewrap(),
b.pathWrappingKey(),
b.pathKeys(),
b.pathListKeys(),
b.pathExportKeys(),
Expand Down
81 changes: 81 additions & 0 deletions builtin/logical/transit/path_wrapping_key.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package transit

import (
"context"
"crypto/x509"
"encoding/pem"
"fmt"
"strconv"

"github.com/hashicorp/vault/sdk/framework"
"github.com/hashicorp/vault/sdk/helper/keysutil"
"github.com/hashicorp/vault/sdk/logical"
)

const WrappingKeyName = "wrapping-key"

func (b *backend) pathWrappingKey() *framework.Path {
return &framework.Path{
Pattern: "wrapping_key",
Callbacks: map[logical.Operation]framework.OperationFunc{
logical.ReadOperation: b.pathWrappingKeyRead,
},
HelpSynopsis: pathWrappingKeyHelpSyn,
HelpDescription: pathWrappingKeyHelpDesc,
}
}

func (b *backend) pathWrappingKeyRead(ctx context.Context, req *logical.Request, _ *framework.FieldData) (*logical.Response, error) {
polReq := keysutil.PolicyRequest{
Upsert: true,
Storage: req.Storage,
Name: fmt.Sprintf("import/%s", WrappingKeyName),
KeyType: keysutil.KeyType_RSA4096,
Derived: false,
Convergent: false,
Exportable: false,
AllowPlaintextBackup: false,
AutoRotatePeriod: 0,
}
p, _, err := b.GetPolicy(ctx, polReq, b.GetRandomReader())
if err != nil {
return nil, err
}
if p == nil {
return nil, fmt.Errorf("error retrieving wrapping key: returned policy was nil")
}
if b.System().CachingDisabled() {
p.Unlock()
}

wrappingKey := p.Keys[strconv.Itoa(p.LatestVersion)]

derBytes, err := x509.MarshalPKIXPublicKey(wrappingKey.RSAKey.Public())
if err != nil {
return nil, fmt.Errorf("error marshaling RSA public key: %w", err)
}
pemBlock := &pem.Block{
Type: "PUBLIC KEY",
Bytes: derBytes,
}
pemBytes := pem.EncodeToMemory(pemBlock)
if pemBytes == nil || len(pemBytes) == 0 {
return nil, fmt.Errorf("failed to PEM-encode RSA public key")
}

publicKeyString := string(pemBytes)

resp := &logical.Response{
Data: map[string]interface{}{
"public_key": publicKeyString,
},
}

return resp, nil
}

const (
pathWrappingKeyHelpSyn = "Returns the public key to use for wrapping imported keys"
pathWrappingKeyHelpDesc = "This path is used to retrieve the RSA-4096 wrapping key " +
"for wrapping keys that are being imported into transit."
)
72 changes: 72 additions & 0 deletions builtin/logical/transit/path_wrapping_key_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package transit

import (
"context"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"testing"

"github.com/hashicorp/vault/sdk/logical"
)

const (
storagePath = "policy/import/" + WrappingKeyName
)

func TestTransit_WrappingKey(t *testing.T) {
// Set up shared backend for subtests
b, s := createBackendWithStorage(t)

// Ensure the key does not exist before requesting it.
keyEntry, err := s.Get(context.Background(), storagePath)
if err != nil {
t.Fatalf("error retrieving wrapping key from storage: %s", err)
}
if keyEntry != nil {
t.Fatal("wrapping key unexpectedly exists")
}

// Generate the key pair by requesting the public key.
req := &logical.Request{
Storage: s,
Operation: logical.ReadOperation,
Path: "wrapping_key",
}
resp, err := b.HandleRequest(context.Background(), req)
if err != nil {
t.Fatalf("unexpected request error: %s", err)
}
if resp == nil || resp.Data == nil || resp.Data["public_key"] == nil {
t.Fatal("expected non-nil response")
}
pubKeyPEM := resp.Data["public_key"]

// Ensure the returned key is a 4096-bit RSA key.
pubKeyBlock, _ := pem.Decode([]byte(pubKeyPEM.(string)))
rawPubKey, err := x509.ParsePKIXPublicKey(pubKeyBlock.Bytes)
if err != nil {
t.Fatalf("failed to parse public wrapping key: %s", err)
}
wrappingKey, ok := rawPubKey.(*rsa.PublicKey)
if !ok || wrappingKey.Size() != 512 {
t.Fatal("public wrapping key is not a 4096-bit RSA key")
}

// Request the wrapping key again to ensure it isn't regenerated.
req = &logical.Request{
Storage: s,
Operation: logical.ReadOperation,
Path: "wrapping_key",
}
resp, err = b.HandleRequest(context.Background(), req)
if err != nil {
t.Fatalf("unexpected request error: %s", err)
}
if resp == nil || resp.Data == nil || resp.Data["public_key"] == nil {
t.Fatal("expected non-nil response")
}
if resp.Data["public_key"] != pubKeyPEM {
t.Fatal("wrapping key public component changed between requests")
}
}

0 comments on commit 91e0bbe

Please sign in to comment.