mirror of
https://github.com/minio/minio.git
synced 2025-04-19 02:05:24 -04:00
crypto: simplify Context encoding (#11812)
This commit adds a `MarshalText` implementation to the `crypto.Context` type. The `MarshalText` implementation replaces the `WriteTo` and `AppendTo` implementation. It is slightly slower than the `AppendTo` implementation ``` goos: darwin goarch: arm64 pkg: github.com/minio/minio/cmd/crypto BenchmarkContext_AppendTo/0-elems-8 381475698 2.892 ns/op 0 B/op 0 allocs/op BenchmarkContext_AppendTo/1-elems-8 17945088 67.54 ns/op 0 B/op 0 allocs/op BenchmarkContext_AppendTo/3-elems-8 5431770 221.2 ns/op 72 B/op 2 allocs/op BenchmarkContext_AppendTo/4-elems-8 3430684 346.7 ns/op 88 B/op 2 allocs/op ``` vs. ``` BenchmarkContext/0-elems-8 135819834 8.658 ns/op 2 B/op 1 allocs/op BenchmarkContext/1-elems-8 13326243 89.20 ns/op 128 B/op 1 allocs/op BenchmarkContext/3-elems-8 4935301 243.1 ns/op 200 B/op 3 allocs/op BenchmarkContext/4-elems-8 2792142 428.2 ns/op 504 B/op 4 allocs/op goos: darwin ``` However, the `AppendTo` benchmark used a pre-allocated buffer. While this improves its performance it does not match the actual usage of `crypto.Context` which is passed to a `KMS` and always encoded into a newly allocated buffer. Therefore, this change seems acceptable since it should not impact the actual performance but reduces the overall code for Context marshaling.
This commit is contained in:
parent
9a6487319a
commit
7b3719c17b
@ -81,8 +81,6 @@ var (
|
|||||||
|
|
||||||
errInvalidInternalIV = Errorf("The internal encryption IV is malformed")
|
errInvalidInternalIV = Errorf("The internal encryption IV is malformed")
|
||||||
errInvalidInternalSealAlgorithm = Errorf("The internal seal algorithm is invalid and not supported")
|
errInvalidInternalSealAlgorithm = Errorf("The internal seal algorithm is invalid and not supported")
|
||||||
|
|
||||||
errMissingUpdatedKey = Errorf("The key update returned no error but also no sealed key")
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -181,7 +181,10 @@ func (kes *kesService) CreateKey(keyID string) error { return kes.client.CreateK
|
|||||||
// named key referenced by keyID. It also binds the generated key
|
// named key referenced by keyID. It also binds the generated key
|
||||||
// cryptographically to the provided context.
|
// cryptographically to the provided context.
|
||||||
func (kes *kesService) GenerateKey(keyID string, ctx Context) (key [32]byte, sealedKey []byte, err error) {
|
func (kes *kesService) GenerateKey(keyID string, ctx Context) (key [32]byte, sealedKey []byte, err error) {
|
||||||
context := ctx.AppendTo(make([]byte, 0, 128))
|
context, err := ctx.MarshalText()
|
||||||
|
if err != nil {
|
||||||
|
return key, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
var plainKey []byte
|
var plainKey []byte
|
||||||
plainKey, sealedKey, err = kes.client.GenerateDataKey(keyID, context)
|
plainKey, sealedKey, err = kes.client.GenerateDataKey(keyID, context)
|
||||||
@ -203,7 +206,10 @@ func (kes *kesService) GenerateKey(keyID string, ctx Context) (key [32]byte, sea
|
|||||||
// The context must be same context as the one provided while
|
// The context must be same context as the one provided while
|
||||||
// generating the plaintext key / sealedKey.
|
// generating the plaintext key / sealedKey.
|
||||||
func (kes *kesService) UnsealKey(keyID string, sealedKey []byte, ctx Context) (key [32]byte, err error) {
|
func (kes *kesService) UnsealKey(keyID string, sealedKey []byte, ctx Context) (key [32]byte, err error) {
|
||||||
context := ctx.AppendTo(make([]byte, 0, 128))
|
context, err := ctx.MarshalText()
|
||||||
|
if err != nil {
|
||||||
|
return key, err
|
||||||
|
}
|
||||||
|
|
||||||
var plainKey []byte
|
var plainKey []byte
|
||||||
plainKey, err = kes.client.DecryptDataKey(keyID, sealedKey, context)
|
plainKey, err = kes.client.DecryptDataKey(keyID, sealedKey, context)
|
||||||
|
@ -21,7 +21,6 @@ import (
|
|||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"io"
|
"io"
|
||||||
"sort"
|
"sort"
|
||||||
|
|
||||||
@ -33,74 +32,29 @@ import (
|
|||||||
// associated with a certain object.
|
// associated with a certain object.
|
||||||
type Context map[string]string
|
type Context map[string]string
|
||||||
|
|
||||||
// WriteTo writes the context in a canonical from to w.
|
// MarshalText returns a canonical text representation of
|
||||||
// It returns the number of bytes and the first error
|
// the Context.
|
||||||
// encounter during writing to w, if any.
|
|
||||||
//
|
|
||||||
// WriteTo sorts the context keys and writes the sorted
|
|
||||||
// key-value pairs as canonical JSON object to w.
|
|
||||||
// Sort order is based on the un-escaped keys.
|
|
||||||
//
|
|
||||||
// Note that neither keys nor values are escaped for JSON.
|
|
||||||
func (c Context) WriteTo(w io.Writer) (n int64, err error) {
|
|
||||||
sortedKeys := make(sort.StringSlice, 0, len(c))
|
|
||||||
for k := range c {
|
|
||||||
sortedKeys = append(sortedKeys, k)
|
|
||||||
}
|
|
||||||
sort.Sort(sortedKeys)
|
|
||||||
|
|
||||||
escape := func(s string) string {
|
// MarshalText sorts the context keys and writes the sorted
|
||||||
buf := bytes.NewBuffer(make([]byte, 0, len(s)))
|
// key-value pairs as canonical JSON object. The sort order
|
||||||
EscapeStringJSON(buf, s)
|
// is based on the un-escaped keys.
|
||||||
return buf.String()
|
func (c Context) MarshalText() ([]byte, error) {
|
||||||
}
|
|
||||||
|
|
||||||
nn, err := io.WriteString(w, "{")
|
|
||||||
if err != nil {
|
|
||||||
return n + int64(nn), err
|
|
||||||
}
|
|
||||||
n += int64(nn)
|
|
||||||
for i, k := range sortedKeys {
|
|
||||||
s := fmt.Sprintf("\"%s\":\"%s\",", escape(k), escape(c[k]))
|
|
||||||
if i == len(sortedKeys)-1 {
|
|
||||||
s = s[:len(s)-1] // remove last ','
|
|
||||||
}
|
|
||||||
|
|
||||||
nn, err = io.WriteString(w, s)
|
|
||||||
if err != nil {
|
|
||||||
return n + int64(nn), err
|
|
||||||
}
|
|
||||||
n += int64(nn)
|
|
||||||
}
|
|
||||||
nn, err = io.WriteString(w, "}")
|
|
||||||
return n + int64(nn), err
|
|
||||||
}
|
|
||||||
|
|
||||||
// AppendTo appends the context in a canonical from to dst.
|
|
||||||
//
|
|
||||||
// AppendTo sorts the context keys and writes the sorted
|
|
||||||
// key-value pairs as canonical JSON object to w.
|
|
||||||
// Sort order is based on the un-escaped keys.
|
|
||||||
//
|
|
||||||
// Note that neither keys nor values are escaped for JSON.
|
|
||||||
func (c Context) AppendTo(dst []byte) (output []byte) {
|
|
||||||
if len(c) == 0 {
|
if len(c) == 0 {
|
||||||
return append(dst, '{', '}')
|
return []byte{'{', '}'}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// out should not escape.
|
// Pre-allocate a buffer - 128 bytes is an arbitrary
|
||||||
out := bytes.NewBuffer(dst)
|
// heuristic value that seems like a good starting size.
|
||||||
|
var b = bytes.NewBuffer(make([]byte, 0, 128))
|
||||||
// No need to copy+sort
|
|
||||||
if len(c) == 1 {
|
if len(c) == 1 {
|
||||||
for k, v := range c {
|
for k, v := range c {
|
||||||
out.WriteString(`{"`)
|
b.WriteString(`{"`)
|
||||||
EscapeStringJSON(out, k)
|
EscapeStringJSON(b, k)
|
||||||
out.WriteString(`":"`)
|
b.WriteString(`":"`)
|
||||||
EscapeStringJSON(out, v)
|
EscapeStringJSON(b, v)
|
||||||
out.WriteString(`"}`)
|
b.WriteString(`"}`)
|
||||||
}
|
}
|
||||||
return out.Bytes()
|
return b.Bytes(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
sortedKeys := make([]string, 0, len(c))
|
sortedKeys := make([]string, 0, len(c))
|
||||||
@ -109,19 +63,19 @@ func (c Context) AppendTo(dst []byte) (output []byte) {
|
|||||||
}
|
}
|
||||||
sort.Strings(sortedKeys)
|
sort.Strings(sortedKeys)
|
||||||
|
|
||||||
out.WriteByte('{')
|
b.WriteByte('{')
|
||||||
for i, k := range sortedKeys {
|
for i, k := range sortedKeys {
|
||||||
out.WriteByte('"')
|
b.WriteByte('"')
|
||||||
EscapeStringJSON(out, k)
|
EscapeStringJSON(b, k)
|
||||||
out.WriteString(`":"`)
|
b.WriteString(`":"`)
|
||||||
EscapeStringJSON(out, c[k])
|
EscapeStringJSON(b, c[k])
|
||||||
out.WriteByte('"')
|
b.WriteByte('"')
|
||||||
if i < len(sortedKeys)-1 {
|
if i < len(sortedKeys)-1 {
|
||||||
out.WriteByte(',')
|
b.WriteByte(',')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
out.WriteByte('}')
|
b.WriteByte('}')
|
||||||
return out.Bytes()
|
return b.Bytes(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// KMS represents an active and authenticted connection
|
// KMS represents an active and authenticted connection
|
||||||
@ -225,9 +179,11 @@ func (kms *masterKeyKMS) deriveKey(keyID string, context Context) (key [32]byte)
|
|||||||
if context == nil {
|
if context == nil {
|
||||||
context = Context{}
|
context = Context{}
|
||||||
}
|
}
|
||||||
|
ctxBytes, _ := context.MarshalText()
|
||||||
|
|
||||||
mac := hmac.New(sha256.New, kms.masterKey[:])
|
mac := hmac.New(sha256.New, kms.masterKey[:])
|
||||||
mac.Write([]byte(keyID))
|
mac.Write([]byte(keyID))
|
||||||
mac.Write(context.AppendTo(make([]byte, 0, 128)))
|
mac.Write(ctxBytes)
|
||||||
mac.Sum(key[:0])
|
mac.Sum(key[:0])
|
||||||
return key
|
return key
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,6 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"path"
|
"path"
|
||||||
"strings"
|
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -61,7 +60,7 @@ func TestMasterKeyKMS(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var contextWriteToTests = []struct {
|
var contextMarshalTextTests = []struct {
|
||||||
Context Context
|
Context Context
|
||||||
ExpectedJSON string
|
ExpectedJSON string
|
||||||
}{
|
}{
|
||||||
@ -76,43 +75,29 @@ var contextWriteToTests = []struct {
|
|||||||
6: {Context: Context{"a": "<>&"}, ExpectedJSON: `{"a":"\u003c\u003e\u0026"}`},
|
6: {Context: Context{"a": "<>&"}, ExpectedJSON: `{"a":"\u003c\u003e\u0026"}`},
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestContextWriteTo(t *testing.T) {
|
func TestContextMarshalText(t *testing.T) {
|
||||||
for i, test := range contextWriteToTests {
|
for i, test := range contextMarshalTextTests {
|
||||||
var jsonContext strings.Builder
|
text, err := test.Context.MarshalText()
|
||||||
if _, err := test.Context.WriteTo(&jsonContext); err != nil {
|
if err != nil {
|
||||||
t.Errorf("Test %d: Failed to encode context: %v", i, err)
|
t.Fatalf("Test %d: Failed to encode context: %v", i, err)
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
if s := jsonContext.String(); s != test.ExpectedJSON {
|
if string(text) != test.ExpectedJSON {
|
||||||
t.Errorf("Test %d: JSON representation differ - got: '%s' want: '%s'", i, s, test.ExpectedJSON)
|
t.Errorf("Test %d: JSON representation differ - got: '%s' want: '%s'", i, string(text), test.ExpectedJSON)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestContextAppendTo(t *testing.T) {
|
func BenchmarkContext(b *testing.B) {
|
||||||
for i, test := range contextWriteToTests {
|
|
||||||
dst := make([]byte, 0, 1024)
|
|
||||||
dst = test.Context.AppendTo(dst)
|
|
||||||
if s := string(dst); s != test.ExpectedJSON {
|
|
||||||
t.Errorf("Test %d: JSON representation differ - got: '%s' want: '%s'", i, s, test.ExpectedJSON)
|
|
||||||
}
|
|
||||||
// Append one more
|
|
||||||
dst = test.Context.AppendTo(dst)
|
|
||||||
if s := string(dst); s != test.ExpectedJSON+test.ExpectedJSON {
|
|
||||||
t.Errorf("Test %d: JSON representation differ - got: '%s' want: '%s'", i, s, test.ExpectedJSON+test.ExpectedJSON)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkContext_AppendTo(b *testing.B) {
|
|
||||||
tests := []Context{{}, {"bucket": "warp-benchmark-bucket"}, {"0": "1", "-": "2", ".": "#"}, {"34trg": "dfioutr89", "ikjfdghkjf": "jkedfhgfjkhg", "sdfhsdjkh": "if88889", "asddsirfh804": "kjfdshgdfuhgfg78-45604586#$%<>&"}}
|
tests := []Context{{}, {"bucket": "warp-benchmark-bucket"}, {"0": "1", "-": "2", ".": "#"}, {"34trg": "dfioutr89", "ikjfdghkjf": "jkedfhgfjkhg", "sdfhsdjkh": "if88889", "asddsirfh804": "kjfdshgdfuhgfg78-45604586#$%<>&"}}
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
b.Run(fmt.Sprintf("%d-elems", len(test)), func(b *testing.B) {
|
b.Run(fmt.Sprintf("%d-elems", len(test)), func(b *testing.B) {
|
||||||
dst := make([]byte, 0, 1024)
|
|
||||||
b.ReportAllocs()
|
b.ReportAllocs()
|
||||||
b.ResetTimer()
|
b.ResetTimer()
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
dst = test.AppendTo(dst[:0])
|
_, err := test.MarshalText()
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -223,7 +223,10 @@ func (v *vaultService) CreateKey(keyID string) error {
|
|||||||
// named key referenced by keyID. It also binds the generated key
|
// named key referenced by keyID. It also binds the generated key
|
||||||
// cryptographically to the provided context.
|
// cryptographically to the provided context.
|
||||||
func (v *vaultService) GenerateKey(keyID string, ctx Context) (key [32]byte, sealedKey []byte, err error) {
|
func (v *vaultService) GenerateKey(keyID string, ctx Context) (key [32]byte, sealedKey []byte, err error) {
|
||||||
context := ctx.AppendTo(make([]byte, 0, 128))
|
context, err := ctx.MarshalText()
|
||||||
|
if err != nil {
|
||||||
|
return key, sealedKey, err
|
||||||
|
}
|
||||||
|
|
||||||
payload := map[string]interface{}{
|
payload := map[string]interface{}{
|
||||||
"context": base64.StdEncoding.EncodeToString(context),
|
"context": base64.StdEncoding.EncodeToString(context),
|
||||||
@ -258,7 +261,10 @@ func (v *vaultService) GenerateKey(keyID string, ctx Context) (key [32]byte, sea
|
|||||||
// The context must be same context as the one provided while
|
// The context must be same context as the one provided while
|
||||||
// generating the plaintext key / sealedKey.
|
// generating the plaintext key / sealedKey.
|
||||||
func (v *vaultService) UnsealKey(keyID string, sealedKey []byte, ctx Context) (key [32]byte, err error) {
|
func (v *vaultService) UnsealKey(keyID string, sealedKey []byte, ctx Context) (key [32]byte, err error) {
|
||||||
context := ctx.AppendTo(make([]byte, 0, 128))
|
context, err := ctx.MarshalText()
|
||||||
|
if err != nil {
|
||||||
|
return key, err
|
||||||
|
}
|
||||||
|
|
||||||
payload := map[string]interface{}{
|
payload := map[string]interface{}{
|
||||||
"ciphertext": string(sealedKey),
|
"ciphertext": string(sealedKey),
|
||||||
@ -282,29 +288,3 @@ func (v *vaultService) UnsealKey(keyID string, sealedKey []byte, ctx Context) (k
|
|||||||
copy(key[:], plainKey)
|
copy(key[:], plainKey)
|
||||||
return key, nil
|
return key, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateKey re-wraps the sealedKey if the master key referenced by the keyID
|
|
||||||
// has been changed by the KMS operator - i.e. the master key has been rotated.
|
|
||||||
// If the master key hasn't changed since the sealedKey has been created / updated
|
|
||||||
// it may return the same sealedKey as rotatedKey.
|
|
||||||
//
|
|
||||||
// The context must be same context as the one provided while
|
|
||||||
// generating the plaintext key / sealedKey.
|
|
||||||
func (v *vaultService) UpdateKey(keyID string, sealedKey []byte, ctx Context) (rotatedKey []byte, err error) {
|
|
||||||
context := ctx.AppendTo(make([]byte, 0, 128))
|
|
||||||
|
|
||||||
payload := map[string]interface{}{
|
|
||||||
"ciphertext": string(sealedKey),
|
|
||||||
"context": base64.StdEncoding.EncodeToString(context),
|
|
||||||
}
|
|
||||||
s, err := v.client.Logical().Write(fmt.Sprintf("/transit/rewrap/%s", keyID), payload)
|
|
||||||
if err != nil {
|
|
||||||
return nil, Errorf("crypto: client error %w", err)
|
|
||||||
}
|
|
||||||
ciphertext, ok := s.Data["ciphertext"]
|
|
||||||
if !ok {
|
|
||||||
return nil, errMissingUpdatedKey
|
|
||||||
}
|
|
||||||
rotatedKey = []byte(ciphertext.(string))
|
|
||||||
return rotatedKey, nil
|
|
||||||
}
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user