ed25519 support in transit (#2778)
This commit is contained in:
parent
d51b060f17
commit
3eebd5cf5a
|
@ -7,7 +7,7 @@ services:
|
|||
- docker
|
||||
|
||||
go:
|
||||
- 1.8.1
|
||||
- 1.8.3
|
||||
|
||||
matrix:
|
||||
allow_failures:
|
||||
|
|
|
@ -610,7 +610,7 @@ func TestKeyUpgrade(t *testing.T) {
|
|||
if p.Key != nil ||
|
||||
p.Keys == nil ||
|
||||
len(p.Keys) != 1 ||
|
||||
!reflect.DeepEqual(p.Keys[1].AESKey, key) {
|
||||
!reflect.DeepEqual(p.Keys[1].Key, key) {
|
||||
t.Errorf("bad key migration, result is %#v", p.Keys)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -151,7 +151,7 @@ func getExportKey(policy *keysutil.Policy, key *keysutil.KeyEntry, exportType st
|
|||
case exportTypeEncryptionKey:
|
||||
switch policy.Type {
|
||||
case keysutil.KeyType_AES256_GCM96:
|
||||
return strings.TrimSpace(base64.StdEncoding.EncodeToString(key.AESKey)), nil
|
||||
return strings.TrimSpace(base64.StdEncoding.EncodeToString(key.Key)), nil
|
||||
}
|
||||
|
||||
case exportTypeSigningKey:
|
||||
|
@ -162,6 +162,9 @@ func getExportKey(policy *keysutil.Policy, key *keysutil.KeyEntry, exportType st
|
|||
return "", err
|
||||
}
|
||||
return ecKey, nil
|
||||
|
||||
case keysutil.KeyType_ED25519:
|
||||
return strings.TrimSpace(base64.StdEncoding.EncodeToString(key.Key)), nil
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -12,8 +12,10 @@ import (
|
|||
func TestTransit_Export_KeyVersion_ExportsCorrectVersion(t *testing.T) {
|
||||
verifyExportsCorrectVersion(t, "encryption-key", "aes256-gcm96")
|
||||
verifyExportsCorrectVersion(t, "signing-key", "ecdsa-p256")
|
||||
verifyExportsCorrectVersion(t, "signing-key", "ed25519")
|
||||
verifyExportsCorrectVersion(t, "hmac-key", "aes256-gcm96")
|
||||
verifyExportsCorrectVersion(t, "hmac-key", "ecdsa-p256")
|
||||
verifyExportsCorrectVersion(t, "hmac-key", "ed25519")
|
||||
}
|
||||
|
||||
func verifyExportsCorrectVersion(t *testing.T, exportType, keyType string) {
|
||||
|
@ -293,6 +295,11 @@ func TestTransit_Export_SigningDoesNotSupportSigning_ReturnsError(t *testing.T)
|
|||
}
|
||||
|
||||
func TestTransit_Export_EncryptionDoesNotSupportEncryption_ReturnsError(t *testing.T) {
|
||||
testTransit_Export_EncryptionDoesNotSupportEncryption_ReturnsError(t, "ecdsa-p256")
|
||||
testTransit_Export_EncryptionDoesNotSupportEncryption_ReturnsError(t, "ed25519")
|
||||
}
|
||||
|
||||
func testTransit_Export_EncryptionDoesNotSupportEncryption_ReturnsError(t *testing.T, keyType string) {
|
||||
var b *backend
|
||||
sysView := logical.TestSystemView()
|
||||
storage := &logical.InmemStorage{}
|
||||
|
@ -309,7 +316,7 @@ func TestTransit_Export_EncryptionDoesNotSupportEncryption_ReturnsError(t *testi
|
|||
}
|
||||
req.Data = map[string]interface{}{
|
||||
"exportable": true,
|
||||
"type": "ecdsa-p256",
|
||||
"type": keyType,
|
||||
}
|
||||
_, err := b.HandleRequest(req)
|
||||
if err != nil {
|
||||
|
|
|
@ -2,8 +2,12 @@ package transit
|
|||
|
||||
import (
|
||||
"crypto/elliptic"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/ed25519"
|
||||
|
||||
"github.com/hashicorp/vault/helper/keysutil"
|
||||
"github.com/hashicorp/vault/logical"
|
||||
|
@ -36,8 +40,8 @@ func (b *backend) pathKeys() *framework.Path {
|
|||
Type: framework.TypeString,
|
||||
Default: "aes256-gcm96",
|
||||
Description: `The type of key to create. Currently,
|
||||
"aes256-gcm96" (symmetric) and "ecdsa-p256" (asymmetric) are
|
||||
supported. Defaults to "aes256-gcm96".`,
|
||||
"aes256-gcm96" (symmetric) and "ecdsa-p256" (asymmetric), and
|
||||
'ed25519' (asymmetric) are supported. Defaults to "aes256-gcm96".`,
|
||||
},
|
||||
|
||||
"derived": &framework.FieldSchema{
|
||||
|
@ -69,6 +73,14 @@ impact the ciphertext's security.`,
|
|||
This allows for all the valid keys
|
||||
in the key ring to be exported.`,
|
||||
},
|
||||
|
||||
"context": &framework.FieldSchema{
|
||||
Type: framework.TypeString,
|
||||
Description: `Base64 encoded context for key derivation.
|
||||
When reading a key with key derivation enabled,
|
||||
if the key type supports public keys, this will
|
||||
return the public key for the given context.`,
|
||||
},
|
||||
},
|
||||
|
||||
Callbacks: map[logical.Operation]framework.OperationFunc{
|
||||
|
@ -116,6 +128,8 @@ func (b *backend) pathPolicyWrite(
|
|||
polReq.KeyType = keysutil.KeyType_AES256_GCM96
|
||||
case "ecdsa-p256":
|
||||
polReq.KeyType = keysutil.KeyType_ECDSA_P256
|
||||
case "ed25519":
|
||||
polReq.KeyType = keysutil.KeyType_ED25519
|
||||
default:
|
||||
return logical.ErrorResponse(fmt.Sprintf("unknown key type %v", keyType)), logical.ErrInvalidRequest
|
||||
}
|
||||
|
@ -139,6 +153,13 @@ func (b *backend) pathPolicyWrite(
|
|||
return nil, nil
|
||||
}
|
||||
|
||||
// Built-in helper type for returning asymmetric keys
|
||||
type asymKey struct {
|
||||
Name string `json:"name"`
|
||||
PublicKey string `json:"public_key"`
|
||||
CreationTime time.Time `json:"creation_time"`
|
||||
}
|
||||
|
||||
func (b *backend) pathPolicyRead(
|
||||
req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
|
||||
name := d.Get("name").(string)
|
||||
|
@ -185,25 +206,54 @@ func (b *backend) pathPolicyRead(
|
|||
}
|
||||
}
|
||||
|
||||
contextRaw := d.Get("context").(string)
|
||||
var context []byte
|
||||
if len(contextRaw) != 0 {
|
||||
context, err = base64.StdEncoding.DecodeString(contextRaw)
|
||||
if err != nil {
|
||||
return logical.ErrorResponse("failed to base64-decode context"), logical.ErrInvalidRequest
|
||||
}
|
||||
}
|
||||
|
||||
switch p.Type {
|
||||
case keysutil.KeyType_AES256_GCM96:
|
||||
retKeys := map[string]int64{}
|
||||
for k, v := range p.Keys {
|
||||
retKeys[strconv.Itoa(k)] = v.CreationTime
|
||||
retKeys[strconv.Itoa(k)] = v.DeprecatedCreationTime
|
||||
}
|
||||
resp.Data["keys"] = retKeys
|
||||
|
||||
case keysutil.KeyType_ECDSA_P256:
|
||||
type ecdsaKey struct {
|
||||
Name string `json:"name"`
|
||||
PublicKey string `json:"public_key"`
|
||||
}
|
||||
retKeys := map[string]ecdsaKey{}
|
||||
case keysutil.KeyType_ECDSA_P256, keysutil.KeyType_ED25519:
|
||||
retKeys := map[string]asymKey{}
|
||||
for k, v := range p.Keys {
|
||||
retKeys[strconv.Itoa(k)] = ecdsaKey{
|
||||
Name: elliptic.P256().Params().Name,
|
||||
PublicKey: v.FormattedPublicKey,
|
||||
key := asymKey{
|
||||
PublicKey: v.FormattedPublicKey,
|
||||
CreationTime: v.CreationTime,
|
||||
}
|
||||
if key.CreationTime.IsZero() {
|
||||
key.CreationTime = time.Unix(v.DeprecatedCreationTime, 0)
|
||||
}
|
||||
|
||||
switch p.Type {
|
||||
case keysutil.KeyType_ECDSA_P256:
|
||||
key.Name = elliptic.P256().Params().Name
|
||||
case keysutil.KeyType_ED25519:
|
||||
if p.Derived {
|
||||
if len(context) == 0 {
|
||||
key.PublicKey = ""
|
||||
} else {
|
||||
derived, err := p.DeriveKey(context, k)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to derive key to return public component")
|
||||
}
|
||||
pubKey := ed25519.PrivateKey(derived).Public().(ed25519.PublicKey)
|
||||
key.PublicKey = base64.StdEncoding.EncodeToString(pubKey)
|
||||
}
|
||||
}
|
||||
key.Name = "ed25519"
|
||||
}
|
||||
|
||||
retKeys[strconv.Itoa(k)] = key
|
||||
}
|
||||
resp.Data["keys"] = retKeys
|
||||
}
|
||||
|
|
|
@ -26,6 +26,12 @@ func (b *backend) pathSign() *framework.Path {
|
|||
Description: "The base64-encoded input data",
|
||||
},
|
||||
|
||||
"context": &framework.FieldSchema{
|
||||
Type: framework.TypeString,
|
||||
Description: `Base64 encoded context for key derivation. Required if key
|
||||
derivation is enabled; currently only available with ed25519 keys.`,
|
||||
},
|
||||
|
||||
"algorithm": &framework.FieldSchema{
|
||||
Type: framework.TypeString,
|
||||
Default: "sha2-256",
|
||||
|
@ -36,7 +42,7 @@ func (b *backend) pathSign() *framework.Path {
|
|||
* sha2-384
|
||||
* sha2-512
|
||||
|
||||
Defaults to "sha2-256".`,
|
||||
Defaults to "sha2-256". Not valid for all key types.`,
|
||||
},
|
||||
|
||||
"urlalgorithm": &framework.FieldSchema{
|
||||
|
@ -63,6 +69,12 @@ func (b *backend) pathVerify() *framework.Path {
|
|||
Description: "The key to use",
|
||||
},
|
||||
|
||||
"context": &framework.FieldSchema{
|
||||
Type: framework.TypeString,
|
||||
Description: `Base64 encoded context for key derivation. Required if key
|
||||
derivation is enabled; currently only available with ed25519 keys.`,
|
||||
},
|
||||
|
||||
"signature": &framework.FieldSchema{
|
||||
Type: framework.TypeString,
|
||||
Description: "The signature, including vault header/key version",
|
||||
|
@ -93,7 +105,7 @@ func (b *backend) pathVerify() *framework.Path {
|
|||
* sha2-384
|
||||
* sha2-512
|
||||
|
||||
Defaults to "sha2-256".`,
|
||||
Defaults to "sha2-256". Not valid for all key types.`,
|
||||
},
|
||||
},
|
||||
|
||||
|
@ -120,22 +132,6 @@ func (b *backend) pathSignWrite(
|
|||
return logical.ErrorResponse(fmt.Sprintf("unable to decode input as base64: %s", err)), logical.ErrInvalidRequest
|
||||
}
|
||||
|
||||
var hf hash.Hash
|
||||
switch algorithm {
|
||||
case "sha2-224":
|
||||
hf = sha256.New224()
|
||||
case "sha2-256":
|
||||
hf = sha256.New()
|
||||
case "sha2-384":
|
||||
hf = sha512.New384()
|
||||
case "sha2-512":
|
||||
hf = sha512.New()
|
||||
default:
|
||||
return logical.ErrorResponse(fmt.Sprintf("unsupported algorithm %s", algorithm)), nil
|
||||
}
|
||||
hf.Write(input)
|
||||
hashedInput := hf.Sum(nil)
|
||||
|
||||
// Get the policy
|
||||
p, lock, err := b.lm.GetPolicyShared(req.Storage, name)
|
||||
if lock != nil {
|
||||
|
@ -152,20 +148,52 @@ func (b *backend) pathSignWrite(
|
|||
return logical.ErrorResponse(fmt.Sprintf("key type %v does not support signing", p.Type)), logical.ErrInvalidRequest
|
||||
}
|
||||
|
||||
sig, err := p.Sign(hashedInput)
|
||||
contextRaw := d.Get("context").(string)
|
||||
var context []byte
|
||||
if len(contextRaw) != 0 {
|
||||
context, err = base64.StdEncoding.DecodeString(contextRaw)
|
||||
if err != nil {
|
||||
return logical.ErrorResponse("failed to base64-decode context"), logical.ErrInvalidRequest
|
||||
}
|
||||
}
|
||||
|
||||
if p.Type.HashSignatureInput() {
|
||||
var hf hash.Hash
|
||||
switch algorithm {
|
||||
case "sha2-224":
|
||||
hf = sha256.New224()
|
||||
case "sha2-256":
|
||||
hf = sha256.New()
|
||||
case "sha2-384":
|
||||
hf = sha512.New384()
|
||||
case "sha2-512":
|
||||
hf = sha512.New()
|
||||
default:
|
||||
return logical.ErrorResponse(fmt.Sprintf("unsupported algorithm %s", algorithm)), nil
|
||||
}
|
||||
hf.Write(input)
|
||||
input = hf.Sum(nil)
|
||||
}
|
||||
|
||||
sig, err := p.Sign(context, input)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if sig == "" {
|
||||
if sig == nil {
|
||||
return nil, fmt.Errorf("signature could not be computed")
|
||||
}
|
||||
|
||||
// Generate the response
|
||||
resp := &logical.Response{
|
||||
Data: map[string]interface{}{
|
||||
"signature": sig,
|
||||
"signature": sig.Signature,
|
||||
},
|
||||
}
|
||||
|
||||
if len(sig.PublicKey) > 0 {
|
||||
resp.Data["public_key"] = sig.PublicKey
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
|
@ -197,22 +225,6 @@ func (b *backend) pathVerifyWrite(
|
|||
return logical.ErrorResponse(fmt.Sprintf("unable to decode input as base64: %s", err)), logical.ErrInvalidRequest
|
||||
}
|
||||
|
||||
var hf hash.Hash
|
||||
switch algorithm {
|
||||
case "sha2-224":
|
||||
hf = sha256.New224()
|
||||
case "sha2-256":
|
||||
hf = sha256.New()
|
||||
case "sha2-384":
|
||||
hf = sha512.New384()
|
||||
case "sha2-512":
|
||||
hf = sha512.New()
|
||||
default:
|
||||
return logical.ErrorResponse(fmt.Sprintf("unsupported algorithm %s", algorithm)), nil
|
||||
}
|
||||
hf.Write(input)
|
||||
hashedInput := hf.Sum(nil)
|
||||
|
||||
// Get the policy
|
||||
p, lock, err := b.lm.GetPolicyShared(req.Storage, name)
|
||||
if lock != nil {
|
||||
|
@ -225,7 +237,38 @@ func (b *backend) pathVerifyWrite(
|
|||
return logical.ErrorResponse("encryption key not found"), logical.ErrInvalidRequest
|
||||
}
|
||||
|
||||
valid, err := p.VerifySignature(hashedInput, sig)
|
||||
if !p.Type.SigningSupported() {
|
||||
return logical.ErrorResponse(fmt.Sprintf("key type %v does not support verification", p.Type)), logical.ErrInvalidRequest
|
||||
}
|
||||
|
||||
contextRaw := d.Get("context").(string)
|
||||
var context []byte
|
||||
if len(contextRaw) != 0 {
|
||||
context, err = base64.StdEncoding.DecodeString(contextRaw)
|
||||
if err != nil {
|
||||
return logical.ErrorResponse("failed to base64-decode context"), logical.ErrInvalidRequest
|
||||
}
|
||||
}
|
||||
|
||||
if p.Type.HashSignatureInput() {
|
||||
var hf hash.Hash
|
||||
switch algorithm {
|
||||
case "sha2-224":
|
||||
hf = sha256.New224()
|
||||
case "sha2-256":
|
||||
hf = sha256.New()
|
||||
case "sha2-384":
|
||||
hf = sha512.New384()
|
||||
case "sha2-512":
|
||||
hf = sha512.New()
|
||||
default:
|
||||
return logical.ErrorResponse(fmt.Sprintf("unsupported algorithm %s", algorithm)), nil
|
||||
}
|
||||
hf.Write(input)
|
||||
input = hf.Sum(nil)
|
||||
}
|
||||
|
||||
valid, err := p.VerifySignature(context, input, sig)
|
||||
if err != nil {
|
||||
switch err.(type) {
|
||||
case errutil.UserError:
|
||||
|
|
|
@ -1,12 +1,16 @@
|
|||
package transit
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/crypto/ed25519"
|
||||
|
||||
"github.com/hashicorp/vault/logical"
|
||||
)
|
||||
|
||||
func TestTransit_SignVerify(t *testing.T) {
|
||||
func TestTransit_SignVerify_P256(t *testing.T) {
|
||||
var b *backend
|
||||
sysView := logical.TestSystemView()
|
||||
storage := &logical.InmemStorage{}
|
||||
|
@ -91,7 +95,7 @@ func TestTransit_SignVerify(t *testing.T) {
|
|||
}
|
||||
if errExpected {
|
||||
if !resp.IsError() {
|
||||
t.Fatalf("bad: got error response: %#v", *resp)
|
||||
t.Fatalf("bad: should have gotten error response: %#v", *resp)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
@ -114,7 +118,7 @@ func TestTransit_SignVerify(t *testing.T) {
|
|||
}
|
||||
if errExpected {
|
||||
if resp != nil && !resp.IsError() {
|
||||
t.Fatalf("bad: got error response: %#v", *resp)
|
||||
t.Fatalf("bad: should have gotten error response: %#v", *resp)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
@ -199,3 +203,203 @@ func TestTransit_SignVerify(t *testing.T) {
|
|||
// Now try the v1
|
||||
verifyRequest(req, true, "", v1sig)
|
||||
}
|
||||
|
||||
func TestTransit_SignVerify_ED25519(t *testing.T) {
|
||||
var b *backend
|
||||
sysView := logical.TestSystemView()
|
||||
storage := &logical.InmemStorage{}
|
||||
|
||||
b = Backend(&logical.BackendConfig{
|
||||
StorageView: storage,
|
||||
System: sysView,
|
||||
})
|
||||
|
||||
// First create a key
|
||||
req := &logical.Request{
|
||||
Storage: storage,
|
||||
Operation: logical.UpdateOperation,
|
||||
Path: "keys/foo",
|
||||
Data: map[string]interface{}{
|
||||
"type": "ed25519",
|
||||
},
|
||||
}
|
||||
_, err := b.HandleRequest(req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Now create a derived key"
|
||||
req = &logical.Request{
|
||||
Storage: storage,
|
||||
Operation: logical.UpdateOperation,
|
||||
Path: "keys/bar",
|
||||
Data: map[string]interface{}{
|
||||
"type": "ed25519",
|
||||
"derived": true,
|
||||
},
|
||||
}
|
||||
_, err = b.HandleRequest(req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Get the keys for later
|
||||
fooP, lock, err := b.lm.GetPolicyShared(storage, "foo")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// We don't care as we're the only one using this
|
||||
lock.RUnlock()
|
||||
|
||||
barP, lock, err := b.lm.GetPolicyShared(storage, "bar")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
lock.RUnlock()
|
||||
|
||||
signRequest := func(req *logical.Request, errExpected bool, postpath string) string {
|
||||
// Delete any key that exists in the request
|
||||
delete(req.Data, "public_key")
|
||||
req.Path = "sign/" + postpath
|
||||
resp, err := b.HandleRequest(req)
|
||||
if err != nil && !errExpected {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if resp == nil {
|
||||
t.Fatal("expected non-nil response")
|
||||
}
|
||||
if errExpected {
|
||||
if !resp.IsError() {
|
||||
t.Fatalf("bad: got error response: %#v", *resp)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
if resp.IsError() {
|
||||
t.Fatalf("bad: got error response: %#v", *resp)
|
||||
}
|
||||
value, ok := resp.Data["signature"]
|
||||
if !ok {
|
||||
t.Fatalf("no signature key found in returned data, got resp data %#v", resp.Data)
|
||||
}
|
||||
// memoize any pubic key
|
||||
if key, ok := resp.Data["public_key"]; ok {
|
||||
req.Data["public_key"] = key
|
||||
}
|
||||
return value.(string)
|
||||
}
|
||||
|
||||
verifyRequest := func(req *logical.Request, errExpected bool, postpath, sig string) {
|
||||
req.Path = "verify/" + postpath
|
||||
req.Data["signature"] = sig
|
||||
resp, err := b.HandleRequest(req)
|
||||
if err != nil && !errExpected {
|
||||
t.Fatalf("got error: %v, sig was %v", err, sig)
|
||||
}
|
||||
if errExpected {
|
||||
if resp != nil && !resp.IsError() {
|
||||
t.Fatalf("bad: got error response: %#v", *resp)
|
||||
}
|
||||
return
|
||||
}
|
||||
if resp == nil {
|
||||
t.Fatal("expected non-nil response")
|
||||
}
|
||||
if resp.IsError() {
|
||||
t.Fatalf("bad: got error response: %#v", *resp)
|
||||
}
|
||||
value, ok := resp.Data["valid"]
|
||||
if !ok {
|
||||
t.Fatalf("no valid key found in returned data, got resp data %#v", resp.Data)
|
||||
}
|
||||
if !value.(bool) && !errExpected {
|
||||
t.Fatalf("verification failed; req was %#v, resp is %#v", *req, *resp)
|
||||
}
|
||||
|
||||
if pubKeyRaw, ok := req.Data["public_key"]; ok {
|
||||
input, _ := base64.StdEncoding.DecodeString(req.Data["input"].(string))
|
||||
splitSig := strings.Split(sig, ":")
|
||||
signature, _ := base64.StdEncoding.DecodeString(splitSig[2])
|
||||
if !ed25519.Verify(ed25519.PublicKey(pubKeyRaw.([]byte)), input, signature) && !errExpected {
|
||||
t.Fatal("invalid signature")
|
||||
}
|
||||
|
||||
keyReadReq := &logical.Request{
|
||||
Operation: logical.ReadOperation,
|
||||
Path: "keys/" + postpath,
|
||||
}
|
||||
keyReadResp, err := b.HandleRequest(keyReadReq)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
val := keyReadResp.Data["keys"].(map[string]asymKey)[strings.TrimPrefix(splitSig[1], "v")]
|
||||
if val.PublicKey != "" {
|
||||
t.Fatal("got non-empty public key")
|
||||
}
|
||||
keyReadReq.Data = map[string]interface{}{
|
||||
"context": "abcd",
|
||||
}
|
||||
keyReadResp, err = b.HandleRequest(keyReadReq)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
val = keyReadResp.Data["keys"].(map[string]asymKey)[strings.TrimPrefix(splitSig[1], "v")]
|
||||
if val.PublicKey != base64.StdEncoding.EncodeToString(pubKeyRaw.([]byte)) {
|
||||
t.Fatalf("got incorrect public key; got %q, expected %q", val.PublicKey, pubKeyRaw)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
req.Data = map[string]interface{}{
|
||||
"input": "dGhlIHF1aWNrIGJyb3duIGZveA==",
|
||||
"context": "abcd",
|
||||
}
|
||||
|
||||
// Test defaults
|
||||
sig := signRequest(req, false, "foo")
|
||||
verifyRequest(req, false, "foo", sig)
|
||||
|
||||
sig = signRequest(req, false, "bar")
|
||||
verifyRequest(req, false, "bar", sig)
|
||||
|
||||
// Test a bad signature
|
||||
verifyRequest(req, true, "foo", sig[0:len(sig)-2])
|
||||
verifyRequest(req, true, "bar", sig[0:len(sig)-2])
|
||||
|
||||
v1sig := sig
|
||||
|
||||
// Rotate and set min decryption version
|
||||
err = fooP.Rotate(storage)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = fooP.Rotate(storage)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
fooP.MinDecryptionVersion = 2
|
||||
if err = fooP.Persist(storage); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = barP.Rotate(storage)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = barP.Rotate(storage)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
barP.MinDecryptionVersion = 2
|
||||
if err = barP.Persist(storage); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Make sure signing still works fine
|
||||
sig = signRequest(req, false, "foo")
|
||||
verifyRequest(req, false, "foo", sig)
|
||||
// Now try the v1
|
||||
verifyRequest(req, true, "foo", v1sig)
|
||||
// Repeat with the other key
|
||||
sig = signRequest(req, false, "bar")
|
||||
verifyRequest(req, false, "bar", sig)
|
||||
verifyRequest(req, true, "bar", v1sig)
|
||||
}
|
||||
|
|
|
@ -251,6 +251,11 @@ func (lm *LockManager) getPolicyCommon(req PolicyRequest, lockType bool) (*Polic
|
|||
return nil, nil, false, fmt.Errorf("key derivation and convergent encryption not supported for keys of type %v", KeyType_ECDSA_P256)
|
||||
}
|
||||
|
||||
case KeyType_ED25519:
|
||||
if req.Convergent {
|
||||
return nil, nil, false, fmt.Errorf("convergent encryption not not supported for keys of type %v", KeyType_ED25519)
|
||||
}
|
||||
|
||||
default:
|
||||
return nil, nil, false, fmt.Errorf("unsupported key type %v", req.KeyType)
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package keysutil
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/ecdsa"
|
||||
|
@ -21,6 +22,7 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/ed25519"
|
||||
"golang.org/x/crypto/hkdf"
|
||||
|
||||
uuid "github.com/hashicorp/go-uuid"
|
||||
|
@ -41,10 +43,16 @@ const (
|
|||
const (
|
||||
KeyType_AES256_GCM96 = iota
|
||||
KeyType_ECDSA_P256
|
||||
KeyType_ED25519
|
||||
)
|
||||
|
||||
const ErrTooOld = "ciphertext or signature version is disallowed by policy (too old)"
|
||||
|
||||
type SigningResult struct {
|
||||
Signature string
|
||||
PublicKey []byte
|
||||
}
|
||||
|
||||
type ecdsaSignature struct {
|
||||
R, S *big.Int
|
||||
}
|
||||
|
@ -68,6 +76,14 @@ func (kt KeyType) DecryptionSupported() bool {
|
|||
}
|
||||
|
||||
func (kt KeyType) SigningSupported() bool {
|
||||
switch kt {
|
||||
case KeyType_ECDSA_P256, KeyType_ED25519:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (kt KeyType) HashSignatureInput() bool {
|
||||
switch kt {
|
||||
case KeyType_ECDSA_P256:
|
||||
return true
|
||||
|
@ -77,7 +93,7 @@ func (kt KeyType) SigningSupported() bool {
|
|||
|
||||
func (kt KeyType) DerivationSupported() bool {
|
||||
switch kt {
|
||||
case KeyType_AES256_GCM96:
|
||||
case KeyType_AES256_GCM96, KeyType_ED25519:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
|
@ -89,6 +105,8 @@ func (kt KeyType) String() string {
|
|||
return "aes256-gcm96"
|
||||
case KeyType_ECDSA_P256:
|
||||
return "ecdsa-p256"
|
||||
case KeyType_ED25519:
|
||||
return "ed25519"
|
||||
}
|
||||
|
||||
return "[unknown]"
|
||||
|
@ -96,13 +114,25 @@ func (kt KeyType) String() string {
|
|||
|
||||
// KeyEntry stores the key and metadata
|
||||
type KeyEntry struct {
|
||||
AESKey []byte `json:"key"`
|
||||
HMACKey []byte `json:"hmac_key"`
|
||||
CreationTime int64 `json:"creation_time"`
|
||||
EC_X *big.Int `json:"ec_x"`
|
||||
EC_Y *big.Int `json:"ec_y"`
|
||||
EC_D *big.Int `json:"ec_d"`
|
||||
FormattedPublicKey string `json:"public_key"`
|
||||
// AES or some other kind that is a pure byte slice like ED25519
|
||||
Key []byte `json:"key"`
|
||||
|
||||
// Key used for HMAC functions
|
||||
HMACKey []byte `json:"hmac_key"`
|
||||
|
||||
// Time of creation
|
||||
CreationTime time.Time `json:"time"`
|
||||
|
||||
EC_X *big.Int `json:"ec_x"`
|
||||
EC_Y *big.Int `json:"ec_y"`
|
||||
EC_D *big.Int `json:"ec_d"`
|
||||
|
||||
// The public key in an appropriate format for the type of key
|
||||
FormattedPublicKey string `json:"public_key"`
|
||||
|
||||
// This is deprecated (but still filled) in favor of the value above which
|
||||
// is more precise
|
||||
DeprecatedCreationTime int64 `json:"creation_time"`
|
||||
}
|
||||
|
||||
// keyEntryMap is used to allow JSON marshal/unmarshal
|
||||
|
@ -427,35 +457,53 @@ func (p *Policy) DeriveKey(context []byte, ver int) ([]byte, error) {
|
|||
|
||||
// Fast-path non-derived keys
|
||||
if !p.Derived {
|
||||
return p.Keys[ver].AESKey, nil
|
||||
return p.Keys[ver].Key, nil
|
||||
}
|
||||
|
||||
// Ensure a context is provided
|
||||
if len(context) == 0 {
|
||||
return nil, errutil.UserError{Err: "missing 'context' for key deriviation. The key was created using a derived key, which means additional, per-request information must be included in order to encrypt or decrypt information"}
|
||||
return nil, errutil.UserError{Err: "Missing 'context' for key deriviation. The key was created using a derived key, which means additional, per-request information must be included in order to perform operations with the key."}
|
||||
}
|
||||
|
||||
switch p.KDF {
|
||||
case Kdf_hmac_sha256_counter:
|
||||
prf := kdf.HMACSHA256PRF
|
||||
prfLen := kdf.HMACSHA256PRFLen
|
||||
return kdf.CounterMode(prf, prfLen, p.Keys[ver].AESKey, context, 256)
|
||||
return kdf.CounterMode(prf, prfLen, p.Keys[ver].Key, context, 256)
|
||||
|
||||
case Kdf_hkdf_sha256:
|
||||
reader := hkdf.New(sha256.New, p.Keys[ver].AESKey, nil, context)
|
||||
reader := hkdf.New(sha256.New, p.Keys[ver].Key, nil, context)
|
||||
derBytes := bytes.NewBuffer(nil)
|
||||
derBytes.Grow(32)
|
||||
limReader := &io.LimitedReader{
|
||||
R: reader,
|
||||
N: 32,
|
||||
}
|
||||
n, err := derBytes.ReadFrom(limReader)
|
||||
if err != nil {
|
||||
return nil, errutil.InternalError{Err: fmt.Sprintf("error reading returned derived bytes: %v", err)}
|
||||
|
||||
switch p.Type {
|
||||
case KeyType_AES256_GCM96:
|
||||
n, err := derBytes.ReadFrom(limReader)
|
||||
if err != nil {
|
||||
return nil, errutil.InternalError{Err: fmt.Sprintf("error reading returned derived bytes: %v", err)}
|
||||
}
|
||||
if n != 32 {
|
||||
return nil, errutil.InternalError{Err: fmt.Sprintf("unable to read enough derived bytes, needed 32, got %d", n)}
|
||||
}
|
||||
return derBytes.Bytes(), nil
|
||||
|
||||
case KeyType_ED25519:
|
||||
// We use the limited reader containing the derived bytes as the
|
||||
// "random" input to the generation function
|
||||
_, pri, err := ed25519.GenerateKey(limReader)
|
||||
if err != nil {
|
||||
return nil, errutil.InternalError{Err: fmt.Sprintf("error generating derived key: %v", err)}
|
||||
}
|
||||
return pri, nil
|
||||
|
||||
default:
|
||||
return nil, errutil.InternalError{Err: "unsupported key type for derivation"}
|
||||
}
|
||||
if n != 32 {
|
||||
return nil, errutil.InternalError{Err: fmt.Sprintf("unable to read enough derived bytes, needed 32, got %d", n)}
|
||||
}
|
||||
return derBytes.Bytes(), nil
|
||||
|
||||
default:
|
||||
return nil, errutil.InternalError{Err: "unsupported key derivation mode"}
|
||||
}
|
||||
|
@ -645,12 +693,14 @@ func (p *Policy) HMACKey(version int) ([]byte, error) {
|
|||
return p.Keys[version].HMACKey, nil
|
||||
}
|
||||
|
||||
func (p *Policy) Sign(hashedInput []byte) (string, error) {
|
||||
func (p *Policy) Sign(context, input []byte) (*SigningResult, error) {
|
||||
if !p.Type.SigningSupported() {
|
||||
return "", fmt.Errorf("message signing not supported for key type %v", p.Type)
|
||||
return nil, fmt.Errorf("message signing not supported for key type %v", p.Type)
|
||||
}
|
||||
|
||||
var sig []byte
|
||||
var pubKey []byte
|
||||
var err error
|
||||
switch p.Type {
|
||||
case KeyType_ECDSA_P256:
|
||||
keyParams := p.Keys[p.LatestVersion]
|
||||
|
@ -662,33 +712,57 @@ func (p *Policy) Sign(hashedInput []byte) (string, error) {
|
|||
},
|
||||
D: keyParams.EC_D,
|
||||
}
|
||||
r, s, err := ecdsa.Sign(rand.Reader, key, hashedInput)
|
||||
r, s, err := ecdsa.Sign(rand.Reader, key, input)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return nil, err
|
||||
}
|
||||
marshaledSig, err := asn1.Marshal(ecdsaSignature{
|
||||
R: r,
|
||||
S: s,
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
return nil, err
|
||||
}
|
||||
sig = marshaledSig
|
||||
|
||||
case KeyType_ED25519:
|
||||
var key ed25519.PrivateKey
|
||||
|
||||
if p.Derived {
|
||||
// Derive the key that should be used
|
||||
var err error
|
||||
key, err = p.DeriveKey(context, p.LatestVersion)
|
||||
if err != nil {
|
||||
return nil, errutil.InternalError{Err: fmt.Sprintf("error deriving key: %v", err)}
|
||||
}
|
||||
pubKey = key.Public().(ed25519.PublicKey)
|
||||
} else {
|
||||
key = ed25519.PrivateKey(p.Keys[p.LatestVersion].Key)
|
||||
}
|
||||
|
||||
// Per docs, do not pre-hash ed25519; it does two passes and performs
|
||||
// its own hashing
|
||||
sig, err = key.Sign(rand.Reader, input, crypto.Hash(0))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
default:
|
||||
return "", fmt.Errorf("unsupported key type %v", p.Type)
|
||||
return nil, fmt.Errorf("unsupported key type %v", p.Type)
|
||||
}
|
||||
|
||||
// Convert to base64
|
||||
encoded := base64.StdEncoding.EncodeToString(sig)
|
||||
|
||||
// Prepend some information
|
||||
encoded = "vault:v" + strconv.Itoa(p.LatestVersion) + ":" + encoded
|
||||
res := &SigningResult{
|
||||
Signature: "vault:v" + strconv.Itoa(p.LatestVersion) + ":" + encoded,
|
||||
PublicKey: pubKey,
|
||||
}
|
||||
|
||||
return encoded, nil
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (p *Policy) VerifySignature(hashedInput []byte, sig string) (bool, error) {
|
||||
func (p *Policy) VerifySignature(context, input []byte, sig string) (bool, error) {
|
||||
if !p.Type.SigningSupported() {
|
||||
return false, errutil.UserError{Err: fmt.Sprintf("message verification not supported for key type %v", p.Type)}
|
||||
}
|
||||
|
@ -716,15 +790,15 @@ func (p *Policy) VerifySignature(hashedInput []byte, sig string) (bool, error) {
|
|||
return false, errutil.UserError{Err: ErrTooOld}
|
||||
}
|
||||
|
||||
sigBytes, err := base64.StdEncoding.DecodeString(splitVerSig[1])
|
||||
if err != nil {
|
||||
return false, errutil.UserError{Err: "invalid base64 signature value"}
|
||||
}
|
||||
|
||||
switch p.Type {
|
||||
case KeyType_ECDSA_P256:
|
||||
asn1Sig, err := base64.StdEncoding.DecodeString(splitVerSig[1])
|
||||
if err != nil {
|
||||
return false, errutil.UserError{Err: "invalid base64 signature value"}
|
||||
}
|
||||
|
||||
var ecdsaSig ecdsaSignature
|
||||
rest, err := asn1.Unmarshal(asn1Sig, &ecdsaSig)
|
||||
rest, err := asn1.Unmarshal(sigBytes, &ecdsaSig)
|
||||
if err != nil {
|
||||
return false, errutil.UserError{Err: "supplied signature is invalid"}
|
||||
}
|
||||
|
@ -739,7 +813,24 @@ func (p *Policy) VerifySignature(hashedInput []byte, sig string) (bool, error) {
|
|||
Y: keyParams.EC_Y,
|
||||
}
|
||||
|
||||
return ecdsa.Verify(key, hashedInput, ecdsaSig.R, ecdsaSig.S), nil
|
||||
return ecdsa.Verify(key, input, ecdsaSig.R, ecdsaSig.S), nil
|
||||
|
||||
case KeyType_ED25519:
|
||||
var key ed25519.PrivateKey
|
||||
|
||||
if p.Derived {
|
||||
// Derive the key that should be used
|
||||
var err error
|
||||
key, err = p.DeriveKey(context, ver)
|
||||
if err != nil {
|
||||
return false, errutil.InternalError{Err: fmt.Sprintf("error deriving key: %v", err)}
|
||||
}
|
||||
} else {
|
||||
key = ed25519.PrivateKey(p.Keys[ver].Key)
|
||||
}
|
||||
|
||||
return ed25519.Verify(key.Public().(ed25519.PublicKey), input, sigBytes), nil
|
||||
|
||||
default:
|
||||
return false, errutil.InternalError{Err: fmt.Sprintf("unsupported key type %v", p.Type)}
|
||||
}
|
||||
|
@ -756,8 +847,10 @@ func (p *Policy) Rotate(storage logical.Storage) error {
|
|||
}
|
||||
|
||||
p.LatestVersion += 1
|
||||
now := time.Now()
|
||||
entry := KeyEntry{
|
||||
CreationTime: time.Now().Unix(),
|
||||
CreationTime: now,
|
||||
DeprecatedCreationTime: now.Unix(),
|
||||
}
|
||||
|
||||
hmacKey, err := uuid.GenerateRandomBytes(32)
|
||||
|
@ -773,7 +866,7 @@ func (p *Policy) Rotate(storage logical.Storage) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
entry.AESKey = newKey
|
||||
entry.Key = newKey
|
||||
|
||||
case KeyType_ECDSA_P256:
|
||||
privKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
|
@ -796,6 +889,14 @@ func (p *Policy) Rotate(storage logical.Storage) error {
|
|||
return fmt.Errorf("error PEM-encoding public key")
|
||||
}
|
||||
entry.FormattedPublicKey = string(pemBytes)
|
||||
|
||||
case KeyType_ED25519:
|
||||
pub, pri, err := ed25519.GenerateKey(rand.Reader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
entry.Key = pri
|
||||
entry.FormattedPublicKey = base64.StdEncoding.EncodeToString(pub)
|
||||
}
|
||||
|
||||
p.Keys[p.LatestVersion] = entry
|
||||
|
@ -811,10 +912,12 @@ func (p *Policy) Rotate(storage logical.Storage) error {
|
|||
}
|
||||
|
||||
func (p *Policy) MigrateKeyToKeysMap() {
|
||||
now := time.Now()
|
||||
p.Keys = keyEntryMap{
|
||||
1: KeyEntry{
|
||||
AESKey: p.Key,
|
||||
CreationTime: time.Now().Unix(),
|
||||
Key: p.Key,
|
||||
CreationTime: now,
|
||||
DeprecatedCreationTime: now.Unix(),
|
||||
},
|
||||
}
|
||||
p.Key = nil
|
||||
|
|
|
@ -40,10 +40,10 @@ func testKeyUpgradeCommon(t *testing.T, lm *LockManager) {
|
|||
t.Fatal("expected an upsert")
|
||||
}
|
||||
|
||||
testBytes := make([]byte, len(p.Keys[1].AESKey))
|
||||
copy(testBytes, p.Keys[1].AESKey)
|
||||
testBytes := make([]byte, len(p.Keys[1].Key))
|
||||
copy(testBytes, p.Keys[1].Key)
|
||||
|
||||
p.Key = p.Keys[1].AESKey
|
||||
p.Key = p.Keys[1].Key
|
||||
p.Keys = nil
|
||||
p.MigrateKeyToKeysMap()
|
||||
if p.Key != nil {
|
||||
|
@ -52,7 +52,7 @@ func testKeyUpgradeCommon(t *testing.T, lm *LockManager) {
|
|||
if len(p.Keys) != 1 {
|
||||
t.Fatal("policy.Keys is the wrong size")
|
||||
}
|
||||
if !reflect.DeepEqual(testBytes, p.Keys[1].AESKey) {
|
||||
if !reflect.DeepEqual(testBytes, p.Keys[1].Key) {
|
||||
t.Fatal("key mismatch")
|
||||
}
|
||||
}
|
||||
|
@ -198,7 +198,8 @@ func Test_Archiving(t *testing.T) {
|
|||
func testArchivingCommon(t *testing.T, lm *LockManager) {
|
||||
resetKeysArchive()
|
||||
|
||||
// First, we generate a policy and rotate it a number of times. Each time // we'll ensure that we have the expected number of keys in the archive and
|
||||
// First, we generate a policy and rotate it a number of times. Each time
|
||||
// we'll ensure that we have the expected number of keys in the archive and
|
||||
// the main keys object, which without changing the min version should be
|
||||
// zero and latest, respectively
|
||||
|
||||
|
@ -330,14 +331,21 @@ func checkKeys(t *testing.T,
|
|||
}
|
||||
|
||||
for i := p.MinDecryptionVersion; i <= p.LatestVersion; i++ {
|
||||
// Travis has weird time zone issues and gets super unhappy
|
||||
if !p.Keys[i].CreationTime.Equal(keysArchive[i].CreationTime) {
|
||||
t.Fatalf("key %d not equivalent between policy keys and test keys archive; policy keys:\n%#v\ntest keys archive:\n%#v\n", i, p.Keys[i], keysArchive[i])
|
||||
}
|
||||
polKey := p.Keys[i]
|
||||
polKey.CreationTime = keysArchive[i].CreationTime
|
||||
p.Keys[i] = polKey
|
||||
if !reflect.DeepEqual(p.Keys[i], keysArchive[i]) {
|
||||
t.Fatalf("key %d not equivalent between policy keys and test keys archive", i)
|
||||
t.Fatalf("key %d not equivalent between policy keys and test keys archive; policy keys:\n%#v\ntest keys archive:\n%#v\n", i, p.Keys[i], keysArchive[i])
|
||||
}
|
||||
}
|
||||
|
||||
for i := 1; i < len(archive.Keys); i++ {
|
||||
if !reflect.DeepEqual(archive.Keys[i].AESKey, keysArchive[i].AESKey) {
|
||||
t.Fatalf("key %d not equivalent between policy archive and test keys archive", i)
|
||||
if !reflect.DeepEqual(archive.Keys[i].Key, keysArchive[i].Key) {
|
||||
t.Fatalf("key %d not equivalent between policy archive and test keys archive; policy archive:\n%#v\ntest keys archive:\n%#v\n", i, archive.Keys[i].Key, keysArchive[i].Key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue