-
Notifications
You must be signed in to change notification settings - Fork 4.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add Transit BYOK wrapping key endpoint (#15271)
* 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
1 parent
d8bbaf0
commit 91e0bbe
Showing
3 changed files
with
154 additions
and
0 deletions.
There are no files selected for viewing
This file contains 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 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,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." | ||
) |
This file contains 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 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") | ||
} | ||
} |