Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
3cb3e5e
feat(kas): support HSM and standard crypto
sujankota Apr 2, 2024
b993e3c
fix the integeration test
sujankota Apr 2, 2024
d6e0600
fix test
sujankota Apr 2, 2024
ddd1b94
rename lib/crypto to lib/ocrypto
sujankota Apr 4, 2024
c1c7c5c
after merge
sujankota Apr 4, 2024
f7955dd
Fix Dockerfile
sujankota Apr 4, 2024
b40a49e
Fix build
sujankota Apr 4, 2024
b920593
fix the build
sujankota Apr 4, 2024
eb94914
go lint fix
sujankota Apr 4, 2024
4ab185f
fix lint errors
sujankota Apr 4, 2024
8214acd
fix lint errors
sujankota Apr 4, 2024
dcc1435
Fix lint error
sujankota Apr 4, 2024
1f372a5
merge main
sujankota Apr 4, 2024
3483990
Fix lint errors
sujankota Apr 4, 2024
98307e2
github flow fixes
sujankota Apr 4, 2024
4602072
Merge branch 'main' into support-standard-crypto
sujankota Apr 4, 2024
1d200d4
fix the build
sujankota Apr 4, 2024
d4c4a10
Fix lint error
sujankota Apr 4, 2024
9a13e79
Fixed lint errors
sujankota Apr 4, 2024
9a5987e
Fix the integeration test
sujankota Apr 4, 2024
30deeda
Fix lint errors
sujankota Apr 4, 2024
a4c210d
fix the lint error
sujankota Apr 4, 2024
b69212a
fix the lint error
sujankota Apr 4, 2024
70d18be
fix the test
sujankota Apr 4, 2024
00844d1
Fix services tests
sujankota Apr 5, 2024
b9508b7
Merge branch 'main' into support-standard-crypto
sujankota Apr 5, 2024
6ed2fd8
Fix lint error
sujankota Apr 5, 2024
acdbccf
Fix test
sujankota Apr 5, 2024
299cd98
Fix service tests
sujankota Apr 5, 2024
955c1f8
Fix service tests
sujankota Apr 5, 2024
2e29d88
Fix service test
sujankota Apr 5, 2024
49185c8
Fix service test
sujankota Apr 5, 2024
199987d
Fix service tests
sujankota Apr 5, 2024
342432a
Fix service tests
sujankota Apr 5, 2024
cdcc223
Fix service tests
sujankota Apr 5, 2024
373a137
Fix service test
sujankota Apr 5, 2024
c7d192d
Disable HSM
sujankota Apr 5, 2024
a48949b
Fix the ocrypto package
sujankota Apr 5, 2024
08e2e41
Merge branch 'main' into support-standard-crypto
sujankota Apr 5, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions internal/security/crypto_provider.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package security

type Config struct {
Type string `yaml:"type" default:"standard"`
// HSMConfig is the configuration for the HSM
HSMConfig HSMConfig `yaml:"hsm,omitempty" mapstructure:"hsm"`
// StandardConfig is the configuration for the standard key provider
StandardConfig StandardConfig `yaml:"standard,omitempty" mapstructure:"standard"`
}

type CryptoProvider interface {
RSAPublicKey(keyId string) (string, error)
ECPublicKey(keyId string) (string, error)
RSADecrypt(hashFunction string, keyId string, keyLabel string, ciphertext []byte) ([]byte, error)
}

func NewCryptoProvider(cfg Config) (CryptoProvider, error) {
switch cfg.Type {
case "hsm":
return New(&cfg.HSMConfig)
case "standard":
return NewStandardCrypto(cfg.StandardConfig)
default:
return NewStandardCrypto(cfg.StandardConfig)
}
}
131 changes: 108 additions & 23 deletions internal/security/hsm.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"crypto/rsa"
"crypto/sha256"
"crypto/x509"
"encoding/pem"
"errors"
"fmt"
"io"
Expand All @@ -19,10 +20,13 @@ import (
)

const (
ErrHSMUnexpected = Error("hsm unexpected")
ErrHSMDecrypt = Error("hsm decrypt error")
ErrHSMNotFound = Error("hsm unavailable")
ErrKeyConfig = Error("key configuration error")
ErrCertificateEncode = Error("certificate encode error")
ErrPublicKeyMarshal = Error("public key marshal error")
ErrHSMUnexpected = Error("hsm unexpected")
ErrHSMDecrypt = Error("hsm decrypt error")
ErrHSMNotFound = Error("hsm unavailable")
ErrKeyConfig = Error("key configuration error")
ErrUnknownHashFunction = Error("unknown hash function")
)
const keyLength = 32

