611ab91e5a
* add import endpoint * fix unlock * add import_version * refactor import endpoints and add tests * add descriptions * Update dependencies to include tink for Transit import operations. Convert Transit wrapping key endpoint to use shared wrapping key retrieval method. Disallow import of convergent keys to Transit via BYOK process. * Include new 'hash_function' parameter on Transit import endpoints to specify OAEP random oracle hash function used to wrap ephemeral AES key. * Add default values for Transit import endpoint fields. Prevent an OOB panic in Transit import. Proactively zero out ephemeral AES key used in Transit imports. * Rename some Transit BYOK import variables. Ensure Transit BYOK ephemeral key is of the size specified byt the RFC. * Add unit tests for Transit BYOK import endpoint. * Simplify Transit BYOK import tests. Add a conditional on auto rotation to avoid errors on BYOK keys with allow_rotation=false. * Added hash_function field to Transit import_version endpoint. Reworked Transit import unit tests. Added unit tests for Transit import_version endpoint. * Add changelog entry for Transit BYOK. * Transit BYOK formatting fixes. * Omit 'convergent_encryption' field from Transit BYOK import endpoint, but reject with an error when the field is provided. * Minor formatting fix in Transit import. Co-authored-by: rculpepper <rculpepper@hashicorp.com>
1849 lines
50 KiB
Go
1849 lines
50 KiB
Go
package keysutil
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"crypto"
|
|
"crypto/aes"
|
|
"crypto/cipher"
|
|
"crypto/ecdsa"
|
|
"crypto/elliptic"
|
|
"crypto/hmac"
|
|
"crypto/rand"
|
|
"crypto/rsa"
|
|
"crypto/sha256"
|
|
"crypto/x509"
|
|
"encoding/asn1"
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"encoding/pem"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"math/big"
|
|
"path"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
"sync/atomic"
|
|
"time"
|
|
|
|
"golang.org/x/crypto/chacha20poly1305"
|
|
"golang.org/x/crypto/ed25519"
|
|
"golang.org/x/crypto/hkdf"
|
|
|
|
"github.com/hashicorp/errwrap"
|
|
uuid "github.com/hashicorp/go-uuid"
|
|
"github.com/hashicorp/vault/sdk/helper/errutil"
|
|
"github.com/hashicorp/vault/sdk/helper/jsonutil"
|
|
"github.com/hashicorp/vault/sdk/helper/kdf"
|
|
"github.com/hashicorp/vault/sdk/logical"
|
|
)
|
|
|
|
// Careful with iota; don't put anything before it in this const block because
|
|
// we need the default of zero to be the old-style KDF
|
|
const (
|
|
Kdf_hmac_sha256_counter = iota // built-in helper
|
|
Kdf_hkdf_sha256 // golang.org/x/crypto/hkdf
|
|
)
|
|
|
|
// Or this one...we need the default of zero to be the original AES256-GCM96
|
|
const (
|
|
KeyType_AES256_GCM96 = iota
|
|
KeyType_ECDSA_P256
|
|
KeyType_ED25519
|
|
KeyType_RSA2048
|
|
KeyType_RSA4096
|
|
KeyType_ChaCha20_Poly1305
|
|
KeyType_ECDSA_P384
|
|
KeyType_ECDSA_P521
|
|
KeyType_AES128_GCM96
|
|
KeyType_RSA3072
|
|
)
|
|
|
|
const (
|
|
// ErrTooOld is returned whtn the ciphertext or signatures's key version is
|
|
// too old.
|
|
ErrTooOld = "ciphertext or signature version is disallowed by policy (too old)"
|
|
|
|
// DefaultVersionTemplate is used when no version template is provided.
|
|
DefaultVersionTemplate = "vault:v{{version}}:"
|
|
)
|
|
|
|
type RestoreInfo struct {
|
|
Time time.Time `json:"time"`
|
|
Version int `json:"version"`
|
|
}
|
|
|
|
type BackupInfo struct {
|
|
Time time.Time `json:"time"`
|
|
Version int `json:"version"`
|
|
}
|
|
|
|
type SigningResult struct {
|
|
Signature string
|
|
PublicKey []byte
|
|
}
|
|
|
|
type ecdsaSignature struct {
|
|
R, S *big.Int
|
|
}
|
|
|
|
type KeyType int
|
|
|
|
func (kt KeyType) EncryptionSupported() bool {
|
|
switch kt {
|
|
case KeyType_AES128_GCM96, KeyType_AES256_GCM96, KeyType_ChaCha20_Poly1305, KeyType_RSA2048, KeyType_RSA3072, KeyType_RSA4096:
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (kt KeyType) DecryptionSupported() bool {
|
|
switch kt {
|
|
case KeyType_AES128_GCM96, KeyType_AES256_GCM96, KeyType_ChaCha20_Poly1305, KeyType_RSA2048, KeyType_RSA3072, KeyType_RSA4096:
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (kt KeyType) SigningSupported() bool {
|
|
switch kt {
|
|
case KeyType_ECDSA_P256, KeyType_ECDSA_P384, KeyType_ECDSA_P521, KeyType_ED25519, KeyType_RSA2048, KeyType_RSA3072, KeyType_RSA4096:
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (kt KeyType) HashSignatureInput() bool {
|
|
switch kt {
|
|
case KeyType_ECDSA_P256, KeyType_ECDSA_P384, KeyType_ECDSA_P521, KeyType_RSA2048, KeyType_RSA3072, KeyType_RSA4096:
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (kt KeyType) DerivationSupported() bool {
|
|
switch kt {
|
|
case KeyType_AES128_GCM96, KeyType_AES256_GCM96, KeyType_ChaCha20_Poly1305, KeyType_ED25519:
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (kt KeyType) String() string {
|
|
switch kt {
|
|
case KeyType_AES128_GCM96:
|
|
return "aes128-gcm96"
|
|
case KeyType_AES256_GCM96:
|
|
return "aes256-gcm96"
|
|
case KeyType_ChaCha20_Poly1305:
|
|
return "chacha20-poly1305"
|
|
case KeyType_ECDSA_P256:
|
|
return "ecdsa-p256"
|
|
case KeyType_ECDSA_P384:
|
|
return "ecdsa-p384"
|
|
case KeyType_ECDSA_P521:
|
|
return "ecdsa-p521"
|
|
case KeyType_ED25519:
|
|
return "ed25519"
|
|
case KeyType_RSA2048:
|
|
return "rsa-2048"
|
|
case KeyType_RSA3072:
|
|
return "rsa-3072"
|
|
case KeyType_RSA4096:
|
|
return "rsa-4096"
|
|
}
|
|
|
|
return "[unknown]"
|
|
}
|
|
|
|
type KeyData struct {
|
|
Policy *Policy `json:"policy"`
|
|
ArchivedKeys *archivedKeys `json:"archived_keys"`
|
|
}
|
|
|
|
// KeyEntry stores the key and metadata
|
|
type KeyEntry struct {
|
|
// 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"`
|
|
|
|
RSAKey *rsa.PrivateKey `json:"rsa_key"`
|
|
|
|
// The public key in an appropriate format for the type of key
|
|
FormattedPublicKey string `json:"public_key"`
|
|
|
|
// If convergent is enabled, the version (falling back to what's in the
|
|
// policy)
|
|
ConvergentVersion int `json:"convergent_version"`
|
|
|
|
// This is deprecated (but still filled) in favor of the value above which
|
|
// is more precise
|
|
DeprecatedCreationTime int64 `json:"creation_time"`
|
|
}
|
|
|
|
// deprecatedKeyEntryMap is used to allow JSON marshal/unmarshal
|
|
type deprecatedKeyEntryMap map[int]KeyEntry
|
|
|
|
// MarshalJSON implements JSON marshaling
|
|
func (kem deprecatedKeyEntryMap) MarshalJSON() ([]byte, error) {
|
|
intermediate := map[string]KeyEntry{}
|
|
for k, v := range kem {
|
|
intermediate[strconv.Itoa(k)] = v
|
|
}
|
|
return json.Marshal(&intermediate)
|
|
}
|
|
|
|
// MarshalJSON implements JSON unmarshalling
|
|
func (kem deprecatedKeyEntryMap) UnmarshalJSON(data []byte) error {
|
|
intermediate := map[string]KeyEntry{}
|
|
if err := jsonutil.DecodeJSON(data, &intermediate); err != nil {
|
|
return err
|
|
}
|
|
for k, v := range intermediate {
|
|
keyval, err := strconv.Atoi(k)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
kem[keyval] = v
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// keyEntryMap is used to allow JSON marshal/unmarshal
|
|
type keyEntryMap map[string]KeyEntry
|
|
|
|
// PolicyConfig is used to create a new policy
|
|
type PolicyConfig struct {
|
|
// The name of the policy
|
|
Name string `json:"name"`
|
|
|
|
// The type of key
|
|
Type KeyType
|
|
|
|
// Derived keys MUST provide a context and the master underlying key is
|
|
// never used.
|
|
Derived bool
|
|
KDF int
|
|
ConvergentEncryption bool
|
|
|
|
// Whether the key is exportable
|
|
Exportable bool
|
|
|
|
// Whether the key is allowed to be deleted
|
|
DeletionAllowed bool
|
|
|
|
// AllowPlaintextBackup allows taking backup of the policy in plaintext
|
|
AllowPlaintextBackup bool
|
|
|
|
// VersionTemplate is used to prefix the ciphertext with information about
|
|
// the key version. It must inclide {{version}} and a delimiter between the
|
|
// version prefix and the ciphertext.
|
|
VersionTemplate string
|
|
|
|
// StoragePrefix is used to add a prefix when storing and retrieving the
|
|
// policy object.
|
|
StoragePrefix string
|
|
}
|
|
|
|
// NewPolicy takes a policy config and returns a Policy with those settings.
|
|
func NewPolicy(config PolicyConfig) *Policy {
|
|
return &Policy{
|
|
l: new(sync.RWMutex),
|
|
Name: config.Name,
|
|
Type: config.Type,
|
|
Derived: config.Derived,
|
|
KDF: config.KDF,
|
|
ConvergentEncryption: config.ConvergentEncryption,
|
|
ConvergentVersion: -1,
|
|
Exportable: config.Exportable,
|
|
DeletionAllowed: config.DeletionAllowed,
|
|
AllowPlaintextBackup: config.AllowPlaintextBackup,
|
|
VersionTemplate: config.VersionTemplate,
|
|
StoragePrefix: config.StoragePrefix,
|
|
}
|
|
}
|
|
|
|
// LoadPolicy will load a policy from the provided storage path and set the
|
|
// necessary un-exported variables. It is particularly useful when accessing a
|
|
// policy without the lock manager.
|
|
func LoadPolicy(ctx context.Context, s logical.Storage, path string) (*Policy, error) {
|
|
raw, err := s.Get(ctx, path)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if raw == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
var policy Policy
|
|
err = jsonutil.DecodeJSON(raw.Value, &policy)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
policy.l = new(sync.RWMutex)
|
|
|
|
return &policy, nil
|
|
}
|
|
|
|
// Policy is the struct used to store metadata
|
|
type Policy struct {
|
|
// This is a pointer on purpose: if we are running with cache disabled we
|
|
// need to actually swap in the lock manager's lock for this policy with
|
|
// the local lock.
|
|
l *sync.RWMutex
|
|
// writeLocked allows us to implement Lock() and Unlock()
|
|
writeLocked bool
|
|
// Stores whether it's been deleted. This acts as a guard for operations
|
|
// that may write data, e.g. if one request rotates and that request is
|
|
// served after a delete.
|
|
deleted uint32
|
|
|
|
Name string `json:"name"`
|
|
Key []byte `json:"key,omitempty"` // DEPRECATED
|
|
Keys keyEntryMap `json:"keys"`
|
|
|
|
// Derived keys MUST provide a context and the master underlying key is
|
|
// never used. If convergent encryption is true, the context will be used
|
|
// as the nonce as well.
|
|
Derived bool `json:"derived"`
|
|
KDF int `json:"kdf"`
|
|
ConvergentEncryption bool `json:"convergent_encryption"`
|
|
|
|
// Whether the key is exportable
|
|
Exportable bool `json:"exportable"`
|
|
|
|
// The minimum version of the key allowed to be used for decryption
|
|
MinDecryptionVersion int `json:"min_decryption_version"`
|
|
|
|
// The minimum version of the key allowed to be used for encryption
|
|
MinEncryptionVersion int `json:"min_encryption_version"`
|
|
|
|
// The latest key version in this policy
|
|
LatestVersion int `json:"latest_version"`
|
|
|
|
// The latest key version in the archive. We never delete these, so this is
|
|
// a max.
|
|
ArchiveVersion int `json:"archive_version"`
|
|
|
|
// ArchiveMinVersion is the minimum version of the key in the archive.
|
|
ArchiveMinVersion int `json:"archive_min_version"`
|
|
|
|
// MinAvailableVersion is the minimum version of the key present. All key
|
|
// versions before this would have been deleted.
|
|
MinAvailableVersion int `json:"min_available_version"`
|
|
|
|
// Whether the key is allowed to be deleted
|
|
DeletionAllowed bool `json:"deletion_allowed"`
|
|
|
|
// The version of the convergent nonce to use
|
|
ConvergentVersion int `json:"convergent_version"`
|
|
|
|
// The type of key
|
|
Type KeyType `json:"type"`
|
|
|
|
// BackupInfo indicates the information about the backup action taken on
|
|
// this policy
|
|
BackupInfo *BackupInfo `json:"backup_info"`
|
|
|
|
// RestoreInfo indicates the information about the restore action taken on
|
|
// this policy
|
|
RestoreInfo *RestoreInfo `json:"restore_info"`
|
|
|
|
// AllowPlaintextBackup allows taking backup of the policy in plaintext
|
|
AllowPlaintextBackup bool `json:"allow_plaintext_backup"`
|
|
|
|
// VersionTemplate is used to prefix the ciphertext with information about
|
|
// the key version. It must inclide {{version}} and a delimiter between the
|
|
// version prefix and the ciphertext.
|
|
VersionTemplate string `json:"version_template"`
|
|
|
|
// StoragePrefix is used to add a prefix when storing and retrieving the
|
|
// policy object.
|
|
StoragePrefix string `json:"storage_prefix"`
|
|
|
|
// AutoRotatePeriod defines how frequently the key should automatically
|
|
// rotate. Setting this to zero disables automatic rotation for the key.
|
|
AutoRotatePeriod time.Duration `json:"auto_rotate_period"`
|
|
|
|
// versionPrefixCache stores caches of version prefix strings and the split
|
|
// version template.
|
|
versionPrefixCache sync.Map
|
|
|
|
// Imported indicates whether the key was generated by Vault or imported
|
|
// from an external source
|
|
Imported bool
|
|
|
|
// AllowImportedKeyRotation indicates whether an imported key may be rotated by Vault
|
|
AllowImportedKeyRotation bool
|
|
}
|
|
|
|
func (p *Policy) Lock(exclusive bool) {
|
|
if exclusive {
|
|
p.l.Lock()
|
|
p.writeLocked = true
|
|
} else {
|
|
p.l.RLock()
|
|
}
|
|
}
|
|
|
|
func (p *Policy) Unlock() {
|
|
if p.writeLocked {
|
|
p.writeLocked = false
|
|
p.l.Unlock()
|
|
} else {
|
|
p.l.RUnlock()
|
|
}
|
|
}
|
|
|
|
// ArchivedKeys stores old keys. This is used to keep the key loading time sane
|
|
// when there are huge numbers of rotations.
|
|
type archivedKeys struct {
|
|
Keys []KeyEntry `json:"keys"`
|
|
}
|
|
|
|
func (p *Policy) LoadArchive(ctx context.Context, storage logical.Storage) (*archivedKeys, error) {
|
|
archive := &archivedKeys{}
|
|
|
|
raw, err := storage.Get(ctx, path.Join(p.StoragePrefix, "archive", p.Name))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if raw == nil {
|
|
archive.Keys = make([]KeyEntry, 0)
|
|
return archive, nil
|
|
}
|
|
|
|
if err := jsonutil.DecodeJSON(raw.Value, archive); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return archive, nil
|
|
}
|
|
|
|
func (p *Policy) storeArchive(ctx context.Context, storage logical.Storage, archive *archivedKeys) error {
|
|
// Encode the policy
|
|
buf, err := json.Marshal(archive)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Write the policy into storage
|
|
err = storage.Put(ctx, &logical.StorageEntry{
|
|
Key: path.Join(p.StoragePrefix, "archive", p.Name),
|
|
Value: buf,
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// handleArchiving manages the movement of keys to and from the policy archive.
|
|
// This should *ONLY* be called from Persist() since it assumes that the policy
|
|
// will be persisted afterwards.
|
|
func (p *Policy) handleArchiving(ctx context.Context, storage logical.Storage) error {
|
|
// We need to move keys that are no longer accessible to archivedKeys, and keys
|
|
// that now need to be accessible back here.
|
|
//
|
|
// For safety, because there isn't really a good reason to, we never delete
|
|
// keys from the archive even when we move them back.
|
|
|
|
// Check if we have the latest minimum version in the current set of keys
|
|
_, keysContainsMinimum := p.Keys[strconv.Itoa(p.MinDecryptionVersion)]
|
|
|
|
// Sanity checks
|
|
switch {
|
|
case p.MinDecryptionVersion < 1:
|
|
return fmt.Errorf("minimum decryption version of %d is less than 1", p.MinDecryptionVersion)
|
|
case p.LatestVersion < 1:
|
|
return fmt.Errorf("latest version of %d is less than 1", p.LatestVersion)
|
|
case !keysContainsMinimum && p.ArchiveVersion != p.LatestVersion:
|
|
return fmt.Errorf("need to move keys from archive but archive version not up-to-date")
|
|
case p.ArchiveVersion > p.LatestVersion:
|
|
return fmt.Errorf("archive version of %d is greater than the latest version %d",
|
|
p.ArchiveVersion, p.LatestVersion)
|
|
case p.MinEncryptionVersion > 0 && p.MinEncryptionVersion < p.MinDecryptionVersion:
|
|
return fmt.Errorf("minimum decryption version of %d is greater than minimum encryption version %d",
|
|
p.MinDecryptionVersion, p.MinEncryptionVersion)
|
|
case p.MinDecryptionVersion > p.LatestVersion:
|
|
return fmt.Errorf("minimum decryption version of %d is greater than the latest version %d",
|
|
p.MinDecryptionVersion, p.LatestVersion)
|
|
}
|
|
|
|
archive, err := p.LoadArchive(ctx, storage)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if !keysContainsMinimum {
|
|
// Need to move keys *from* archive
|
|
for i := p.MinDecryptionVersion; i <= p.LatestVersion; i++ {
|
|
p.Keys[strconv.Itoa(i)] = archive.Keys[i-p.MinAvailableVersion]
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Need to move keys *to* archive
|
|
|
|
// We need a size that is equivalent to the latest version (number of keys)
|
|
// but adding one since slice numbering starts at 0 and we're indexing by
|
|
// key version
|
|
if len(archive.Keys)+p.MinAvailableVersion < p.LatestVersion+1 {
|
|
// Increase the size of the archive slice
|
|
newKeys := make([]KeyEntry, p.LatestVersion-p.MinAvailableVersion+1)
|
|
copy(newKeys, archive.Keys)
|
|
archive.Keys = newKeys
|
|
}
|
|
|
|
// We are storing all keys in the archive, so we ensure that it is up to
|
|
// date up to p.LatestVersion
|
|
for i := p.ArchiveVersion + 1; i <= p.LatestVersion; i++ {
|
|
archive.Keys[i-p.MinAvailableVersion] = p.Keys[strconv.Itoa(i)]
|
|
p.ArchiveVersion = i
|
|
}
|
|
|
|
// Trim the keys if required
|
|
if p.ArchiveMinVersion < p.MinAvailableVersion {
|
|
archive.Keys = archive.Keys[p.MinAvailableVersion-p.ArchiveMinVersion:]
|
|
p.ArchiveMinVersion = p.MinAvailableVersion
|
|
}
|
|
|
|
err = p.storeArchive(ctx, storage, archive)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Perform deletion afterwards so that if there is an error saving we
|
|
// haven't messed with the current policy
|
|
for i := p.LatestVersion - len(p.Keys) + 1; i < p.MinDecryptionVersion; i++ {
|
|
delete(p.Keys, strconv.Itoa(i))
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (p *Policy) Persist(ctx context.Context, storage logical.Storage) (retErr error) {
|
|
if atomic.LoadUint32(&p.deleted) == 1 {
|
|
return errors.New("key has been deleted, not persisting")
|
|
}
|
|
|
|
// Other functions will take care of restoring other values; this is just
|
|
// responsible for archiving and keys since the archive function can modify
|
|
// keys. At the moment one of the other functions calling persist will also
|
|
// roll back keys, but better safe than sorry and this doesn't happen
|
|
// enough to worry about the speed tradeoff.
|
|
priorArchiveVersion := p.ArchiveVersion
|
|
var priorKeys keyEntryMap
|
|
|
|
if p.Keys != nil {
|
|
priorKeys = keyEntryMap{}
|
|
for k, v := range p.Keys {
|
|
priorKeys[k] = v
|
|
}
|
|
}
|
|
|
|
defer func() {
|
|
if retErr != nil {
|
|
p.ArchiveVersion = priorArchiveVersion
|
|
p.Keys = priorKeys
|
|
}
|
|
}()
|
|
|
|
err := p.handleArchiving(ctx, storage)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Encode the policy
|
|
buf, err := p.Serialize()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Write the policy into storage
|
|
err = storage.Put(ctx, &logical.StorageEntry{
|
|
Key: path.Join(p.StoragePrefix, "policy", p.Name),
|
|
Value: buf,
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (p *Policy) Serialize() ([]byte, error) {
|
|
return json.Marshal(p)
|
|
}
|
|
|
|
func (p *Policy) NeedsUpgrade() bool {
|
|
// Ensure we've moved from Key -> Keys
|
|
if p.Key != nil && len(p.Key) > 0 {
|
|
return true
|
|
}
|
|
|
|
// With archiving, past assumptions about the length of the keys map are no
|
|
// longer valid
|
|
if p.LatestVersion == 0 && len(p.Keys) != 0 {
|
|
return true
|
|
}
|
|
|
|
// We disallow setting the version to 0, since they start at 1 since moving
|
|
// to rotate-able keys, so update if it's set to 0
|
|
if p.MinDecryptionVersion == 0 {
|
|
return true
|
|
}
|
|
|
|
// On first load after an upgrade, copy keys to the archive
|
|
if p.ArchiveVersion == 0 {
|
|
return true
|
|
}
|
|
|
|
// Need to write the version if zero; for version 3 on we set this to -1 to
|
|
// ignore it since we store this information in each key entry
|
|
if p.ConvergentEncryption && p.ConvergentVersion == 0 {
|
|
return true
|
|
}
|
|
|
|
if p.Keys[strconv.Itoa(p.LatestVersion)].HMACKey == nil || len(p.Keys[strconv.Itoa(p.LatestVersion)].HMACKey) == 0 {
|
|
return true
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
func (p *Policy) Upgrade(ctx context.Context, storage logical.Storage, randReader io.Reader) (retErr error) {
|
|
priorKey := p.Key
|
|
priorLatestVersion := p.LatestVersion
|
|
priorMinDecryptionVersion := p.MinDecryptionVersion
|
|
priorConvergentVersion := p.ConvergentVersion
|
|
var priorKeys keyEntryMap
|
|
|
|
if p.Keys != nil {
|
|
priorKeys = keyEntryMap{}
|
|
for k, v := range p.Keys {
|
|
priorKeys[k] = v
|
|
}
|
|
}
|
|
|
|
defer func() {
|
|
if retErr != nil {
|
|
p.Key = priorKey
|
|
p.LatestVersion = priorLatestVersion
|
|
p.MinDecryptionVersion = priorMinDecryptionVersion
|
|
p.ConvergentVersion = priorConvergentVersion
|
|
p.Keys = priorKeys
|
|
}
|
|
}()
|
|
|
|
persistNeeded := false
|
|
// Ensure we've moved from Key -> Keys
|
|
if p.Key != nil && len(p.Key) > 0 {
|
|
p.MigrateKeyToKeysMap()
|
|
persistNeeded = true
|
|
}
|
|
|
|
// With archiving, past assumptions about the length of the keys map are no
|
|
// longer valid
|
|
if p.LatestVersion == 0 && len(p.Keys) != 0 {
|
|
p.LatestVersion = len(p.Keys)
|
|
persistNeeded = true
|
|
}
|
|
|
|
// We disallow setting the version to 0, since they start at 1 since moving
|
|
// to rotate-able keys, so update if it's set to 0
|
|
if p.MinDecryptionVersion == 0 {
|
|
p.MinDecryptionVersion = 1
|
|
persistNeeded = true
|
|
}
|
|
|
|
// On first load after an upgrade, copy keys to the archive
|
|
if p.ArchiveVersion == 0 {
|
|
persistNeeded = true
|
|
}
|
|
|
|
if p.ConvergentEncryption && p.ConvergentVersion == 0 {
|
|
p.ConvergentVersion = 1
|
|
persistNeeded = true
|
|
}
|
|
|
|
if p.Keys[strconv.Itoa(p.LatestVersion)].HMACKey == nil || len(p.Keys[strconv.Itoa(p.LatestVersion)].HMACKey) == 0 {
|
|
entry := p.Keys[strconv.Itoa(p.LatestVersion)]
|
|
hmacKey, err := uuid.GenerateRandomBytesWithReader(32, randReader)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
entry.HMACKey = hmacKey
|
|
p.Keys[strconv.Itoa(p.LatestVersion)] = entry
|
|
persistNeeded = true
|
|
}
|
|
|
|
if persistNeeded {
|
|
err := p.Persist(ctx, storage)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// GetKey is used to derive the encryption key that should be used depending
|
|
// on the policy. If derivation is disabled the raw key is used and no context
|
|
// is required, otherwise the KDF mode is used with the context to derive the
|
|
// proper key.
|
|
func (p *Policy) GetKey(context []byte, ver, numBytes int) ([]byte, error) {
|
|
// Fast-path non-derived keys
|
|
if !p.Derived {
|
|
keyEntry, err := p.safeGetKeyEntry(ver)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return keyEntry.Key, nil
|
|
}
|
|
|
|
return p.DeriveKey(context, nil, ver, numBytes)
|
|
}
|
|
|
|
// DeriveKey is used to derive a symmetric key given a context and salt. This does not
|
|
// check the policies Derived flag, but just implements the derivation logic. GetKey
|
|
// is responsible for switching on the policy config.
|
|
func (p *Policy) DeriveKey(context, salt []byte, ver int, numBytes int) ([]byte, error) {
|
|
if !p.Type.DerivationSupported() {
|
|
return nil, errutil.UserError{Err: fmt.Sprintf("derivation not supported for key type %v", p.Type)}
|
|
}
|
|
|
|
if p.Keys == nil || p.LatestVersion == 0 {
|
|
return nil, errutil.InternalError{Err: "unable to access the key; no key versions found"}
|
|
}
|
|
|
|
if ver <= 0 || ver > p.LatestVersion {
|
|
return nil, errutil.UserError{Err: "invalid key version"}
|
|
}
|
|
|
|
// Ensure a context is provided
|
|
if len(context) == 0 {
|
|
return nil, errutil.UserError{Err: "missing 'context' for key derivation; 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"}
|
|
}
|
|
|
|
keyEntry, err := p.safeGetKeyEntry(ver)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
switch p.KDF {
|
|
case Kdf_hmac_sha256_counter:
|
|
prf := kdf.HMACSHA256PRF
|
|
prfLen := kdf.HMACSHA256PRFLen
|
|
return kdf.CounterMode(prf, prfLen, keyEntry.Key, append(context, salt...), 256)
|
|
|
|
case Kdf_hkdf_sha256:
|
|
reader := hkdf.New(sha256.New, keyEntry.Key, salt, context)
|
|
derBytes := bytes.NewBuffer(nil)
|
|
derBytes.Grow(numBytes)
|
|
limReader := &io.LimitedReader{
|
|
R: reader,
|
|
N: int64(numBytes),
|
|
}
|
|
|
|
switch p.Type {
|
|
case KeyType_AES128_GCM96, KeyType_AES256_GCM96, KeyType_ChaCha20_Poly1305:
|
|
n, err := derBytes.ReadFrom(limReader)
|
|
if err != nil {
|
|
return nil, errutil.InternalError{Err: fmt.Sprintf("error reading returned derived bytes: %v", err)}
|
|
}
|
|
if n != int64(numBytes) {
|
|
return nil, errutil.InternalError{Err: fmt.Sprintf("unable to read enough derived bytes, needed %d, got %d", numBytes, 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"}
|
|
}
|
|
|
|
default:
|
|
return nil, errutil.InternalError{Err: "unsupported key derivation mode"}
|
|
}
|
|
}
|
|
|
|
func (p *Policy) safeGetKeyEntry(ver int) (KeyEntry, error) {
|
|
keyVerStr := strconv.Itoa(ver)
|
|
keyEntry, ok := p.Keys[keyVerStr]
|
|
if !ok {
|
|
return keyEntry, errutil.UserError{Err: "no such key version"}
|
|
}
|
|
return keyEntry, nil
|
|
}
|
|
|
|
func (p *Policy) convergentVersion(ver int) int {
|
|
if !p.ConvergentEncryption {
|
|
return 0
|
|
}
|
|
|
|
convergentVersion := p.ConvergentVersion
|
|
if convergentVersion == 0 {
|
|
// For some reason, not upgraded yet
|
|
convergentVersion = 1
|
|
}
|
|
currKey := p.Keys[strconv.Itoa(ver)]
|
|
if currKey.ConvergentVersion != 0 {
|
|
convergentVersion = currKey.ConvergentVersion
|
|
}
|
|
|
|
return convergentVersion
|
|
}
|
|
|
|
func (p *Policy) Encrypt(ver int, context, nonce []byte, value string) (string, error) {
|
|
if !p.Type.EncryptionSupported() {
|
|
return "", errutil.UserError{Err: fmt.Sprintf("message encryption not supported for key type %v", p.Type)}
|
|
}
|
|
|
|
// Decode the plaintext value
|
|
plaintext, err := base64.StdEncoding.DecodeString(value)
|
|
if err != nil {
|
|
return "", errutil.UserError{Err: err.Error()}
|
|
}
|
|
|
|
switch {
|
|
case ver == 0:
|
|
ver = p.LatestVersion
|
|
case ver < 0:
|
|
return "", errutil.UserError{Err: "requested version for encryption is negative"}
|
|
case ver > p.LatestVersion:
|
|
return "", errutil.UserError{Err: "requested version for encryption is higher than the latest key version"}
|
|
case ver < p.MinEncryptionVersion:
|
|
return "", errutil.UserError{Err: "requested version for encryption is less than the minimum encryption key version"}
|
|
}
|
|
|
|
var ciphertext []byte
|
|
|
|
switch p.Type {
|
|
case KeyType_AES128_GCM96, KeyType_AES256_GCM96, KeyType_ChaCha20_Poly1305:
|
|
hmacKey := context
|
|
|
|
var encKey []byte
|
|
var deriveHMAC bool
|
|
|
|
encBytes := 32
|
|
hmacBytes := 0
|
|
if p.convergentVersion(ver) > 2 {
|
|
deriveHMAC = true
|
|
hmacBytes = 32
|
|
}
|
|
if p.Type == KeyType_AES128_GCM96 {
|
|
encBytes = 16
|
|
}
|
|
|
|
key, err := p.GetKey(context, ver, encBytes+hmacBytes)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
if len(key) < encBytes+hmacBytes {
|
|
return "", errutil.InternalError{Err: "could not derive key, length too small"}
|
|
}
|
|
|
|
encKey = key[:encBytes]
|
|
if len(encKey) != encBytes {
|
|
return "", errutil.InternalError{Err: "could not derive enc key, length not correct"}
|
|
}
|
|
if deriveHMAC {
|
|
hmacKey = key[encBytes:]
|
|
if len(hmacKey) != hmacBytes {
|
|
return "", errutil.InternalError{Err: "could not derive hmac key, length not correct"}
|
|
}
|
|
}
|
|
|
|
ciphertext, err = p.SymmetricEncryptRaw(ver, encKey, plaintext,
|
|
SymmetricOpts{
|
|
Convergent: p.ConvergentEncryption,
|
|
HMACKey: hmacKey,
|
|
Nonce: nonce,
|
|
})
|
|
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
case KeyType_RSA2048, KeyType_RSA3072, KeyType_RSA4096:
|
|
keyEntry, err := p.safeGetKeyEntry(ver)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
key := keyEntry.RSAKey
|
|
ciphertext, err = rsa.EncryptOAEP(sha256.New(), rand.Reader, &key.PublicKey, plaintext, nil)
|
|
if err != nil {
|
|
return "", errutil.InternalError{Err: fmt.Sprintf("failed to RSA encrypt the plaintext: %v", err)}
|
|
}
|
|
|
|
default:
|
|
return "", errutil.InternalError{Err: fmt.Sprintf("unsupported key type %v", p.Type)}
|
|
}
|
|
|
|
// Convert to base64
|
|
encoded := base64.StdEncoding.EncodeToString(ciphertext)
|
|
|
|
// Prepend some information
|
|
encoded = p.getVersionPrefix(ver) + encoded
|
|
|
|
return encoded, nil
|
|
}
|
|
|
|
func (p *Policy) Decrypt(context, nonce []byte, value string) (string, error) {
|
|
if !p.Type.DecryptionSupported() {
|
|
return "", errutil.UserError{Err: fmt.Sprintf("message decryption not supported for key type %v", p.Type)}
|
|
}
|
|
|
|
tplParts, err := p.getTemplateParts()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
// Verify the prefix
|
|
if !strings.HasPrefix(value, tplParts[0]) {
|
|
return "", errutil.UserError{Err: "invalid ciphertext: no prefix"}
|
|
}
|
|
|
|
splitVerCiphertext := strings.SplitN(strings.TrimPrefix(value, tplParts[0]), tplParts[1], 2)
|
|
if len(splitVerCiphertext) != 2 {
|
|
return "", errutil.UserError{Err: "invalid ciphertext: wrong number of fields"}
|
|
}
|
|
|
|
ver, err := strconv.Atoi(splitVerCiphertext[0])
|
|
if err != nil {
|
|
return "", errutil.UserError{Err: "invalid ciphertext: version number could not be decoded"}
|
|
}
|
|
|
|
if ver == 0 {
|
|
// Compatibility mode with initial implementation, where keys start at
|
|
// zero
|
|
ver = 1
|
|
}
|
|
|
|
if ver > p.LatestVersion {
|
|
return "", errutil.UserError{Err: "invalid ciphertext: version is too new"}
|
|
}
|
|
|
|
if p.MinDecryptionVersion > 0 && ver < p.MinDecryptionVersion {
|
|
return "", errutil.UserError{Err: ErrTooOld}
|
|
}
|
|
|
|
convergentVersion := p.convergentVersion(ver)
|
|
if convergentVersion == 1 && (nonce == nil || len(nonce) == 0) {
|
|
return "", errutil.UserError{Err: "invalid convergent nonce supplied"}
|
|
}
|
|
|
|
// Decode the base64
|
|
decoded, err := base64.StdEncoding.DecodeString(splitVerCiphertext[1])
|
|
if err != nil {
|
|
return "", errutil.UserError{Err: "invalid ciphertext: could not decode base64"}
|
|
}
|
|
|
|
var plain []byte
|
|
|
|
switch p.Type {
|
|
case KeyType_AES128_GCM96, KeyType_AES256_GCM96, KeyType_ChaCha20_Poly1305:
|
|
numBytes := 32
|
|
if p.Type == KeyType_AES128_GCM96 {
|
|
numBytes = 16
|
|
}
|
|
|
|
encKey, err := p.GetKey(context, ver, numBytes)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
if len(encKey) != numBytes {
|
|
return "", errutil.InternalError{Err: "could not derive enc key, length not correct"}
|
|
}
|
|
|
|
plain, err = p.SymmetricDecryptRaw(encKey, decoded,
|
|
SymmetricOpts{
|
|
Convergent: p.ConvergentEncryption,
|
|
ConvergentVersion: p.ConvergentVersion,
|
|
})
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
case KeyType_RSA2048, KeyType_RSA3072, KeyType_RSA4096:
|
|
keyEntry, err := p.safeGetKeyEntry(ver)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
key := keyEntry.RSAKey
|
|
plain, err = rsa.DecryptOAEP(sha256.New(), rand.Reader, key, decoded, nil)
|
|
if err != nil {
|
|
return "", errutil.InternalError{Err: fmt.Sprintf("failed to RSA decrypt the ciphertext: %v", err)}
|
|
}
|
|
|
|
default:
|
|
return "", errutil.InternalError{Err: fmt.Sprintf("unsupported key type %v", p.Type)}
|
|
}
|
|
|
|
return base64.StdEncoding.EncodeToString(plain), nil
|
|
}
|
|
|
|
func (p *Policy) HMACKey(version int) ([]byte, error) {
|
|
switch {
|
|
case version < 0:
|
|
return nil, fmt.Errorf("key version does not exist (cannot be negative)")
|
|
case version > p.LatestVersion:
|
|
return nil, fmt.Errorf("key version does not exist; latest key version is %d", p.LatestVersion)
|
|
}
|
|
keyEntry, err := p.safeGetKeyEntry(version)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if keyEntry.HMACKey == nil {
|
|
return nil, fmt.Errorf("no HMAC key exists for that key version")
|
|
}
|
|
|
|
return keyEntry.HMACKey, nil
|
|
}
|
|
|
|
func (p *Policy) Sign(ver int, context, input []byte, hashAlgorithm HashType, sigAlgorithm string, marshaling MarshalingType) (*SigningResult, error) {
|
|
if !p.Type.SigningSupported() {
|
|
return nil, fmt.Errorf("message signing not supported for key type %v", p.Type)
|
|
}
|
|
|
|
switch {
|
|
case ver == 0:
|
|
ver = p.LatestVersion
|
|
case ver < 0:
|
|
return nil, errutil.UserError{Err: "requested version for signing is negative"}
|
|
case ver > p.LatestVersion:
|
|
return nil, errutil.UserError{Err: "requested version for signing is higher than the latest key version"}
|
|
case p.MinEncryptionVersion > 0 && ver < p.MinEncryptionVersion:
|
|
return nil, errutil.UserError{Err: "requested version for signing is less than the minimum encryption key version"}
|
|
}
|
|
|
|
var sig []byte
|
|
var pubKey []byte
|
|
var err error
|
|
keyParams, err := p.safeGetKeyEntry(ver)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
switch p.Type {
|
|
case KeyType_ECDSA_P256, KeyType_ECDSA_P384, KeyType_ECDSA_P521:
|
|
var curveBits int
|
|
var curve elliptic.Curve
|
|
switch p.Type {
|
|
case KeyType_ECDSA_P384:
|
|
curveBits = 384
|
|
curve = elliptic.P384()
|
|
case KeyType_ECDSA_P521:
|
|
curveBits = 521
|
|
curve = elliptic.P521()
|
|
default:
|
|
curveBits = 256
|
|
curve = elliptic.P256()
|
|
}
|
|
|
|
key := &ecdsa.PrivateKey{
|
|
PublicKey: ecdsa.PublicKey{
|
|
Curve: curve,
|
|
X: keyParams.EC_X,
|
|
Y: keyParams.EC_Y,
|
|
},
|
|
D: keyParams.EC_D,
|
|
}
|
|
|
|
r, s, err := ecdsa.Sign(rand.Reader, key, input)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
switch marshaling {
|
|
case MarshalingTypeASN1:
|
|
// This is used by openssl and X.509
|
|
sig, err = asn1.Marshal(ecdsaSignature{
|
|
R: r,
|
|
S: s,
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
case MarshalingTypeJWS:
|
|
// This is used by JWS
|
|
|
|
// First we have to get the length of the curve in bytes. Although
|
|
// we only support 256 now, we'll do this in an agnostic way so we
|
|
// can reuse this marshaling if we support e.g. 521. Getting the
|
|
// number of bytes without rounding up would be 65.125 so we need
|
|
// to add one in that case.
|
|
keyLen := curveBits / 8
|
|
if curveBits%8 > 0 {
|
|
keyLen++
|
|
}
|
|
|
|
// Now create the output array
|
|
sig = make([]byte, keyLen*2)
|
|
rb := r.Bytes()
|
|
sb := s.Bytes()
|
|
copy(sig[keyLen-len(rb):], rb)
|
|
copy(sig[2*keyLen-len(sb):], sb)
|
|
|
|
default:
|
|
return nil, errutil.UserError{Err: "requested marshaling type is invalid"}
|
|
}
|
|
|
|
case KeyType_ED25519:
|
|
var key ed25519.PrivateKey
|
|
|
|
if p.Derived {
|
|
// Derive the key that should be used
|
|
var err error
|
|
key, err = p.GetKey(context, ver, 32)
|
|
if err != nil {
|
|
return nil, errutil.InternalError{Err: fmt.Sprintf("error deriving key: %v", err)}
|
|
}
|
|
pubKey = key.Public().(ed25519.PublicKey)
|
|
} else {
|
|
key = ed25519.PrivateKey(keyParams.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
|
|
}
|
|
|
|
case KeyType_RSA2048, KeyType_RSA3072, KeyType_RSA4096:
|
|
key := keyParams.RSAKey
|
|
|
|
var algo crypto.Hash
|
|
switch hashAlgorithm {
|
|
case HashTypeSHA1:
|
|
algo = crypto.SHA1
|
|
case HashTypeSHA2224:
|
|
algo = crypto.SHA224
|
|
case HashTypeSHA2256:
|
|
algo = crypto.SHA256
|
|
case HashTypeSHA2384:
|
|
algo = crypto.SHA384
|
|
case HashTypeSHA2512:
|
|
algo = crypto.SHA512
|
|
case HashTypeSHA3224:
|
|
algo = crypto.SHA3_224
|
|
case HashTypeSHA3256:
|
|
algo = crypto.SHA3_256
|
|
case HashTypeSHA3384:
|
|
algo = crypto.SHA3_384
|
|
case HashTypeSHA3512:
|
|
algo = crypto.SHA3_512
|
|
default:
|
|
return nil, errutil.InternalError{Err: "unsupported hash algorithm"}
|
|
}
|
|
|
|
if sigAlgorithm == "" {
|
|
sigAlgorithm = "pss"
|
|
}
|
|
|
|
switch sigAlgorithm {
|
|
case "pss":
|
|
sig, err = rsa.SignPSS(rand.Reader, key, algo, input, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
case "pkcs1v15":
|
|
sig, err = rsa.SignPKCS1v15(rand.Reader, key, algo, input)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
default:
|
|
return nil, errutil.InternalError{Err: fmt.Sprintf("unsupported rsa signature algorithm %s", sigAlgorithm)}
|
|
}
|
|
|
|
default:
|
|
return nil, fmt.Errorf("unsupported key type %v", p.Type)
|
|
}
|
|
|
|
// Convert to base64
|
|
var encoded string
|
|
switch marshaling {
|
|
case MarshalingTypeASN1:
|
|
encoded = base64.StdEncoding.EncodeToString(sig)
|
|
case MarshalingTypeJWS:
|
|
encoded = base64.RawURLEncoding.EncodeToString(sig)
|
|
}
|
|
res := &SigningResult{
|
|
Signature: p.getVersionPrefix(ver) + encoded,
|
|
PublicKey: pubKey,
|
|
}
|
|
|
|
return res, nil
|
|
}
|
|
|
|
func (p *Policy) VerifySignature(context, input []byte, hashAlgorithm HashType, sigAlgorithm string, marshaling MarshalingType, 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)}
|
|
}
|
|
|
|
tplParts, err := p.getTemplateParts()
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
|
|
// Verify the prefix
|
|
if !strings.HasPrefix(sig, tplParts[0]) {
|
|
return false, errutil.UserError{Err: "invalid signature: no prefix"}
|
|
}
|
|
|
|
splitVerSig := strings.SplitN(strings.TrimPrefix(sig, tplParts[0]), tplParts[1], 2)
|
|
if len(splitVerSig) != 2 {
|
|
return false, errutil.UserError{Err: "invalid signature: wrong number of fields"}
|
|
}
|
|
|
|
ver, err := strconv.Atoi(splitVerSig[0])
|
|
if err != nil {
|
|
return false, errutil.UserError{Err: "invalid signature: version number could not be decoded"}
|
|
}
|
|
|
|
if ver > p.LatestVersion {
|
|
return false, errutil.UserError{Err: "invalid signature: version is too new"}
|
|
}
|
|
|
|
if p.MinDecryptionVersion > 0 && ver < p.MinDecryptionVersion {
|
|
return false, errutil.UserError{Err: ErrTooOld}
|
|
}
|
|
|
|
var sigBytes []byte
|
|
switch marshaling {
|
|
case MarshalingTypeASN1:
|
|
sigBytes, err = base64.StdEncoding.DecodeString(splitVerSig[1])
|
|
case MarshalingTypeJWS:
|
|
sigBytes, err = base64.RawURLEncoding.DecodeString(splitVerSig[1])
|
|
default:
|
|
return false, errutil.UserError{Err: "requested marshaling type is invalid"}
|
|
}
|
|
if err != nil {
|
|
return false, errutil.UserError{Err: "invalid base64 signature value"}
|
|
}
|
|
|
|
switch p.Type {
|
|
case KeyType_ECDSA_P256, KeyType_ECDSA_P384, KeyType_ECDSA_P521:
|
|
var curve elliptic.Curve
|
|
switch p.Type {
|
|
case KeyType_ECDSA_P384:
|
|
curve = elliptic.P384()
|
|
case KeyType_ECDSA_P521:
|
|
curve = elliptic.P521()
|
|
default:
|
|
curve = elliptic.P256()
|
|
}
|
|
|
|
var ecdsaSig ecdsaSignature
|
|
|
|
switch marshaling {
|
|
case MarshalingTypeASN1:
|
|
rest, err := asn1.Unmarshal(sigBytes, &ecdsaSig)
|
|
if err != nil {
|
|
return false, errutil.UserError{Err: "supplied signature is invalid"}
|
|
}
|
|
if rest != nil && len(rest) != 0 {
|
|
return false, errutil.UserError{Err: "supplied signature contains extra data"}
|
|
}
|
|
|
|
case MarshalingTypeJWS:
|
|
paramLen := len(sigBytes) / 2
|
|
rb := sigBytes[:paramLen]
|
|
sb := sigBytes[paramLen:]
|
|
ecdsaSig.R = new(big.Int)
|
|
ecdsaSig.R.SetBytes(rb)
|
|
ecdsaSig.S = new(big.Int)
|
|
ecdsaSig.S.SetBytes(sb)
|
|
}
|
|
|
|
keyParams, err := p.safeGetKeyEntry(ver)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
key := &ecdsa.PublicKey{
|
|
Curve: curve,
|
|
X: keyParams.EC_X,
|
|
Y: keyParams.EC_Y,
|
|
}
|
|
|
|
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.GetKey(context, ver, 32)
|
|
if err != nil {
|
|
return false, errutil.InternalError{Err: fmt.Sprintf("error deriving key: %v", err)}
|
|
}
|
|
} else {
|
|
key = ed25519.PrivateKey(p.Keys[strconv.Itoa(ver)].Key)
|
|
}
|
|
|
|
return ed25519.Verify(key.Public().(ed25519.PublicKey), input, sigBytes), nil
|
|
|
|
case KeyType_RSA2048, KeyType_RSA3072, KeyType_RSA4096:
|
|
keyEntry, err := p.safeGetKeyEntry(ver)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
|
|
key := keyEntry.RSAKey
|
|
|
|
var algo crypto.Hash
|
|
switch hashAlgorithm {
|
|
case HashTypeSHA1:
|
|
algo = crypto.SHA1
|
|
case HashTypeSHA2224:
|
|
algo = crypto.SHA224
|
|
case HashTypeSHA2256:
|
|
algo = crypto.SHA256
|
|
case HashTypeSHA2384:
|
|
algo = crypto.SHA384
|
|
case HashTypeSHA2512:
|
|
algo = crypto.SHA512
|
|
case HashTypeSHA3224:
|
|
algo = crypto.SHA3_224
|
|
case HashTypeSHA3256:
|
|
algo = crypto.SHA3_256
|
|
case HashTypeSHA3384:
|
|
algo = crypto.SHA3_384
|
|
case HashTypeSHA3512:
|
|
algo = crypto.SHA3_512
|
|
default:
|
|
return false, errutil.InternalError{Err: "unsupported hash algorithm"}
|
|
}
|
|
|
|
if sigAlgorithm == "" {
|
|
sigAlgorithm = "pss"
|
|
}
|
|
|
|
switch sigAlgorithm {
|
|
case "pss":
|
|
err = rsa.VerifyPSS(&key.PublicKey, algo, input, sigBytes, nil)
|
|
case "pkcs1v15":
|
|
err = rsa.VerifyPKCS1v15(&key.PublicKey, algo, input, sigBytes)
|
|
default:
|
|
return false, errutil.InternalError{Err: fmt.Sprintf("unsupported rsa signature algorithm %s", sigAlgorithm)}
|
|
}
|
|
|
|
return err == nil, nil
|
|
|
|
default:
|
|
return false, errutil.InternalError{Err: fmt.Sprintf("unsupported key type %v", p.Type)}
|
|
}
|
|
}
|
|
|
|
func (p *Policy) Import(ctx context.Context, storage logical.Storage, key []byte, randReader io.Reader) error {
|
|
now := time.Now()
|
|
entry := KeyEntry{
|
|
CreationTime: now,
|
|
DeprecatedCreationTime: now.Unix(),
|
|
}
|
|
|
|
hmacKey, err := uuid.GenerateRandomBytesWithReader(32, randReader)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
entry.HMACKey = hmacKey
|
|
|
|
if (p.Type == KeyType_AES128_GCM96 && len(key) != 16) ||
|
|
((p.Type == KeyType_AES256_GCM96 || p.Type == KeyType_ChaCha20_Poly1305) && len(key) != 32) {
|
|
return fmt.Errorf("invalid key size %d bytes for key type %s", len(key), p.Type)
|
|
}
|
|
|
|
if p.Type == KeyType_AES128_GCM96 || p.Type == KeyType_AES256_GCM96 || p.Type == KeyType_ChaCha20_Poly1305 {
|
|
entry.Key = key
|
|
} else {
|
|
parsedPrivateKey, err := x509.ParsePKCS8PrivateKey(key)
|
|
if err != nil {
|
|
return fmt.Errorf("error parsing asymmetric key: %s", err)
|
|
}
|
|
|
|
switch parsedPrivateKey.(type) {
|
|
case *ecdsa.PrivateKey:
|
|
if p.Type != KeyType_ECDSA_P256 && p.Type != KeyType_ECDSA_P384 && p.Type != KeyType_ECDSA_P521 {
|
|
return fmt.Errorf("invalid key type: expected %s, got %T", p.Type, parsedPrivateKey)
|
|
}
|
|
|
|
ecdsaKey := parsedPrivateKey.(*ecdsa.PrivateKey)
|
|
curve := elliptic.P256()
|
|
if p.Type == KeyType_ECDSA_P384 {
|
|
curve = elliptic.P384()
|
|
} else if p.Type == KeyType_ECDSA_P521 {
|
|
curve = elliptic.P521()
|
|
}
|
|
|
|
if ecdsaKey.Curve != curve {
|
|
return fmt.Errorf("invalid curve: expected %s, got %s", curve.Params().Name, ecdsaKey.Curve.Params().Name)
|
|
}
|
|
|
|
entry.EC_D = ecdsaKey.D
|
|
entry.EC_X = ecdsaKey.X
|
|
entry.EC_Y = ecdsaKey.Y
|
|
derBytes, err := x509.MarshalPKIXPublicKey(ecdsaKey.Public())
|
|
if err != nil {
|
|
return errwrap.Wrapf("error marshaling public key: {{err}}", err)
|
|
}
|
|
pemBlock := &pem.Block{
|
|
Type: "PUBLIC KEY",
|
|
Bytes: derBytes,
|
|
}
|
|
pemBytes := pem.EncodeToMemory(pemBlock)
|
|
if pemBytes == nil || len(pemBytes) == 0 {
|
|
return fmt.Errorf("error PEM-encoding public key")
|
|
}
|
|
entry.FormattedPublicKey = string(pemBytes)
|
|
case ed25519.PrivateKey:
|
|
if p.Type != KeyType_ED25519 {
|
|
return fmt.Errorf("invalid key type: expected %s, got %T", p.Type, parsedPrivateKey)
|
|
}
|
|
privateKey := parsedPrivateKey.(ed25519.PrivateKey)
|
|
|
|
entry.Key = privateKey
|
|
publicKey := privateKey.Public().(ed25519.PublicKey)
|
|
entry.FormattedPublicKey = base64.StdEncoding.EncodeToString(publicKey)
|
|
case *rsa.PrivateKey:
|
|
if p.Type != KeyType_RSA2048 && p.Type != KeyType_RSA3072 && p.Type != KeyType_RSA4096 {
|
|
return fmt.Errorf("invalid key type: expected %s, got %T", p.Type, parsedPrivateKey)
|
|
}
|
|
|
|
keyBytes := 256
|
|
if p.Type == KeyType_RSA3072 {
|
|
keyBytes = 384
|
|
} else if p.Type == KeyType_RSA4096 {
|
|
keyBytes = 512
|
|
}
|
|
rsaKey := parsedPrivateKey.(*rsa.PrivateKey)
|
|
if rsaKey.Size() != keyBytes {
|
|
return fmt.Errorf("invalid key size: expected %d bytes, got %d bytes", keyBytes, rsaKey.Size())
|
|
}
|
|
|
|
entry.RSAKey = rsaKey
|
|
default:
|
|
return fmt.Errorf("invalid key type: expected %s, got %T", p.Type, parsedPrivateKey)
|
|
}
|
|
}
|
|
|
|
p.LatestVersion += 1
|
|
|
|
if p.Keys == nil {
|
|
// This is an initial key rotation when generating a new policy. We
|
|
// don't need to call migrate here because if we've called getPolicy to
|
|
// get the policy in the first place it will have been run.
|
|
p.Keys = keyEntryMap{}
|
|
}
|
|
p.Keys[strconv.Itoa(p.LatestVersion)] = entry
|
|
|
|
// This ensures that with new key creations min decryption version is set
|
|
// to 1 rather than the int default of 0, since keys start at 1 (either
|
|
// fresh or after migration to the key map)
|
|
if p.MinDecryptionVersion == 0 {
|
|
p.MinDecryptionVersion = 1
|
|
}
|
|
|
|
return p.Persist(ctx, storage)
|
|
}
|
|
|
|
// Rotate rotates the policy and persists it to storage.
|
|
// If the rotation partially fails, the policy state will be restored.
|
|
func (p *Policy) Rotate(ctx context.Context, storage logical.Storage, randReader io.Reader) (retErr error) {
|
|
priorLatestVersion := p.LatestVersion
|
|
priorMinDecryptionVersion := p.MinDecryptionVersion
|
|
var priorKeys keyEntryMap
|
|
|
|
if p.Imported && !p.AllowImportedKeyRotation {
|
|
return fmt.Errorf("imported key %s does not allow rotation within Vault", p.Name)
|
|
}
|
|
|
|
if p.Keys != nil {
|
|
priorKeys = keyEntryMap{}
|
|
for k, v := range p.Keys {
|
|
priorKeys[k] = v
|
|
}
|
|
}
|
|
|
|
defer func() {
|
|
if retErr != nil {
|
|
p.LatestVersion = priorLatestVersion
|
|
p.MinDecryptionVersion = priorMinDecryptionVersion
|
|
p.Keys = priorKeys
|
|
}
|
|
}()
|
|
|
|
if err := p.RotateInMemory(randReader); err != nil {
|
|
return err
|
|
}
|
|
|
|
p.Imported = false
|
|
return p.Persist(ctx, storage)
|
|
}
|
|
|
|
// RotateInMemory rotates the policy but does not persist it to storage.
|
|
func (p *Policy) RotateInMemory(randReader io.Reader) (retErr error) {
|
|
now := time.Now()
|
|
entry := KeyEntry{
|
|
CreationTime: now,
|
|
DeprecatedCreationTime: now.Unix(),
|
|
}
|
|
|
|
hmacKey, err := uuid.GenerateRandomBytesWithReader(32, randReader)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
entry.HMACKey = hmacKey
|
|
|
|
switch p.Type {
|
|
case KeyType_AES128_GCM96, KeyType_AES256_GCM96, KeyType_ChaCha20_Poly1305:
|
|
// Default to 256 bit key
|
|
numBytes := 32
|
|
if p.Type == KeyType_AES128_GCM96 {
|
|
numBytes = 16
|
|
}
|
|
newKey, err := uuid.GenerateRandomBytesWithReader(numBytes, randReader)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
entry.Key = newKey
|
|
|
|
case KeyType_ECDSA_P256, KeyType_ECDSA_P384, KeyType_ECDSA_P521:
|
|
var curve elliptic.Curve
|
|
switch p.Type {
|
|
case KeyType_ECDSA_P384:
|
|
curve = elliptic.P384()
|
|
case KeyType_ECDSA_P521:
|
|
curve = elliptic.P521()
|
|
default:
|
|
curve = elliptic.P256()
|
|
}
|
|
|
|
privKey, err := ecdsa.GenerateKey(curve, rand.Reader)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
entry.EC_D = privKey.D
|
|
entry.EC_X = privKey.X
|
|
entry.EC_Y = privKey.Y
|
|
derBytes, err := x509.MarshalPKIXPublicKey(privKey.Public())
|
|
if err != nil {
|
|
return errwrap.Wrapf("error marshaling public key: {{err}}", err)
|
|
}
|
|
pemBlock := &pem.Block{
|
|
Type: "PUBLIC KEY",
|
|
Bytes: derBytes,
|
|
}
|
|
pemBytes := pem.EncodeToMemory(pemBlock)
|
|
if pemBytes == nil || len(pemBytes) == 0 {
|
|
return fmt.Errorf("error PEM-encoding public key")
|
|
}
|
|
entry.FormattedPublicKey = string(pemBytes)
|
|
|
|
case KeyType_ED25519:
|
|
pub, pri, err := ed25519.GenerateKey(randReader)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
entry.Key = pri
|
|
entry.FormattedPublicKey = base64.StdEncoding.EncodeToString(pub)
|
|
|
|
case KeyType_RSA2048, KeyType_RSA3072, KeyType_RSA4096:
|
|
bitSize := 2048
|
|
if p.Type == KeyType_RSA3072 {
|
|
bitSize = 3072
|
|
}
|
|
if p.Type == KeyType_RSA4096 {
|
|
bitSize = 4096
|
|
}
|
|
|
|
entry.RSAKey, err = rsa.GenerateKey(randReader, bitSize)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if p.ConvergentEncryption {
|
|
if p.ConvergentVersion == -1 || p.ConvergentVersion > 1 {
|
|
entry.ConvergentVersion = currentConvergentVersion
|
|
}
|
|
}
|
|
|
|
p.LatestVersion += 1
|
|
|
|
if p.Keys == nil {
|
|
// This is an initial key rotation when generating a new policy. We
|
|
// don't need to call migrate here because if we've called getPolicy to
|
|
// get the policy in the first place it will have been run.
|
|
p.Keys = keyEntryMap{}
|
|
}
|
|
p.Keys[strconv.Itoa(p.LatestVersion)] = entry
|
|
|
|
// This ensures that with new key creations min decryption version is set
|
|
// to 1 rather than the int default of 0, since keys start at 1 (either
|
|
// fresh or after migration to the key map)
|
|
if p.MinDecryptionVersion == 0 {
|
|
p.MinDecryptionVersion = 1
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (p *Policy) MigrateKeyToKeysMap() {
|
|
now := time.Now()
|
|
p.Keys = keyEntryMap{
|
|
"1": KeyEntry{
|
|
Key: p.Key,
|
|
CreationTime: now,
|
|
DeprecatedCreationTime: now.Unix(),
|
|
},
|
|
}
|
|
p.Key = nil
|
|
}
|
|
|
|
// Backup should be called with an exclusive lock held on the policy
|
|
func (p *Policy) Backup(ctx context.Context, storage logical.Storage) (out string, retErr error) {
|
|
if !p.Exportable {
|
|
return "", fmt.Errorf("exporting is disallowed on the policy")
|
|
}
|
|
|
|
if !p.AllowPlaintextBackup {
|
|
return "", fmt.Errorf("plaintext backup is disallowed on the policy")
|
|
}
|
|
|
|
priorBackupInfo := p.BackupInfo
|
|
|
|
defer func() {
|
|
if retErr != nil {
|
|
p.BackupInfo = priorBackupInfo
|
|
}
|
|
}()
|
|
|
|
// Create a record of this backup operation in the policy
|
|
p.BackupInfo = &BackupInfo{
|
|
Time: time.Now(),
|
|
Version: p.LatestVersion,
|
|
}
|
|
err := p.Persist(ctx, storage)
|
|
if err != nil {
|
|
return "", errwrap.Wrapf("failed to persist policy with backup info: {{err}}", err)
|
|
}
|
|
|
|
// Load the archive only after persisting the policy as the archive can get
|
|
// adjusted while persisting the policy
|
|
archivedKeys, err := p.LoadArchive(ctx, storage)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
keyData := &KeyData{
|
|
Policy: p,
|
|
ArchivedKeys: archivedKeys,
|
|
}
|
|
|
|
encodedBackup, err := jsonutil.EncodeJSON(keyData)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
return base64.StdEncoding.EncodeToString(encodedBackup), nil
|
|
}
|
|
|
|
func (p *Policy) getTemplateParts() ([]string, error) {
|
|
partsRaw, ok := p.versionPrefixCache.Load("template-parts")
|
|
if ok {
|
|
return partsRaw.([]string), nil
|
|
}
|
|
|
|
template := p.VersionTemplate
|
|
if template == "" {
|
|
template = DefaultVersionTemplate
|
|
}
|
|
|
|
tplParts := strings.Split(template, "{{version}}")
|
|
if len(tplParts) != 2 {
|
|
return nil, errutil.InternalError{Err: "error parsing version template"}
|
|
}
|
|
|
|
p.versionPrefixCache.Store("template-parts", tplParts)
|
|
return tplParts, nil
|
|
}
|
|
|
|
func (p *Policy) getVersionPrefix(ver int) string {
|
|
prefixRaw, ok := p.versionPrefixCache.Load(ver)
|
|
if ok {
|
|
return prefixRaw.(string)
|
|
}
|
|
|
|
template := p.VersionTemplate
|
|
if template == "" {
|
|
template = DefaultVersionTemplate
|
|
}
|
|
|
|
prefix := strings.Replace(template, "{{version}}", strconv.Itoa(ver), -1)
|
|
p.versionPrefixCache.Store(ver, prefix)
|
|
|
|
return prefix
|
|
}
|
|
|
|
// SymmetricOpts are the arguments to symmetric operations that are "optional", e.g.
|
|
// not always used. This improves the aesthetics of calls to those functions.
|
|
type SymmetricOpts struct {
|
|
// Whether to use convergent encryption
|
|
Convergent bool
|
|
// The version of the convergent encryption scheme
|
|
ConvergentVersion int
|
|
// The nonce, if not randomly generated
|
|
Nonce []byte
|
|
// Additional data to include in AEAD authentication
|
|
AdditionalData []byte
|
|
// The HMAC key, for generating IVs in convergent encryption
|
|
HMACKey []byte
|
|
}
|
|
|
|
// Symmetrically encrypt a plaintext given the convergence configuration and appropriate keys
|
|
func (p *Policy) SymmetricEncryptRaw(ver int, encKey, plaintext []byte, opts SymmetricOpts) ([]byte, error) {
|
|
var aead cipher.AEAD
|
|
var err error
|
|
nonce := opts.Nonce
|
|
|
|
switch p.Type {
|
|
case KeyType_AES128_GCM96, KeyType_AES256_GCM96:
|
|
// Setup the cipher
|
|
aesCipher, err := aes.NewCipher(encKey)
|
|
if err != nil {
|
|
return nil, errutil.InternalError{Err: err.Error()}
|
|
}
|
|
|
|
// Setup the GCM AEAD
|
|
gcm, err := cipher.NewGCM(aesCipher)
|
|
if err != nil {
|
|
return nil, errutil.InternalError{Err: err.Error()}
|
|
}
|
|
|
|
aead = gcm
|
|
|
|
case KeyType_ChaCha20_Poly1305:
|
|
cha, err := chacha20poly1305.New(encKey)
|
|
if err != nil {
|
|
return nil, errutil.InternalError{Err: err.Error()}
|
|
}
|
|
|
|
aead = cha
|
|
}
|
|
|
|
if opts.Convergent {
|
|
convergentVersion := p.convergentVersion(ver)
|
|
switch convergentVersion {
|
|
case 1:
|
|
if len(opts.Nonce) != aead.NonceSize() {
|
|
return nil, errutil.UserError{Err: fmt.Sprintf("base64-decoded nonce must be %d bytes long when using convergent encryption with this key", aead.NonceSize())}
|
|
}
|
|
case 2, 3:
|
|
if len(opts.HMACKey) == 0 {
|
|
return nil, errutil.InternalError{Err: fmt.Sprintf("invalid hmac key length of zero")}
|
|
}
|
|
nonceHmac := hmac.New(sha256.New, opts.HMACKey)
|
|
nonceHmac.Write(plaintext)
|
|
nonceSum := nonceHmac.Sum(nil)
|
|
nonce = nonceSum[:aead.NonceSize()]
|
|
default:
|
|
return nil, errutil.InternalError{Err: fmt.Sprintf("unhandled convergent version %d", convergentVersion)}
|
|
}
|
|
} else if len(nonce) == 0 {
|
|
// Compute random nonce
|
|
nonce, err = uuid.GenerateRandomBytes(aead.NonceSize())
|
|
if err != nil {
|
|
return nil, errutil.InternalError{Err: err.Error()}
|
|
}
|
|
} else if len(nonce) != aead.NonceSize() {
|
|
return nil, errutil.UserError{Err: fmt.Sprintf("base64-decoded nonce must be %d bytes long but given %d bytes", aead.NonceSize(), len(nonce))}
|
|
}
|
|
|
|
// Encrypt and tag with AEAD
|
|
ciphertext := aead.Seal(nil, nonce, plaintext, opts.AdditionalData)
|
|
|
|
// Place the encrypted data after the nonce
|
|
if !opts.Convergent || p.convergentVersion(ver) > 1 {
|
|
ciphertext = append(nonce, ciphertext...)
|
|
}
|
|
return ciphertext, nil
|
|
}
|
|
|
|
// Symmetrically decrypt a ciphertext given the convergence configuration and appropriate keys
|
|
func (p *Policy) SymmetricDecryptRaw(encKey, ciphertext []byte, opts SymmetricOpts) ([]byte, error) {
|
|
var aead cipher.AEAD
|
|
var nonce []byte
|
|
|
|
switch p.Type {
|
|
case KeyType_AES128_GCM96, KeyType_AES256_GCM96:
|
|
// Setup the cipher
|
|
aesCipher, err := aes.NewCipher(encKey)
|
|
if err != nil {
|
|
return nil, errutil.InternalError{Err: err.Error()}
|
|
}
|
|
|
|
// Setup the GCM AEAD
|
|
gcm, err := cipher.NewGCM(aesCipher)
|
|
if err != nil {
|
|
return nil, errutil.InternalError{Err: err.Error()}
|
|
}
|
|
|
|
aead = gcm
|
|
|
|
case KeyType_ChaCha20_Poly1305:
|
|
cha, err := chacha20poly1305.New(encKey)
|
|
if err != nil {
|
|
return nil, errutil.InternalError{Err: err.Error()}
|
|
}
|
|
|
|
aead = cha
|
|
}
|
|
|
|
if len(ciphertext) < aead.NonceSize() {
|
|
return nil, errutil.UserError{Err: "invalid ciphertext length"}
|
|
}
|
|
|
|
// Extract the nonce and ciphertext
|
|
var trueCT []byte
|
|
if opts.Convergent && opts.ConvergentVersion == 1 {
|
|
trueCT = ciphertext
|
|
} else {
|
|
nonce = ciphertext[:aead.NonceSize()]
|
|
trueCT = ciphertext[aead.NonceSize():]
|
|
}
|
|
|
|
// Verify and Decrypt
|
|
plain, err := aead.Open(nil, nonce, trueCT, opts.AdditionalData)
|
|
if err != nil {
|
|
return nil, errutil.UserError{Err: err.Error()}
|
|
}
|
|
return plain, nil
|
|
}
|