Escape the JSON keys+values from the context. We do not add the HTML escapes, since that is an extra escape level not mandatory for JSON.
233 lines
6.5 KiB
Go
233 lines
6.5 KiB
Go
// MinIO Cloud Storage, (C) 2015, 2016, 2017, 2018 MinIO, Inc.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package crypto
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"crypto/hmac"
|
|
"crypto/rand"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"sort"
|
|
|
|
"github.com/minio/minio/cmd/logger"
|
|
sha256 "github.com/minio/sha256-simd"
|
|
"github.com/minio/sio"
|
|
)
|
|
|
|
// Context is a list of key-value pairs cryptographically
|
|
// associated with a certain object.
|
|
type Context map[string]string
|
|
|
|
// WriteTo writes the context in a canonical from to w.
|
|
// It returns the number of bytes and the first error
|
|
// 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 {
|
|
buf := bytes.NewBuffer(make([]byte, 0, len(s)))
|
|
EscapeStringJSON(buf, s)
|
|
return buf.String()
|
|
}
|
|
|
|
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 {
|
|
return append(dst, '{', '}')
|
|
}
|
|
|
|
// out should not escape.
|
|
out := bytes.NewBuffer(dst)
|
|
|
|
// No need to copy+sort
|
|
if len(c) == 1 {
|
|
for k, v := range c {
|
|
out.WriteString(`{"`)
|
|
EscapeStringJSON(out, k)
|
|
out.WriteString(`":"`)
|
|
EscapeStringJSON(out, v)
|
|
out.WriteString(`"}`)
|
|
}
|
|
return out.Bytes()
|
|
}
|
|
|
|
sortedKeys := make([]string, 0, len(c))
|
|
for k := range c {
|
|
sortedKeys = append(sortedKeys, k)
|
|
}
|
|
sort.Strings(sortedKeys)
|
|
|
|
out.WriteByte('{')
|
|
for i, k := range sortedKeys {
|
|
out.WriteByte('"')
|
|
EscapeStringJSON(out, k)
|
|
out.WriteString(`":"`)
|
|
EscapeStringJSON(out, c[k])
|
|
out.WriteByte('"')
|
|
if i < len(sortedKeys)-1 {
|
|
out.WriteByte(',')
|
|
}
|
|
}
|
|
out.WriteByte('}')
|
|
return out.Bytes()
|
|
}
|
|
|
|
// KMS represents an active and authenticted connection
|
|
// to a Key-Management-Service. It supports generating
|
|
// data key generation and unsealing of KMS-generated
|
|
// data keys.
|
|
type KMS interface {
|
|
// DefaultKeyID returns the default master key ID. It should be
|
|
// used for SSE-S3 and whenever a S3 client requests SSE-KMS but
|
|
// does not specify an explicit SSE-KMS key ID.
|
|
DefaultKeyID() string
|
|
|
|
// CreateKey creates a new master key with the given key ID
|
|
// at the KMS.
|
|
CreateKey(keyID string) error
|
|
|
|
// GenerateKey generates a new random data key using
|
|
// the master key referenced by the keyID. It returns
|
|
// the plaintext key and the sealed plaintext key
|
|
// on success.
|
|
//
|
|
// The context is cryptographically bound to the
|
|
// generated key. The same context must be provided
|
|
// again to unseal the generated key.
|
|
GenerateKey(keyID string, context Context) (key [32]byte, sealedKey []byte, err error)
|
|
|
|
// UnsealKey unseals the sealedKey using the master key
|
|
// referenced by the keyID. The provided context must
|
|
// match the context used to generate the sealed key.
|
|
UnsealKey(keyID string, sealedKey []byte, context Context) (key [32]byte, err error)
|
|
|
|
// Info returns descriptive information about the KMS,
|
|
// like the default key ID and authentication method.
|
|
Info() KMSInfo
|
|
}
|
|
|
|
type masterKeyKMS struct {
|
|
keyID string
|
|
masterKey [32]byte
|
|
}
|
|
|
|
// KMSInfo contains some describing information about
|
|
// the KMS.
|
|
type KMSInfo struct {
|
|
Endpoints []string
|
|
Name string
|
|
AuthType string
|
|
}
|
|
|
|
// NewMasterKey returns a basic KMS implementation from a single 256 bit master key.
|
|
//
|
|
// The KMS accepts any keyID but binds the keyID and context cryptographically
|
|
// to the generated keys.
|
|
func NewMasterKey(keyID string, key [32]byte) KMS { return &masterKeyKMS{keyID: keyID, masterKey: key} }
|
|
|
|
func (kms *masterKeyKMS) DefaultKeyID() string {
|
|
return kms.keyID
|
|
}
|
|
|
|
func (kms *masterKeyKMS) CreateKey(keyID string) error {
|
|
return errors.New("crypto: creating keys is not supported by a static master key")
|
|
}
|
|
|
|
func (kms *masterKeyKMS) GenerateKey(keyID string, ctx Context) (key [32]byte, sealedKey []byte, err error) {
|
|
if _, err = io.ReadFull(rand.Reader, key[:]); err != nil {
|
|
logger.CriticalIf(context.Background(), errOutOfEntropy)
|
|
}
|
|
|
|
var (
|
|
buffer bytes.Buffer
|
|
derivedKey = kms.deriveKey(keyID, ctx)
|
|
)
|
|
if n, err := sio.Encrypt(&buffer, bytes.NewReader(key[:]), sio.Config{Key: derivedKey[:]}); err != nil || n != 64 {
|
|
logger.CriticalIf(context.Background(), errors.New("KMS: unable to encrypt data key"))
|
|
}
|
|
sealedKey = buffer.Bytes()
|
|
return key, sealedKey, nil
|
|
}
|
|
|
|
// KMS is configured directly using master key
|
|
func (kms *masterKeyKMS) Info() (info KMSInfo) {
|
|
return KMSInfo{
|
|
Endpoints: []string{},
|
|
Name: "",
|
|
AuthType: "master-key",
|
|
}
|
|
}
|
|
|
|
func (kms *masterKeyKMS) UnsealKey(keyID string, sealedKey []byte, ctx Context) (key [32]byte, err error) {
|
|
var (
|
|
derivedKey = kms.deriveKey(keyID, ctx)
|
|
)
|
|
out, err := sio.DecryptBuffer(key[:0], sealedKey, sio.Config{Key: derivedKey[:]})
|
|
if err != nil || len(out) != 32 {
|
|
return key, err // TODO(aead): upgrade sio to use sio.Error
|
|
}
|
|
return key, nil
|
|
}
|
|
|
|
func (kms *masterKeyKMS) deriveKey(keyID string, context Context) (key [32]byte) {
|
|
if context == nil {
|
|
context = Context{}
|
|
}
|
|
mac := hmac.New(sha256.New, kms.masterKey[:])
|
|
mac.Write([]byte(keyID))
|
|
mac.Write(context.AppendTo(make([]byte, 0, 128)))
|
|
mac.Sum(key[:0])
|
|
return key
|
|
}
|