State Encryption Documentation and Partial Implementation (#1227)

Signed-off-by: StephanHCB <sbs_github_u43a@packetloss.de>
Signed-off-by: Christian Mesh <christianmesh1@gmail.com>
Signed-off-by: Janos <86970079+janosdebugs@users.noreply.github.com>
Signed-off-by: James Humphries <james@james-humphries.co.uk>
Co-authored-by: StephanHCB <sbs_github_u43a@packetloss.de>
Co-authored-by: Janos <86970079+janosdebugs@users.noreply.github.com>
Co-authored-by: Christian Mesh <christianmesh1@gmail.com>
This commit is contained in:
James Humphries
2024-02-16 14:59:19 +00:00
committed by GitHub
parent 7054dda96e
commit cbab4bee83
36 changed files with 3014 additions and 2 deletions

View File

@@ -0,0 +1,73 @@
// Copyright (c) The OpenTofu Authors
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) 2023 HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package method
import (
"fmt"
"regexp"
"github.com/hashicorp/hcl/v2"
)
// TODO is there a generalized way to regexp-check names?
var addrRe = regexp.MustCompile(`^method\.([a-zA-Z_0-9-]+)\.([a-zA-Z_0-9-]+)$`)
var nameRe = regexp.MustCompile("^([a-zA-Z_0-9-]+)$")
// Addr is a type-alias for method address strings that identify a specific encryption method configuration.
// The Addr is an opaque value. Do not perform string manipulation on it outside the functions supplied by the
// method package.
type Addr string
// Validate validates the Addr for formal naming conformance, but does not check if the referenced method actually
// exists in the configuration.
func (a Addr) Validate() hcl.Diagnostics {
if !addrRe.MatchString(string(a)) {
return hcl.Diagnostics{
&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid encryption method address",
Detail: fmt.Sprintf(
"The supplied encryption method address does not match the required form of %s",
addrRe.String(),
),
},
}
}
return nil
}
// NewAddr creates a new Addr type from the provider and name supplied. The Addr is a type-alias for encryption method
// address strings that identify a specific encryption method configuration. You should treat the value as opaque and
// not perform string manipulation on it outside the functions supplied by the method package.
func NewAddr(method string, name string) (addr Addr, err hcl.Diagnostics) {
if !nameRe.MatchString(method) {
err = err.Append(
&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "The provided encryption method type is invalid",
Detail: fmt.Sprintf(
"The supplied encryption method type (%s) does not match the required form of %s.",
method,
nameRe.String(),
),
},
)
}
if !nameRe.MatchString(name) {
err = err.Append(
&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "The provided encryption method name is invalid",
Detail: fmt.Sprintf(
"The supplied encryption method name (%s) does not match the required form of %s.",
name,
nameRe.String(),
),
},
)
}
return Addr(fmt.Sprintf("method.%s.%s", method, name)), err
}

View File

@@ -0,0 +1,61 @@
// Copyright (c) The OpenTofu Authors
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) 2023 HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package aesgcm
import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
)
type aesgcm struct {
key []byte
}
// Inspired by https://bruinsslot.jp/post/golang-crypto/
func (a aesgcm) Encrypt(data []byte) ([]byte, error) {
// TODO change this to the implementation in Stephan's PR.
blockCipher, err := aes.NewCipher(a.key)
if err != nil {
return nil, err
}
gcm, err := cipher.NewGCM(blockCipher)
if err != nil {
return nil, err
}
nonce := make([]byte, gcm.NonceSize())
if _, err = rand.Read(nonce); err != nil {
return nil, err
}
ciphertext := gcm.Seal(nonce, nonce, data, nil)
return ciphertext, nil
}
func (a aesgcm) Decrypt(data []byte) ([]byte, error) {
blockCipher, err := aes.NewCipher(a.key)
if err != nil {
return nil, err
}
gcm, err := cipher.NewGCM(blockCipher)
if err != nil {
return nil, err
}
nonce, ciphertext := data[:gcm.NonceSize()], data[gcm.NonceSize():]
plaintext, err := gcm.Open(nil, nonce, ciphertext, nil)
if err != nil {
return nil, err
}
return plaintext, nil
}

View File

@@ -0,0 +1,25 @@
// Copyright (c) The OpenTofu Authors
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) 2023 HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package aesgcm
import (
"fmt"
"github.com/opentofu/opentofu/internal/encryption/method"
)
type Config struct {
Key []byte `hcl:"cipher"`
}
func (c Config) Build() (method.Method, error) {
if len(c.Key) != 32 {
return nil, fmt.Errorf("AES-GCM requires a 32-byte key")
}
return &aesgcm{
c.Key,
}, nil
}

View File

@@ -0,0 +1,24 @@
// Copyright (c) The OpenTofu Authors
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) 2023 HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package aesgcm
import "github.com/opentofu/opentofu/internal/encryption/method"
// New creates a new descriptor for the AES-GCM encryption method, which requires a 32-byte key.
func New() method.Descriptor {
return &descriptor{}
}
type descriptor struct {
}
func (f *descriptor) ID() method.ID {
return "aes_gcm"
}
func (f *descriptor) ConfigStruct() method.Config {
return &Config{}
}

View File

@@ -0,0 +1,15 @@
// Copyright (c) The OpenTofu Authors
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) 2023 HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package method
// ID is a type alias to make passing the wrong ID into a method ID harder.
type ID string
// Validate validates the key provider ID for correctness.
func (i ID) Validate() error {
// TODO implement format checking
return nil
}

View File

@@ -0,0 +1,37 @@
// Copyright (c) The OpenTofu Authors
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) 2023 HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package method
type Config interface {
// Build takes the configuration and builds an encryption method.
Build() (Method, error)
}
type Descriptor interface {
// ID returns the unique identifier used when parsing HCL or JSON configs.
ID() ID
// ConfigStruct creates a new configuration struct annotated with hcl tags. The Build() receiver on
// this struct must be able to build a Method from the configuration.
//
// Common errors:
// - Returning a struct without a pointer
// - Returning a non-struct
ConfigStruct() Config
}
// Method is a low-level encryption method interface that is responsible for encrypting a binary blob of data. It should
// not try to interpret what kind of data it is encrypting.
type Method interface {
// Encrypt encrypts the specified data with the set configuration. This method should treat any data passed as
// opaque and should not try to interpret its contents. The interpretation is the job of the encryption.Encryption
// interface.
Encrypt(data []byte) ([]byte, error)
// Decrypt decrypts the specified data with the set configuration. This method should treat any data passed as
// opaque and should not try to interpret its contents. The interpretation is the job of the encryption.Encryption
// interface.
Decrypt(data []byte) ([]byte, error)
}