client/shares/shares.go (131 lines of code) (raw):
// Copyright 2021 Google LLC
//
// 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 shares contains functions for processing DEK shares.
package shares
import (
"bytes"
"fmt"
"github.com/GoogleCloudPlatform/stet/client/internal/secret_sharing/finitefield"
"github.com/GoogleCloudPlatform/stet/client/internal/secret_sharing/secrets"
"github.com/GoogleCloudPlatform/stet/client/internal/secret_sharing/shamir"
configpb "github.com/GoogleCloudPlatform/stet/proto/config_go_proto"
"github.com/google/tink/go/subtle/random"
"crypto/sha256"
)
// DEKBytes is the size of the DEK in bytes.
const DEKBytes uint32 = 32
// DEK represents a byte array that serves as a Data Encryption Key.
type DEK [DEKBytes]byte
// NewDEK randomly generates and returns a DEK.
func NewDEK() DEK {
var dek DEK
copy(dek[:DEKBytes], random.GetRandomBytes(DEKBytes))
return dek
}
// UnwrappedShare represents an unwrapped share and its associated external URI.
type UnwrappedShare struct {
Share []byte
URI string
}
// HashShare performs a SHA-256 hash on the provided share.
func HashShare(share []byte) []byte {
hash := sha256.Sum256(share)
return hash[:]
}
// ValidateShare performs HashShare on the provided share, then returns whether
// the result is equal to the provided hash.
func ValidateShare(share []byte, expectedHash []byte) bool {
actualHash := HashShare(share)
return bytes.Equal(actualHash[:], expectedHash[:])
}
func convertToByteShares(shares []secrets.Share) [][]byte {
byteShares := make([][]byte, 0, len(shares))
for _, share := range shares {
// Hashicorp/vault appends the X value to the end of each share.
// We have to do this manually for backwards compatibility.
shareWithX := append(share.Value, byte(share.X))
byteShares = append(byteShares, shareWithX)
}
return byteShares
}
// SplitShares takes a DEK as `data`, and returns a slice of byte slices, each representing
// one of the n shares.
func SplitShares(data []byte, numShares, threshold int) ([][]byte, error) {
// Validate data length is DEKBytes.
if len(data) != int(DEKBytes) {
return nil, fmt.Errorf("data has length %v, expected %v", len(data), DEKBytes)
}
md := secrets.Metadata{
Field: finitefield.GF8,
NumShares: numShares,
Threshold: threshold,
}
split, err := shamir.SplitSecret(md, data)
if err != nil {
return nil, fmt.Errorf("error splitting secret: %v", err)
}
// Validate the returned data.
if split.SecretLen != int(DEKBytes) {
return nil, fmt.Errorf("split indicates secret has length %v, expected %v", split.SecretLen, DEKBytes)
}
return convertToByteShares(split.Shares), nil
}
func convertToSecretShares(unwrappedShares []UnwrappedShare) []secrets.Share {
secretShares := make([]secrets.Share, 0, len(unwrappedShares))
for _, unwrapped := range unwrappedShares {
share := unwrapped.Share
// Split each share into the value and the X field (last byte).
secretShare := secrets.Share{
Value: share[:len(share)-1],
X: int(share[len(share)-1]),
}
secretShares = append(secretShares, secretShare)
}
return secretShares
}
// CombineShares takes a list of shares and reconstitutes the original data. Note that this does not
// guarantee the shares are correct (SSS will succeed at "reconstructing" data from
// even faulty shares), so integrity checks are done separately.
func CombineShares(shares []UnwrappedShare, numShares, threshold int) ([]byte, error) {
secretShares := convertToSecretShares(shares)
split := secrets.Split{
SecretLen: int(DEKBytes),
Metadata: secrets.Metadata{
Field: finitefield.GF8,
NumShares: numShares,
Threshold: threshold,
},
Shares: secretShares,
}
return shamir.Reconstruct(split)
}
// CreateDEKShares generates a DEK and - if applicable - splits it into shares.
func CreateDEKShares(dek DEK, keyCfg *configpb.KeyConfig) ([][]byte, error) {
var shares [][]byte
// Depending on the key splitting algorithm given in the KeyConfig, take
// the DEK and split it, wrapping the resulting shares and writing them
// back to the `Shares` field of `metadata`.
switch keyCfg.KeySplittingAlgorithm.(type) {
// Don't split the DEK.
case *configpb.KeyConfig_NoSplit:
if len(keyCfg.GetKekInfos()) != 1 {
return nil, fmt.Errorf("invalid Encrypt configuration, number of KekInfos is %v but expected 1 for 'no split' option", len(keyCfg.GetKekInfos()))
}
shares = [][]byte{dek[:]}
// Split DEK with Shamir's Secret Sharing.
case *configpb.KeyConfig_Shamir:
shamirConfig := keyCfg.GetShamir()
shamirShares := int(shamirConfig.GetShares())
shamirThreshold := int(shamirConfig.GetThreshold())
// The number of KEK Infos should match the number of shares to generate
if len(keyCfg.GetKekInfos()) != shamirShares {
return nil, fmt.Errorf("invalid Encrypt configuration, number of KEK Infos does not match the number of shares to generate: found %v KEK Infos, %v shares", len(keyCfg.GetKekInfos()), shamirShares)
}
var err error
shares, err = SplitShares(dek[:], shamirShares, shamirThreshold)
if err != nil {
return nil, fmt.Errorf("error splitting encryption key: %v", err)
}
default:
return nil, fmt.Errorf("unknown key splitting algorithm")
}
return shares, nil
}
// CombineUnwrappedShares reconstitutes and returns the DEK from the provided shares.
func CombineUnwrappedShares(keyCfg *configpb.KeyConfig, unwrappedShares []UnwrappedShare) ([]byte, error) {
// Reconstitute DEK.
var combinedShares []byte
switch keyCfg.KeySplittingAlgorithm.(type) {
// DEK wasn't split, so combined shares is just the sole share.
case *configpb.KeyConfig_NoSplit:
if len(unwrappedShares) != 1 {
return nil, fmt.Errorf("number of unwrapped shares is %v but expected 1 for 'no split' option", len(unwrappedShares))
}
combinedShares = unwrappedShares[0].Share
// Reverse Shamir's Secret Sharing to reconstitute the whole DEK.
case *configpb.KeyConfig_Shamir:
if len(unwrappedShares) < int(keyCfg.GetShamir().GetThreshold()) {
return nil, fmt.Errorf("only successfully unwrapped %v shares, which is fewer than threshold of %v", len(unwrappedShares), keyCfg.GetShamir().GetThreshold())
}
var err error
combinedShares, err = CombineShares(unwrappedShares, int(keyCfg.GetShamir().GetShares()), int(keyCfg.GetShamir().GetThreshold()))
if err != nil {
return nil, fmt.Errorf("Error combining DEK shares: %v", err)
}
default:
return nil, fmt.Errorf("Unknown key splitting algorithm")
}
if len(combinedShares) != int(DEKBytes) {
return nil, fmt.Errorf("Reconstituted DEK has the wrong length")
}
return combinedShares, nil
}