Expand Down Expand Up @@ -493,25 +497,6 @@ func (h *HSMSession) LoadECKey(info KeyInfo) (*ECKeyPair, error) {
return &pair, nil
}

func (session *HSMSession) DecryptOAEP(key *PrivateKeyRSA, ciphertext []byte, hashFunction crypto.Hash, label []byte) ([]byte, error) {
hashAlg, mgfAlg, _, err := hashToPKCS11(hashFunction)
if err != nil {
return nil, errors.Join(ErrHSMDecrypt, err)
}

mech := pkcs11.NewMechanism(pkcs11.CKM_RSA_PKCS_OAEP, pkcs11.NewOAEPParams(hashAlg, mgfAlg, pkcs11.CKZ_DATA_SPECIFIED, label))

err = session.ctx.DecryptInit(session.sh, []*pkcs11.Mechanism{mech}, pkcs11.ObjectHandle(*key))
if err != nil {
return nil, errors.Join(ErrHSMDecrypt, err)
}
decrypt, err := session.ctx.Decrypt(session.sh, ciphertext)
if err != nil {
return nil, errors.Join(ErrHSMDecrypt, err)
}
return decrypt, nil
}

func hashToPKCS11(hashFunction crypto.Hash) (hashAlg uint, mgfAlg uint, hashLen uint, err error) {
switch hashFunction {
case crypto.SHA1:
Expand Down Expand Up @@ -660,8 +645,108 @@ func (h *HSMSession) GenerateEphemeralKasKeys() (PrivateKeyEC, []byte, error) {
return PrivateKeyEC(prvHandle), publicKeyBytes, nil
}

func (h *HSMSession) RSAPublicKey(keyId string) (string, error) {
// TODO: For now ignore the key id
slog.Info("⚠️ Ignoring the", slog.String("key id", keyId))

pubkeyBytes, err := x509.MarshalPKIXPublicKey(h.RSA.PublicKey)
if err != nil {
return "", errors.Join(ErrPublicKeyMarshal, err)
}

certPem := pem.EncodeToMemory(
&pem.Block{
Type: "PUBLIC KEY",
Headers: nil,
Bytes: pubkeyBytes,
},
)
if certPem == nil {
return "", ErrCertificateEncode
}
return string(certPem), nil
}

func (h *HSMSession) ECPublicKey(keyId string) (string, error) {
return "", nil
}

func (h *HSMSession) RSADecrypt(hashFunction string, keyId string, keyLabel string, ciphertext []byte) ([]byte, error) {

// TODO: For now ignore the key id
slog.Info("⚠️ Ignoring the", slog.String("key id", keyId))

hash, err := stringToHashFunction(hashFunction)
if err != nil {
return nil, errors.Join(ErrHSMDecrypt, err)
}

hashAlg, mgfAlg, _, err := hashToPKCS11(hash)
if err != nil {
return nil, errors.Join(ErrHSMDecrypt, err)
}

mech := pkcs11.NewMechanism(pkcs11.CKM_RSA_PKCS_OAEP,
pkcs11.NewOAEPParams(hashAlg, mgfAlg, pkcs11.CKZ_DATA_SPECIFIED,
[]byte(keyLabel)))

err = h.ctx.DecryptInit(h.sh, []*pkcs11.Mechanism{mech}, pkcs11.ObjectHandle(h.RSA.PrivateKey))
if err != nil {
return nil, errors.Join(ErrHSMDecrypt, err)
}
decrypt, err := h.ctx.Decrypt(h.sh, ciphertext)
if err != nil {
return nil, errors.Join(ErrHSMDecrypt, err)
}
return decrypt, nil
}

func versionSalt() []byte {
digest := sha256.New()
digest.Write([]byte("L1L"))
return digest.Sum(nil)
}

// StringToHashFunction String to crypto hash function name
func stringToHashFunction(hashFunction string) (crypto.Hash, error) {
switch hashFunction {
case "MD4":
return crypto.MD4, nil
case "MD5":
return crypto.MD5, nil
case "SHA1":
return crypto.SHA1, nil
case "SHA224":
return crypto.SHA224, nil
case "SHA256":
return crypto.SHA256, nil
case "SHA384":
return crypto.SHA384, nil
case "SHA512":
return crypto.SHA512, nil
case "MD5SHA1":
return crypto.MD5SHA1, nil
case "SHA3_224":
return crypto.SHA3_224, nil
case "SHA3_256":
return crypto.SHA3_256, nil
case "SHA3_384":
return crypto.SHA3_384, nil
case "SHA3_512":
return crypto.SHA3_512, nil
case "SHA512_224":
return crypto.SHA512_224, nil
case "SHA512_256":
return crypto.SHA512_256, nil
case "BLAKE2s_256":
return crypto.BLAKE2s_256, nil
case "BLAKE2b_256":
return crypto.BLAKE2b_256, nil
case "BLAKE2b_384":
return crypto.BLAKE2b_384, nil
case "BLAKE2b_512":
return crypto.BLAKE2b_512, nil
default:
return 0, ErrUnknownHashFunction
}
}
8 changes: 2 additions & 6 deletions internal/security/hsm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,18 +99,14 @@ func TestHashToPKCS11Success(t *testing.T) {
}

func TestDecryptOAEPUnsupportedRSAFailure(t *testing.T) {
objectHandle := pkcs11.ObjectHandle(2)
sessionHandle := pkcs11.SessionHandle(1)

session := &HSMSession{
ctx: pkcs11.Ctx{},
sh: sessionHandle,
}

key := PrivateKeyRSA(objectHandle)
unsupportedRSA := crypto.BLAKE2b_384

decrypted, err := session.DecryptOAEP(&key, []byte("sample ciphertext"), unsupportedRSA, []byte("sample label"))
unsupportedRSA := "BLAKE2b_384"
decrypted, err := session.RSADecrypt(unsupportedRSA, "unknown", "sample label", []byte("sample ciphertext"))

t.Log(err)
t.Log(decrypted)
Expand Down
114 changes: 114 additions & 0 deletions internal/security/standard_crypto.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
package security

import (
"crypto/ecdh"
"errors"
"fmt"
"github.com/opentdf/platform/lib/crypto"
"log/slog"
"os"
)

var (
errStandardCryptoNotEnabled = errors.New("standard crypto flag is enabled in the config")
errStandardCryptoObjIsInvalid = errors.New("standard crypto object is invalid")
)

type StandardConfig struct {
RSAKeys map[string]StandardKeyInfo `yaml:"rsa,omitempty" mapstructure:"rsa"`
ECKeys map[string]StandardKeyInfo `yaml:"ec,omitempty" mapstructure:"ec"`
}

type StandardKeyInfo struct {
PrivateKeyPath string `yaml:"privateKeyPath" mapstructure:"privateKeyPath"`
PublicKeyPath string `yaml:"publicKeyPath" mapstructure:"publicKeyPath"`
}

type StandardRSACrypto struct {
Identifier string
asymDecryption crypto.AsymDecryption
asymEncryption crypto.AsymEncryption
}

type StandardECCrypto struct {
Identifier string
ecPublicKey *ecdh.PublicKey
ecPrivateKey *ecdh.PrivateKey
}

type StandardCrypto struct {
rsaKeys []StandardRSACrypto
ecKeys []StandardECCrypto
}

// NewStandardCrypto Create a new instance of standard crypto
func NewStandardCrypto(cfg StandardConfig) (*StandardCrypto, error) {
standardCrypto := &StandardCrypto{}
for id, kasInfo := range cfg.RSAKeys {
privatePemData, err := os.ReadFile(kasInfo.PrivateKeyPath)
if err != nil {
return nil, fmt.Errorf("failed to rsa private key file: %w", err)
}

asymDecryption, err := crypto.NewAsymDecryption(string(privatePemData))
if err != nil {
return nil, fmt.Errorf("crypto.NewAsymDecryption failed: %w", err)
}

publicPemData, err := os.ReadFile(kasInfo.PublicKeyPath)
if err != nil {
return nil, fmt.Errorf("failed to rsa public key file: %w", err)
}

asymEncryption, err := crypto.NewAsymEncryption(string(publicPemData))
if err != nil {
return nil, fmt.Errorf("crypto.NewAsymEncryption failed: %w", err)
}

standardCrypto.rsaKeys = append(standardCrypto.rsaKeys, StandardRSACrypto{
Identifier: id,
asymDecryption: asymDecryption,
asymEncryption: asymEncryption,
})
}

return standardCrypto, nil
}

func (s StandardCrypto) RSAPublicKey(keyId string) (string, error) {

if len(s.rsaKeys) == 0 {
return "", errStandardCryptoObjIsInvalid
}

// TODO: For now ignore the key id
slog.Info("⚠️ Ignoring the", slog.String("key id", keyId))

pem, err := s.rsaKeys[0].asymEncryption.PublicKeyInPemFormat()
if err != nil {
return "", fmt.Errorf("failed to retrive rsa public key file: %w", err)
}

return pem, nil
}

func (s StandardCrypto) ECPublicKey(keyId string) (string, error) {
return "", nil
}

func (s StandardCrypto) RSADecrypt(hashFunction string, keyId string, keyLabel string, ciphertext []byte) ([]byte, error) {

if len(s.rsaKeys) == 0 {
return nil, errStandardCryptoObjIsInvalid
}

// TODO: For now ignore the key id
slog.Info("⚠️ Ignoring the", slog.String("key id", keyId))

data, err := s.rsaKeys[0].asymDecryption.Decrypt(ciphertext)
if err != nil {
return nil, fmt.Errorf("error decrypting data: %w", err)
}

return data, nil
}
32 changes: 18 additions & 14 deletions internal/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,10 @@ func (e Error) Error() string {
}

type Config struct {
Auth auth.Config `yaml:"auth"`
GRPC GRPCConfig `yaml:"grpc"`
HSM security.HSMConfig `yaml:"hsm"`
TLS TLSConfig `yaml:"tls"`
Auth auth.Config `yaml:"auth"`
GRPC GRPCConfig `yaml:"grpc"`
CryptoProvider security.Config `yaml:"cryptoProvider"`
TLS TLSConfig `yaml:"tls"`
WellKnownConfigRegister func(namespace string, config any) error
Port int `yaml:"port" default:"8080"`
Host string `yaml:"host,omitempty"`
Expand All @@ -63,11 +63,11 @@ type TLSConfig struct {
}

type OpenTDFServer struct {
Mux *runtime.ServeMux
HTTPServer *http.Server
GRPCServer *grpc.Server
GRPCInProcess *inProcessServer
HSM *security.HSMSession
Mux *runtime.ServeMux
HTTPServer *http.Server
GRPCServer *grpc.Server
GRPCInProcess *inProcessServer
CryptoProvider security.CryptoProvider
}

/*
Expand Down Expand Up @@ -128,11 +128,15 @@ func NewOpenTDFServer(config Config, d *db.Client) (*OpenTDFServer, error) {
GRPCInProcess: grpcIPCServer,
}

if config.HSM.Enabled {
o.HSM, err = security.New(&config.HSM)
if err != nil {
return nil, fmt.Errorf("failed to initialize hsm: %w", err)
}
if config.CryptoProvider.HSMConfig.Enabled {
config.CryptoProvider.Type = "hsm"
o.CryptoProvider, err = security.NewCryptoProvider(config.CryptoProvider)
slog.Info("✅crypto provider: HSM")

} else {
config.CryptoProvider.Type = "standard"
o.CryptoProvider, err = security.NewCryptoProvider(config.CryptoProvider)
slog.Info("✅ crypto provider: standard")
}

return &o, nil
Expand Down
6 changes: 3 additions & 3 deletions lib/crypto/asym_decryption.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (
)

type AsymDecryption struct {
privateKey *rsa.PrivateKey
PrivateKey *rsa.PrivateKey
}

// NewAsymDecryption creates and returns a new AsymDecryption.
Expand All @@ -37,11 +37,11 @@ func NewAsymDecryption(privateKeyInPem string) (AsymDecryption, error) {

// Decrypt decrypts ciphertext with private key.
func (asymDecryption AsymDecryption) Decrypt(data []byte) ([]byte, error) {
if asymDecryption.privateKey == nil {
if asymDecryption.PrivateKey == nil {
return nil, errors.New("failed to decrypt, private key is empty")
}

bytes, err := asymDecryption.privateKey.Decrypt(nil,
bytes, err := asymDecryption.PrivateKey.Decrypt(nil,
data,
&rsa.OAEPOptions{Hash: crypto.SHA1})
if err != nil {
Expand Down
Loading