ed25519 support in transit (#2778)

This commit is contained in:
Jeff Mitchell 2017-06-05 15:00:39 -04:00 committed by GitHub
parent d51b060f17
commit 3eebd5cf5a
10 changed files with 528 additions and 105 deletions

View File

@ -7,7 +7,7 @@ services:
- docker
go:
- 1.8.1
- 1.8.3
matrix:
allow_failures:

View File

@ -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)
}
}

View File

@ -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
}
}

View File

@ -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 {

View File

@ -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
}

View File

@ -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:

View File

@ -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)
}

View File

@ -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)
}

View File

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

View File

@ -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)
}
}
}