diff --git a/.github/scripts/hsm-init-temporary-keys.sh b/.github/scripts/hsm-init-temporary-keys.sh index ec00765ee6..0412b84857 100755 --- a/.github/scripts/hsm-init-temporary-keys.sh +++ b/.github/scripts/hsm-init-temporary-keys.sh @@ -4,15 +4,15 @@ set -ex -: "${OPENTDF_SERVER_HSM_PIN:=12345}" -: "${OPENTDF_SERVER_HSM_KEYS_EC_LABEL:=development-ec-kas}" -: "${OPENTDF_SERVER_HSM_KEYS_RSA_LABEL:=development-rsa-kas}" +: "${OPENTDF_SERVER_CRYPTOPROVIDER_HSM_PIN:=12345}" +: "${OPENTDF_SERVER_CRYPTOPROVIDER_HSM_KEYS_EC_LABEL:=development-ec-kas}" +: "${OPENTDF_SERVER_CRYPTOPROVIDER_HSM_KEYS_RSA_LABEL:=development-rsa-kas}" -if [ -z "${OPENTDF_SERVER_HSM_MODULEPATH}" ]; then +if [ -z "${OPENTDF_SERVER_CRYPTOPROVIDER_HSM_MODULEPATH}" ]; then if which brew; then - OPENTDF_SERVER_HSM_MODULEPATH=$(brew --prefix)/lib/softhsm/libsofthsm2.so + OPENTDF_SERVER_CRYPTOPROVIDER_HSM_MODULEPATH=$(brew --prefix)/lib/softhsm/libsofthsm2.so else - OPENTDF_SERVER_HSM_MODULEPATH=/lib/softhsm/libsofthsm2.so + OPENTDF_SERVER_CRYPTOPROVIDER_HSM_MODULEPATH=/lib/softhsm/libsofthsm2.so fi fi @@ -21,13 +21,13 @@ if softhsm2-util --show-slots | grep dev-token; then exit 0 fi -softhsm2-util --init-token --free --label "dev-token" --pin "${OPENTDF_SERVER_HSM_PIN}" --so-pin "${OPENTDF_SERVER_HSM_PIN}" -pkcs11-tool --module "${OPENTDF_SERVER_HSM_MODULEPATH}" --login --show-info --list-objects --pin "${OPENTDF_SERVER_HSM_PIN}" +softhsm2-util --init-token --free --label "dev-token" --pin "${OPENTDF_SERVER_CRYPTOPROVIDER_HSM_PIN}" --so-pin "${OPENTDF_SERVER_CRYPTOPROVIDER_HSM_PIN}" +pkcs11-tool --module "${OPENTDF_SERVER_CRYPTOPROVIDER_HSM_MODULEPATH}" --login --show-info --list-objects --pin "${OPENTDF_SERVER_CRYPTOPROVIDER_HSM_PIN}" openssl req -x509 -nodes -newkey RSA:2048 -subj "/CN=kas" -keyout kas-private.pem -out kas-cert.pem -days 365 openssl ecparam -name prime256v1 >ecparams.tmp openssl req -x509 -nodes -newkey ec:ecparams.tmp -subj "/CN=kas" -keyout kas-ec-private.pem -out kas-ec-cert.pem -days 365 -pkcs11-tool --module "${OPENTDF_SERVER_HSM_MODULEPATH}" --login --pin "${OPENTDF_SERVER_HSM_PIN}" --write-object kas-private.pem --type privkey --label "${OPENTDF_SERVER_HSM_KEYS_RSA_LABEL}" -pkcs11-tool --module "${OPENTDF_SERVER_HSM_MODULEPATH}" --login --pin "${OPENTDF_SERVER_HSM_PIN}" --write-object kas-cert.pem --type cert --label "${OPENTDF_SERVER_HSM_KEYS_RSA_LABEL}" +pkcs11-tool --module "${OPENTDF_SERVER_CRYPTOPROVIDER_HSM_MODULEPATH}" --login --pin "${OPENTDF_SERVER_CRYPTOPROVIDER_HSM_PIN}" --write-object kas-private.pem --type privkey --label "${OPENTDF_SERVER_CRYPTOPROVIDER_HSM_KEYS_RSA_LABEL}" +pkcs11-tool --module "${OPENTDF_SERVER_CRYPTOPROVIDER_HSM_MODULEPATH}" --login --pin "${OPENTDF_SERVER_CRYPTOPROVIDER_HSM_PIN}" --write-object kas-cert.pem --type cert --label "${OPENTDF_SERVER_CRYPTOPROVIDER_HSM_KEYS_RSA_LABEL}" # https://manpages.ubuntu.com/manpages/jammy/man1/pkcs11-tool.1.html --usage-derive -pkcs11-tool --module "${OPENTDF_SERVER_HSM_MODULEPATH}" --login --pin "${OPENTDF_SERVER_HSM_PIN}" --write-object kas-ec-private.pem --type privkey --label "${OPENTDF_SERVER_HSM_KEYS_EC_LABEL}" --usage-derive -pkcs11-tool --module "${OPENTDF_SERVER_HSM_MODULEPATH}" --login --pin "${OPENTDF_SERVER_HSM_PIN}" --write-object kas-ec-cert.pem --type cert --label "${OPENTDF_SERVER_HSM_KEYS_EC_LABEL}" +pkcs11-tool --module "${OPENTDF_SERVER_CRYPTOPROVIDER_HSM_MODULEPATH}" --login --pin "${OPENTDF_SERVER_CRYPTOPROVIDER_HSM_PIN}" --write-object kas-ec-private.pem --type privkey --label "${OPENTDF_SERVER_CRYPTOPROVIDER_HSM_KEYS_EC_LABEL}" --usage-derive +pkcs11-tool --module "${OPENTDF_SERVER_CRYPTOPROVIDER_HSM_MODULEPATH}" --login --pin "${OPENTDF_SERVER_CRYPTOPROVIDER_HSM_PIN}" --write-object kas-ec-cert.pem --type cert --label "${OPENTDF_SERVER_CRYPTOPROVIDER_HSM_KEYS_EC_LABEL}" diff --git a/.github/workflows/checks.yaml b/.github/workflows/checks.yaml index 3aa0d6c57a..4d16261e76 100644 --- a/.github/workflows/checks.yaml +++ b/.github/workflows/checks.yaml @@ -37,7 +37,7 @@ jobs: - examples - sdk - service - - lib/crypto + - lib/ocrypto steps: - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 - uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 @@ -70,6 +70,8 @@ jobs: echo "directories.tokendir = $(pwd)/.tmp/tokens" > softhsm2.conf echo "log.level = DEBUG" >> softhsm2.conf echo "SOFTHSM2_CONF=$(pwd)/softhsm2.conf" >> "$GITHUB_ENV" + - if: matrix.directory == 'service' + run: .github/scripts/hsm-init-temporary-keys.sh - run: go test ./... -short working-directory: ${{ matrix.directory }} @@ -99,7 +101,6 @@ jobs: echo "directories.tokendir = $(pwd)/.tmp/tokens" > softhsm2.conf echo "log.level = DEBUG" >> softhsm2.conf echo "SOFTHSM2_CONF=$(pwd)/softhsm2.conf" >> "$GITHUB_ENV" - echo "OPENTDF_SERVER_HSM_PIN=$(openssl rand -hex 4)" >> "$GITHUB_ENV" - run: .github/scripts/hsm-init-temporary-keys.sh - run: docker compose up -d --wait --wait-timeout 240 - run: cp opentdf-example.yaml opentdf.yaml diff --git a/.github/workflows/lint-all.yaml b/.github/workflows/lint-all.yaml index 06dc30bc3d..cf239c4d52 100644 --- a/.github/workflows/lint-all.yaml +++ b/.github/workflows/lint-all.yaml @@ -14,9 +14,9 @@ jobs: strategy: matrix: directory: - - "." - sdk - - lib/crypto + - lib/ocrypto + - service - examples steps: - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 @@ -24,9 +24,9 @@ jobs: with: go-version: "1.21" cache-dependency-path: | - ./go.sum sdk/go.sum examples/go.sum + service/go.sum - run: make go.work - name: golangci-lint # uses: golangci/golangci-lint-action@3a919529898de77ec3da873e3063ca4b10e7f5cc diff --git a/Dockerfile b/Dockerfile index c6f9aa8981..8e5047aaed 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,7 +4,7 @@ WORKDIR /app # dependencies, add local,dependant package here COPY protocol/ protocol/ COPY sdk/ sdk/ -COPY lib/crypto lib/crypto +COPY lib/ocrypto lib/ocrypto COPY service/ service/ COPY examples/ examples/ COPY Makefile ./ diff --git a/Makefile b/Makefile index 36f04d1707..f9e451e458 100644 --- a/Makefile +++ b/Makefile @@ -3,8 +3,8 @@ .PHONY: all build clean docker-build fix go-lint lint proto-generate proto-lint sdk/sdk test toolcheck -MODS=protocol/go lib/crypto sdk service examples -HAND_MODS=lib/crypto sdk service examples +MODS=protocol/go lib/ocrypto sdk service examples +HAND_MODS=lib/ocrypto sdk service examples EXCLUDE_OPENAPI=./service/authorization/idp_plugin.proto diff --git a/README.md b/README.md index 1d0d90887b..311dd32ed3 100644 --- a/README.md +++ b/README.md @@ -86,26 +86,26 @@ For development, we use [the SoftHSM library](https://www.softhsm.org/), which presents a `PKCS #11` interface to on CPU cryptography libraries. ``` -export OPENTDF_SERVER_HSM_PIN=12345 -export OPENTDF_SERVER_HSM_MODULEPATH=/lib/softhsm/libsofthsm2.so -export OPENTDF_SERVER_HSM_KEYS_EC_LABEL=kas-ec -export OPENTDF_SERVER_HSM_KEYS_RSA_LABEL=kas-rsa +export OPENTDF_SERVER_CRYPTOPROVIDER_HSM_PIN=12345 +export OPENTDF_SERVER_CRYPTOPROVIDER_HSM_MODULEPATH=/lib/softhsm/libsofthsm2.so +export OPENTDF_SERVER_CRYPTOPROVIDER_HSM_KEYS_EC_LABEL=kas-ec +export OPENTDF_SERVER_CRYPTOPROVIDER_HSM_KEYS_RSA_LABEL=kas-rsa -pkcs11-tool --module $OPENTDF_SERVER_HSM_MODULEPATH \ - --login --pin ${OPENTDF_SERVER_HSM_PIN} \ +pkcs11-tool --module $OPENTDF_SERVER_CRYPTOPROVIDER_HSM_MODULEPATH \ + --login --pin ${OPENTDF_SERVER_CRYPTOPROVIDER_HSM_PIN} \ --write-object kas-private.pem --type privkey \ --label kas-rsa -pkcs11-tool --module $OPENTDF_SERVER_HSM_MODULEPATH \ - --login --pin ${OPENTDF_SERVER_HSM_PIN} \ +pkcs11-tool --module $OPENTDF_SERVER_CRYPTOPROVIDER_HSM_MODULEPATH \ + --login --pin ${OPENTDF_SERVER_CRYPTOPROVIDER_HSM_PIN} \ --write-object kas-cert.pem --type cert \ --label kas-rsa -pkcs11-tool --module $OPENTDF_SERVER_HSM_MODULEPATH \ - --login --pin ${OPENTDF_SERVER_HSM_PIN} \ +pkcs11-tool --module $OPENTDF_SERVER_CRYPTOPROVIDER_HSM_MODULEPATH \ + --login --pin ${OPENTDF_SERVER_CRYPTOPROVIDER_HSM_PIN} \ --write-object ec-private.pem --type privkey \ --label kas-ec -pkcs11-tool --module $OPENTDF_SERVER_HSM_MODULEPATH \ - --login --pin ${OPENTDF_SERVER_HSM_PIN} \ +pkcs11-tool --module $OPENTDF_SERVER_CRYPTOPROVIDER_HSM_MODULEPATH \ + --login --pin ${OPENTDF_SERVER_CRYPTOPROVIDER_HSM_PIN} \ --write-object ec-cert.pem --type cert \ --label kas-ec ``` diff --git a/examples/go.mod b/examples/go.mod index 50659bb787..95661e3f76 100644 --- a/examples/go.mod +++ b/examples/go.mod @@ -11,7 +11,7 @@ require ( ) replace ( - github.com/opentdf/platform/lib/crypto => ../lib/crypto + github.com/opentdf/platform/lib/ocrypto => ../lib/ocrypto github.com/opentdf/platform/protocol/go => ../protocol/go github.com/opentdf/platform/sdk => ../sdk ) @@ -32,7 +32,7 @@ require ( github.com/lestrrat-go/iter v1.0.2 // indirect github.com/lestrrat-go/jwx/v2 v2.0.21 // indirect github.com/lestrrat-go/option v1.0.1 // indirect - github.com/opentdf/platform/lib/crypto v0.0.0-00010101000000-000000000000 // indirect + github.com/opentdf/platform/lib/ocrypto v0.0.0-00010101000000-000000000000 // indirect github.com/rogpeppe/go-internal v1.12.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/segmentio/asm v1.2.0 // indirect diff --git a/lib/crypto/go.mod b/lib/crypto/go.mod deleted file mode 100644 index 5a452ad3f2..0000000000 --- a/lib/crypto/go.mod +++ /dev/null @@ -1,3 +0,0 @@ -module github.com/opentdf/platform/lib/crypto - -go 1.21.8 diff --git a/lib/crypto/LICENSE b/lib/ocrypto/LICENSE similarity index 100% rename from lib/crypto/LICENSE rename to lib/ocrypto/LICENSE diff --git a/lib/crypto/aes_gcm.go b/lib/ocrypto/aes_gcm.go similarity index 99% rename from lib/crypto/aes_gcm.go rename to lib/ocrypto/aes_gcm.go index 2a1b34aff7..c6e5a0285b 100644 --- a/lib/crypto/aes_gcm.go +++ b/lib/ocrypto/aes_gcm.go @@ -1,4 +1,4 @@ -package crypto +package ocrypto import ( "crypto/aes" diff --git a/lib/crypto/aes_gcm_test.go b/lib/ocrypto/aes_gcm_test.go similarity index 99% rename from lib/crypto/aes_gcm_test.go rename to lib/ocrypto/aes_gcm_test.go index 23364c2dca..186a00f761 100644 --- a/lib/crypto/aes_gcm_test.go +++ b/lib/ocrypto/aes_gcm_test.go @@ -1,4 +1,4 @@ -package crypto +package ocrypto import ( "encoding/hex" diff --git a/lib/crypto/asym_decryption.go b/lib/ocrypto/asym_decryption.go similarity index 89% rename from lib/crypto/asym_decryption.go rename to lib/ocrypto/asym_decryption.go index e817b157b7..ff71eef734 100644 --- a/lib/crypto/asym_decryption.go +++ b/lib/ocrypto/asym_decryption.go @@ -1,4 +1,4 @@ -package crypto +package ocrypto import ( "crypto" @@ -10,7 +10,7 @@ import ( ) type AsymDecryption struct { - privateKey *rsa.PrivateKey + PrivateKey *rsa.PrivateKey } // NewAsymDecryption creates and returns a new AsymDecryption. @@ -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 { diff --git a/lib/crypto/asym_encrypt_decrypt_test.go b/lib/ocrypto/asym_encrypt_decrypt_test.go similarity index 99% rename from lib/crypto/asym_encrypt_decrypt_test.go rename to lib/ocrypto/asym_encrypt_decrypt_test.go index 026f77982c..a40e938348 100644 --- a/lib/crypto/asym_encrypt_decrypt_test.go +++ b/lib/ocrypto/asym_encrypt_decrypt_test.go @@ -1,4 +1,4 @@ -package crypto +package ocrypto import ( "testing" diff --git a/lib/crypto/asym_encryption.go b/lib/ocrypto/asym_encryption.go similarity index 69% rename from lib/crypto/asym_encryption.go rename to lib/ocrypto/asym_encryption.go index ce064e3279..65ef5a115d 100644 --- a/lib/crypto/asym_encryption.go +++ b/lib/ocrypto/asym_encryption.go @@ -1,4 +1,4 @@ -package crypto +package ocrypto import ( "crypto/rand" @@ -12,7 +12,7 @@ import ( ) type AsymEncryption struct { - publicKey *rsa.PublicKey + PublicKey *rsa.PublicKey } // NewAsymEncryption creates and returns a new AsymEncryption. @@ -53,14 +53,35 @@ func NewAsymEncryption(publicKeyInPem string) (AsymEncryption, error) { // Encrypt encrypts data with public key. func (asymEncryption AsymEncryption) Encrypt(data []byte) ([]byte, error) { - if asymEncryption.publicKey == nil { + if asymEncryption.PublicKey == nil { return nil, errors.New("failed to encrypt, public key is empty") } - bytes, err := rsa.EncryptOAEP(sha1.New(), rand.Reader, asymEncryption.publicKey, data, nil) //nolint:gosec // used for padding which is safe + bytes, err := rsa.EncryptOAEP(sha1.New(), rand.Reader, asymEncryption.PublicKey, data, nil) //nolint:gosec // used for padding which is safe if err != nil { return nil, fmt.Errorf("rsa.EncryptOAEP failed: %w", err) } return bytes, nil } + +// PublicKeyInPemFormat Returns public key in pem format. +func (asymEncryption AsymEncryption) PublicKeyInPemFormat() (string, error) { + if asymEncryption.PublicKey == nil { + return "", errors.New("failed to generate PEM formatted public key") + } + + publicKeyBytes, err := x509.MarshalPKIXPublicKey(asymEncryption.PublicKey) + if err != nil { + return "", fmt.Errorf("x509.MarshalPKIXPublicKey failed: %w", err) + } + + publicKeyPem := pem.EncodeToMemory( + &pem.Block{ + Type: "PUBLIC KEY", + Bytes: publicKeyBytes, + }, + ) + + return string(publicKeyPem), nil +} diff --git a/lib/crypto/crypto_utils.go b/lib/ocrypto/crypto_utils.go similarity index 99% rename from lib/crypto/crypto_utils.go rename to lib/ocrypto/crypto_utils.go index 0b280b64b0..3fb100847e 100644 --- a/lib/crypto/crypto_utils.go +++ b/lib/ocrypto/crypto_utils.go @@ -1,4 +1,4 @@ -package crypto +package ocrypto import ( "crypto/hmac" diff --git a/lib/crypto/crypto_utils_test.go b/lib/ocrypto/crypto_utils_test.go similarity index 99% rename from lib/crypto/crypto_utils_test.go rename to lib/ocrypto/crypto_utils_test.go index d233822e0f..a5ebedf24b 100644 --- a/lib/crypto/crypto_utils_test.go +++ b/lib/ocrypto/crypto_utils_test.go @@ -1,4 +1,4 @@ -package crypto +package ocrypto import ( "testing" diff --git a/lib/ocrypto/go.mod b/lib/ocrypto/go.mod new file mode 100644 index 0000000000..f2fb40de4f --- /dev/null +++ b/lib/ocrypto/go.mod @@ -0,0 +1,3 @@ +module github.com/opentdf/platform/lib/ocrypto + +go 1.21.8 diff --git a/lib/crypto/rsa_key_pair.go b/lib/ocrypto/rsa_key_pair.go similarity index 99% rename from lib/crypto/rsa_key_pair.go rename to lib/ocrypto/rsa_key_pair.go index 4586384dc3..b2b6d6b7f9 100644 --- a/lib/crypto/rsa_key_pair.go +++ b/lib/ocrypto/rsa_key_pair.go @@ -1,4 +1,4 @@ -package crypto +package ocrypto import ( "crypto/rand" diff --git a/lib/crypto/rsa_key_pair_test.go b/lib/ocrypto/rsa_key_pair_test.go similarity index 98% rename from lib/crypto/rsa_key_pair_test.go rename to lib/ocrypto/rsa_key_pair_test.go index 92d5ba8241..425527b51d 100644 --- a/lib/crypto/rsa_key_pair_test.go +++ b/lib/ocrypto/rsa_key_pair_test.go @@ -1,4 +1,4 @@ -package crypto +package ocrypto import ( "testing" diff --git a/opentdf-example.yaml b/opentdf-example.yaml index 263aef4703..e94ed605c7 100644 --- a/opentdf-example.yaml +++ b/opentdf-example.yaml @@ -67,16 +67,29 @@ server: grpc: reflectionEnabled: true # Default is false - hsm: - enabled: true - # As configured by hsm-init-temporary-keys.sh - pin: "12345" - slotlabel: "dev-token" - keys: + cryptoProvider: + hsm: + enabled: false + # As configured by hsm-init-temporary-keys.sh + pin: "12345" + slotlabel: "dev-token" + keys: + rsa: + label: development-rsa-kas + ec: + label: development-ec-kas + standard: rsa: - label: development-rsa-kas + 123: + privateKeyPath: kas-private.pem + publicKeyPath: kas-cert.pem + 456: + privateKeyPath: kas-private.pem + publicKeyPath: kas-cert.pem ec: - label: development-ec-kas + 123: + privateKeyPath: kas-ec-private.pem + publicKeyPath: kas-ec-cert.pem port: 8080 opa: embedded: true # Only for local development diff --git a/sdk/auth_config.go b/sdk/auth_config.go index 680c97dea1..c258937634 100644 --- a/sdk/auth_config.go +++ b/sdk/auth_config.go @@ -13,7 +13,7 @@ import ( "time" "github.com/golang-jwt/jwt/v4" - "github.com/opentdf/platform/lib/crypto" + "github.com/opentdf/platform/lib/ocrypto" ) type AuthConfig struct { @@ -35,19 +35,19 @@ type rewrapJWTClaims struct { // NewAuthConfig Create a new instance of authConfig func NewAuthConfig() (*AuthConfig, error) { - rsaKeyPair, err := crypto.NewRSAKeyPair(tdf3KeySize) + rsaKeyPair, err := ocrypto.NewRSAKeyPair(tdf3KeySize) if err != nil { - return nil, fmt.Errorf("crypto.NewRSAKeyPair failed: %w", err) + return nil, fmt.Errorf("ocrypto.NewRSAKeyPair failed: %w", err) } publicKey, err := rsaKeyPair.PublicKeyInPemFormat() if err != nil { - return nil, fmt.Errorf("crypto.PublicKeyInPemFormat failed: %w", err) + return nil, fmt.Errorf("ocrypto.PublicKeyInPemFormat failed: %w", err) } privateKey, err := rsaKeyPair.PrivateKeyInPemFormat() if err != nil { - return nil, fmt.Errorf("crypto.PrivateKeyInPemFormat failed: %w", err) + return nil, fmt.Errorf("ocrypto.PrivateKeyInPemFormat failed: %w", err) } return &AuthConfig{dpopPublicKeyPEM: publicKey, dpopPrivateKeyPEM: privateKey}, nil @@ -77,7 +77,7 @@ func (a *AuthConfig) fetchOIDCAccessToken(ctx context.Context, host, realm, clie } req.Header.Set("Content-Type", "application/x-www-form-urlencoded") - certB64 := crypto.Base64Encode([]byte(a.dpopPublicKeyPEM)) + certB64 := ocrypto.Base64Encode([]byte(a.dpopPublicKeyPEM)) req.Header.Set("X-VirtruPubKey", string(certB64)) client := &http.Client{} @@ -211,19 +211,19 @@ func getWrappedKey(rewrapResponseBody []byte, clientPrivateKey string) ([]byte, return nil, fmt.Errorf("entityWrappedKey is missing in key access object") } - asymDecrypt, err := crypto.NewAsymDecryption(clientPrivateKey) + asymDecrypt, err := ocrypto.NewAsymDecryption(clientPrivateKey) if err != nil { - return nil, fmt.Errorf("crypto.NewAsymDecryption failed: %w", err) + return nil, fmt.Errorf("ocrypto.NewAsymDecryption failed: %w", err) } - entityWrappedKeyDecoded, err := crypto.Base64Decode([]byte(fmt.Sprintf("%v", entityWrappedKey))) + entityWrappedKeyDecoded, err := ocrypto.Base64Decode([]byte(fmt.Sprintf("%v", entityWrappedKey))) if err != nil { - return nil, fmt.Errorf("crypto.Base64Decode failed: %w", err) + return nil, fmt.Errorf("ocrypto.Base64Decode failed: %w", err) } key, err := asymDecrypt.Decrypt(entityWrappedKeyDecoded) if err != nil { - return nil, fmt.Errorf("crypto.Decrypt failed: %w", err) + return nil, fmt.Errorf("ocrypto.Decrypt failed: %w", err) } return key, nil diff --git a/sdk/go.mod b/sdk/go.mod index a69fe5c19d..1c6590c9fc 100644 --- a/sdk/go.mod +++ b/sdk/go.mod @@ -7,7 +7,7 @@ require ( github.com/golang-jwt/jwt/v4 v4.5.0 github.com/google/uuid v1.6.0 github.com/lestrrat-go/jwx/v2 v2.0.21 - github.com/opentdf/platform/lib/crypto v0.0.0-00010101000000-000000000000 + github.com/opentdf/platform/lib/ocrypto v0.0.0-00010101000000-000000000000 github.com/opentdf/platform/protocol/go v0.0.0-00010101000000-000000000000 github.com/stretchr/testify v1.9.0 github.com/testcontainers/testcontainers-go v0.28.0 @@ -16,7 +16,7 @@ require ( ) replace ( - github.com/opentdf/platform/lib/crypto => ../lib/crypto + github.com/opentdf/platform/lib/ocrypto => ../lib/ocrypto github.com/opentdf/platform/protocol/go => ../protocol/go ) diff --git a/sdk/idp_access_token_source.go b/sdk/idp_access_token_source.go index 5dc9f23e13..fac0135db7 100644 --- a/sdk/idp_access_token_source.go +++ b/sdk/idp_access_token_source.go @@ -13,7 +13,7 @@ import ( "github.com/lestrrat-go/jwx/v2/jwa" "github.com/lestrrat-go/jwx/v2/jwk" - "github.com/opentdf/platform/lib/crypto" + "github.com/opentdf/platform/lib/ocrypto" "github.com/opentdf/platform/sdk/auth" "github.com/opentdf/platform/sdk/internal/oauth" ) @@ -22,7 +22,7 @@ const ( dpopKeySize = 2048 ) -func getNewDPoPKey() (string, jwk.Key, *crypto.AsymDecryption, error) { //nolint:ireturn // this is only internal +func getNewDPoPKey() (string, jwk.Key, *ocrypto.AsymDecryption, error) { //nolint:ireturn // this is only internal dpopPrivate, err := rsa.GenerateKey(rand.Reader, dpopKeySize) if err != nil { return "", nil, nil, fmt.Errorf("error creating DPoP keypair: %w", err) @@ -66,7 +66,7 @@ func getNewDPoPKey() (string, jwk.Key, *crypto.AsymDecryption, error) { //nolint return "", nil, nil, fmt.Errorf("error encoding public key to PEM") } - asymDecryption, err := crypto.NewAsymDecryption(dpopPrivatePEM.String()) + asymDecryption, err := ocrypto.NewAsymDecryption(dpopPrivatePEM.String()) if err != nil { return "", nil, nil, fmt.Errorf("error creating asymmetric decryptor: %w", err) } @@ -84,7 +84,7 @@ type IDPAccessTokenSource struct { token *oauth.Token scopes []string dpopKey jwk.Key - asymDecryption crypto.AsymDecryption + asymDecryption ocrypto.AsymDecryption dpopPEM string tokenMutex *sync.Mutex } diff --git a/sdk/kas_client.go b/sdk/kas_client.go index 151ce3f6dd..95fcd19b9f 100644 --- a/sdk/kas_client.go +++ b/sdk/kas_client.go @@ -9,8 +9,8 @@ import ( "github.com/lestrrat-go/jwx/v2/jwk" "github.com/lestrrat-go/jwx/v2/jwt" - "github.com/opentdf/platform/lib/crypto" - kas "github.com/opentdf/platform/protocol/go/kas" + "github.com/opentdf/platform/lib/ocrypto" + "github.com/opentdf/platform/protocol/go/kas" "github.com/opentdf/platform/sdk/auth" "google.golang.org/grpc" ) @@ -23,7 +23,7 @@ type KASClient struct { accessTokenSource auth.AccessTokenSource dialOptions []grpc.DialOption clientPublicKeyPEM string - asymDecryption crypto.AsymDecryption + asymDecryption ocrypto.AsymDecryption } // once the backend moves over we should use the same type that the golang backend uses here @@ -36,24 +36,24 @@ type rewrapRequestBody struct { } func newKASClient(dialOptions []grpc.DialOption, accessTokenSource auth.AccessTokenSource) (*KASClient, error) { - rsaKeyPair, err := crypto.NewRSAKeyPair(tdf3KeySize) + rsaKeyPair, err := ocrypto.NewRSAKeyPair(tdf3KeySize) if err != nil { - return nil, fmt.Errorf("crypto.NewRSAKeyPair failed: %w", err) + return nil, fmt.Errorf("ocrypto.NewRSAKeyPair failed: %w", err) } clientPublicKey, err := rsaKeyPair.PublicKeyInPemFormat() if err != nil { - return nil, fmt.Errorf("crypto.PublicKeyInPemFormat failed: %w", err) + return nil, fmt.Errorf("ocrypto.PublicKeyInPemFormat failed: %w", err) } clientPrivateKey, err := rsaKeyPair.PrivateKeyInPemFormat() if err != nil { - return nil, fmt.Errorf("crypto.PrivateKeyInPemFormat failed: %w", err) + return nil, fmt.Errorf("ocrypto.PrivateKeyInPemFormat failed: %w", err) } - asymDecryption, err := crypto.NewAsymDecryption(clientPrivateKey) + asymDecryption, err := ocrypto.NewAsymDecryption(clientPrivateKey) if err != nil { - return nil, fmt.Errorf("crypto.NewAsymDecryption failed: %w", err) + return nil, fmt.Errorf("ocrypto.NewAsymDecryption failed: %w", err) } return &KASClient{ diff --git a/sdk/kas_client_test.go b/sdk/kas_client_test.go index d655caccbf..8ab9192919 100644 --- a/sdk/kas_client_test.go +++ b/sdk/kas_client_test.go @@ -9,13 +9,13 @@ import ( "github.com/lestrrat-go/jwx/v2/jwa" "github.com/lestrrat-go/jwx/v2/jwk" "github.com/lestrrat-go/jwx/v2/jwt" - "github.com/opentdf/platform/lib/crypto" + "github.com/opentdf/platform/lib/ocrypto" "github.com/opentdf/platform/sdk/auth" ) type FakeAccessTokenSource struct { dpopKey jwk.Key - asymDecryption crypto.AsymDecryption + asymDecryption ocrypto.AsymDecryption accessToken string } @@ -27,9 +27,9 @@ func (fake FakeAccessTokenSource) MakeToken(tokenMaker func(jwk.Key) ([]byte, er } func getTokenSource(t *testing.T) FakeAccessTokenSource { - dpopKey, _ := crypto.NewRSAKeyPair(2048) + dpopKey, _ := ocrypto.NewRSAKeyPair(2048) dpopPEM, _ := dpopKey.PrivateKeyInPemFormat() - decryption, _ := crypto.NewAsymDecryption(dpopPEM) + decryption, _ := ocrypto.NewAsymDecryption(dpopPEM) dpopJWK, err := jwk.ParseKey([]byte(dpopPEM), jwk.WithPEM(true)) if err != nil { t.Fatalf("error creating JWK: %v", err) @@ -91,7 +91,7 @@ func TestCreatingRequest(t *testing.T) { t.Fatalf("error unmarshaling request body: %v", err) } - _, err = crypto.NewAsymEncryption(requestBody["clientPublicKey"].(string)) + _, err = ocrypto.NewAsymEncryption(requestBody["clientPublicKey"].(string)) if err != nil { t.Fatalf("NewAsymEncryption failed, incorrect public key include: %v", err) } diff --git a/sdk/tdf.go b/sdk/tdf.go index 13d423db9d..5894aa02aa 100644 --- a/sdk/tdf.go +++ b/sdk/tdf.go @@ -11,7 +11,7 @@ import ( "strings" "github.com/google/uuid" - "github.com/opentdf/platform/lib/crypto" + "github.com/opentdf/platform/lib/ocrypto" "github.com/opentdf/platform/sdk/internal/archive" ) @@ -63,7 +63,7 @@ type Reader struct { tdfReader archive.TDFReader unwrapper Unwrapper cursor int64 - aesGcm crypto.AesGcm + aesGcm ocrypto.AesGcm payloadSize int64 payloadKey []byte } @@ -71,7 +71,7 @@ type Reader struct { type TDFObject struct { manifest Manifest size int64 - aesGcm crypto.AesGcm + aesGcm ocrypto.AesGcm payloadKey [kKeySize]byte } @@ -167,7 +167,7 @@ func (s SDK) CreateTDF(writer io.Writer, reader io.ReadSeeker, opts ...TDFOption aggregateHash += segmentSig segmentInfo := Segment{ - Hash: string(crypto.Base64Encode([]byte(segmentSig))), + Hash: string(ocrypto.Base64Encode([]byte(segmentSig))), Size: readSize, EncryptedSize: int64(len(cipherData)), } @@ -184,7 +184,7 @@ func (s SDK) CreateTDF(writer io.Writer, reader io.ReadSeeker, opts ...TDFOption return nil, fmt.Errorf("splitKey.GetSignaturefailed: %w", err) } - sig := string(crypto.Base64Encode([]byte(rootSignature))) + sig := string(ocrypto.Base64Encode([]byte(rootSignature))) tdfObject.manifest.EncryptionInformation.IntegrityInformation.RootSignature.Signature = sig integrityAlgStr := gmacIntegrityAlgorithm @@ -256,16 +256,16 @@ func (t *TDFObject) prepareManifest(tdfConfig TDFConfig) error { //nolint:funlen return fmt.Errorf("json.Marshal failed:%w", err) } - base64PolicyObject := crypto.Base64Encode(policyObjectAsStr) + base64PolicyObject := ocrypto.Base64Encode(policyObjectAsStr) symKeys := make([][]byte, 0, len(tdfConfig.kasInfoList)) for _, kasInfo := range tdfConfig.kasInfoList { if len(kasInfo.PublicKey) == 0 { return errKasPubKeyMissing } - symKey, err := crypto.RandomBytes(kKeySize) + symKey, err := ocrypto.RandomBytes(kKeySize) if err != nil { - return fmt.Errorf("crypto.RandomBytes failed:%w", err) + return fmt.Errorf("ocrypto.RandomBytes failed:%w", err) } keyAccess := KeyAccess{} @@ -274,37 +274,37 @@ func (t *TDFObject) prepareManifest(tdfConfig TDFConfig) error { //nolint:funlen keyAccess.Protocol = kKasProtocol // add policyBinding - policyBinding := hex.EncodeToString(crypto.CalculateSHA256Hmac(symKey, base64PolicyObject)) - keyAccess.PolicyBinding = string(crypto.Base64Encode([]byte(policyBinding))) + policyBinding := hex.EncodeToString(ocrypto.CalculateSHA256Hmac(symKey, base64PolicyObject)) + keyAccess.PolicyBinding = string(ocrypto.Base64Encode([]byte(policyBinding))) // wrap the key with kas public key - asymEncrypt, err := crypto.NewAsymEncryption(kasInfo.PublicKey) + asymEncrypt, err := ocrypto.NewAsymEncryption(kasInfo.PublicKey) if err != nil { - return fmt.Errorf("crypto.NewAsymEncryption failed:%w", err) + return fmt.Errorf("ocrypto.NewAsymEncryption failed:%w", err) } wrappedKey, err := asymEncrypt.Encrypt(symKey) if err != nil { - return fmt.Errorf("crypto.AsymEncryption.encrypt failed:%w", err) + return fmt.Errorf("ocrypto.AsymEncryption.encrypt failed:%w", err) } - keyAccess.WrappedKey = string(crypto.Base64Encode(wrappedKey)) + keyAccess.WrappedKey = string(ocrypto.Base64Encode(wrappedKey)) // add meta data if len(tdfConfig.metaData) > 0 { - gcm, err := crypto.NewAESGcm(symKey) + gcm, err := ocrypto.NewAESGcm(symKey) if err != nil { - return fmt.Errorf("crypto.NewAESGcm failed:%w", err) + return fmt.Errorf("ocrypto.NewAESGcm failed:%w", err) } encryptedMetaData, err := gcm.Encrypt([]byte(tdfConfig.metaData)) if err != nil { - return fmt.Errorf("crypto.AesGcm.encrypt failed:%w", err) + return fmt.Errorf("ocrypto.AesGcm.encrypt failed:%w", err) } - iv := encryptedMetaData[:crypto.GcmStandardNonceSize] + iv := encryptedMetaData[:ocrypto.GcmStandardNonceSize] metadata := EncryptedMetadata{ - Cipher: string(crypto.Base64Encode(encryptedMetaData)), - Iv: string(crypto.Base64Encode(iv)), + Cipher: string(ocrypto.Base64Encode(encryptedMetaData)), + Iv: string(ocrypto.Base64Encode(iv)), } metadataJSON, err := json.Marshal(metadata) @@ -312,7 +312,7 @@ func (t *TDFObject) prepareManifest(tdfConfig TDFConfig) error { //nolint:funlen return fmt.Errorf(" json.Marshal failed:%w", err) } - keyAccess.EncryptedMetadata = string(crypto.Base64Encode(metadataJSON)) + keyAccess.EncryptedMetadata = string(ocrypto.Base64Encode(metadataJSON)) } symKeys = append(symKeys, symKey) @@ -329,9 +329,9 @@ func (t *TDFObject) prepareManifest(tdfConfig TDFConfig) error { //nolint:funlen } } - gcm, err := crypto.NewAESGcm(t.payloadKey[:]) + gcm, err := ocrypto.NewAESGcm(t.payloadKey[:]) if err != nil { - return fmt.Errorf(" crypto.NewAESGcm failed:%w", err) + return fmt.Errorf(" ocrypto.NewAESGcm failed:%w", err) } t.manifest = manifest @@ -434,7 +434,7 @@ func (r *Reader) WriteTo(writer io.Writer) (int64, error) { return totalBytes, fmt.Errorf("splitKey.GetSignaturefailed: %w", err) } - if seg.Hash != string(crypto.Base64Encode([]byte(payloadSig))) { + if seg.Hash != string(ocrypto.Base64Encode([]byte(payloadSig))) { return totalBytes, errSegSigValidation } @@ -520,7 +520,7 @@ func (r *Reader) ReadAt(buf []byte, offset int64) (int, error) { //nolint:funlen return 0, fmt.Errorf("splitKey.GetSignaturefailed: %w", err) } - if seg.Hash != string(crypto.Base64Encode([]byte(payloadSig))) { + if seg.Hash != string(ocrypto.Base64Encode([]byte(payloadSig))) { return 0, errSegSigValidation } @@ -574,9 +574,9 @@ func (r *Reader) UnencryptedMetadata() ([]byte, error) { // Otherwise, returns an error. func (r *Reader) Policy() (PolicyObject, error) { policyObj := PolicyObject{} - policy, err := crypto.Base64Decode([]byte(r.manifest.Policy)) + policy, err := ocrypto.Base64Decode([]byte(r.manifest.Policy)) if err != nil { - return policyObj, fmt.Errorf("crypto.Base64Decode failed:%w", err) + return policyObj, fmt.Errorf("ocrypto.Base64Decode failed:%w", err) } err = json.Unmarshal(policy, &policyObj) @@ -589,9 +589,9 @@ func (r *Reader) Policy() (PolicyObject, error) { // DataAttributes return the data attributes present in tdf. func (r *Reader) DataAttributes() ([]string, error) { - policy, err := crypto.Base64Decode([]byte(r.manifest.Policy)) + policy, err := ocrypto.Base64Decode([]byte(r.manifest.Policy)) if err != nil { - return nil, fmt.Errorf("crypto.Base64Decode failed:%w", err) + return nil, fmt.Errorf("ocrypto.Base64Decode failed:%w", err) } policyObj := PolicyObject{} @@ -624,14 +624,14 @@ func (r *Reader) doPayloadKeyUnwrap() error { //nolint:gocognit } if len(keyAccessObj.EncryptedMetadata) != 0 { - gcm, err := crypto.NewAESGcm(wrappedKey) + gcm, err := ocrypto.NewAESGcm(wrappedKey) if err != nil { - return fmt.Errorf("crypto.NewAESGcm failed:%w", err) + return fmt.Errorf("ocrypto.NewAESGcm failed:%w", err) } - decodedMetaData, err := crypto.Base64Decode([]byte(keyAccessObj.EncryptedMetadata)) + decodedMetaData, err := ocrypto.Base64Decode([]byte(keyAccessObj.EncryptedMetadata)) if err != nil { - return fmt.Errorf("crypto.Base64Decode failed:%w", err) + return fmt.Errorf("ocrypto.Base64Decode failed:%w", err) } metadata := EncryptedMetadata{} @@ -641,10 +641,10 @@ func (r *Reader) doPayloadKeyUnwrap() error { //nolint:gocognit } encodedCipherText := metadata.Cipher - cipherText, _ := crypto.Base64Decode([]byte(encodedCipherText)) + cipherText, _ := ocrypto.Base64Decode([]byte(encodedCipherText)) metaData, err := gcm.Decrypt(cipherText) if err != nil { - return fmt.Errorf("crypto.AesGcm.encrypt failed:%w", err) + return fmt.Errorf("ocrypto.AesGcm.encrypt failed:%w", err) } unencryptedMetadata = metaData @@ -672,9 +672,9 @@ func (r *Reader) doPayloadKeyUnwrap() error { //nolint:gocognit payloadSize += seg.Size } - gcm, err := crypto.NewAESGcm(payloadKey[:]) + gcm, err := ocrypto.NewAESGcm(payloadKey[:]) if err != nil { - return fmt.Errorf(" crypto.NewAESGcm failed:%w", err) + return fmt.Errorf("ocrypto.NewAESGcm failed:%w", err) } r.payloadSize = payloadSize @@ -688,7 +688,7 @@ func (r *Reader) doPayloadKeyUnwrap() error { //nolint:gocognit // calculateSignature calculate signature of data of the given algorithm. func calculateSignature(data []byte, secret []byte, alg IntegrityAlgorithm) (string, error) { if alg == HS256 { - hmac := crypto.CalculateSHA256Hmac(secret, data) + hmac := ocrypto.CalculateSHA256Hmac(secret, data) return hex.EncodeToString(hmac), nil } if kGMACPayloadLength > len(data) { @@ -705,9 +705,9 @@ func validateRootSignature(manifest Manifest, secret []byte) (bool, error) { aggregateHash := &bytes.Buffer{} for _, segment := range manifest.EncryptionInformation.IntegrityInformation.Segments { - decodedHash, err := crypto.Base64Decode([]byte(segment.Hash)) + decodedHash, err := ocrypto.Base64Decode([]byte(segment.Hash)) if err != nil { - return false, fmt.Errorf("crypto.Base64Decode failed:%w", err) + return false, fmt.Errorf("ocrypto.Base64Decode failed:%w", err) } aggregateHash.Write(decodedHash) @@ -723,7 +723,7 @@ func validateRootSignature(manifest Manifest, secret []byte) (bool, error) { return false, fmt.Errorf("splitkey.getSignature failed:%w", err) } - if rootSigValue == string(crypto.Base64Encode([]byte(sig))) { + if rootSigValue == string(ocrypto.Base64Encode([]byte(sig))) { return true, nil } diff --git a/sdk/tdf_config.go b/sdk/tdf_config.go index 67ec7b2ca4..b98ee506d9 100644 --- a/sdk/tdf_config.go +++ b/sdk/tdf_config.go @@ -3,7 +3,7 @@ package sdk import ( "fmt" - "github.com/opentdf/platform/lib/crypto" + "github.com/opentdf/platform/lib/ocrypto" ) const ( @@ -55,19 +55,19 @@ type TDFConfig struct { // NewTDFConfig CreateTDF a new instance of tdf config. func NewTDFConfig(opt ...TDFOption) (*TDFConfig, error) { - rsaKeyPair, err := crypto.NewRSAKeyPair(tdf3KeySize) + rsaKeyPair, err := ocrypto.NewRSAKeyPair(tdf3KeySize) if err != nil { - return nil, fmt.Errorf("crypto.NewRSAKeyPair failed: %w", err) + return nil, fmt.Errorf("ocrypto.NewRSAKeyPair failed: %w", err) } publicKey, err := rsaKeyPair.PublicKeyInPemFormat() if err != nil { - return nil, fmt.Errorf("crypto.PublicKeyInPemFormat failed: %w", err) + return nil, fmt.Errorf("ocrypto.PublicKeyInPemFormat failed: %w", err) } privateKey, err := rsaKeyPair.PublicKeyInPemFormat() if err != nil { - return nil, fmt.Errorf("crypto.PublicKeyInPemFormat failed: %w", err) + return nil, fmt.Errorf("ocrypto.PublicKeyInPemFormat failed: %w", err) } c := &TDFConfig{ diff --git a/sdk/tdf_test.go b/sdk/tdf_test.go index 7e7b0bdc03..788750a987 100644 --- a/sdk/tdf_test.go +++ b/sdk/tdf_test.go @@ -18,7 +18,8 @@ import ( "testing" "github.com/golang-jwt/jwt/v4" - "github.com/opentdf/platform/lib/crypto" + "github.com/opentdf/platform/lib/ocrypto" + "github.com/stretchr/testify/assert" ) @@ -709,26 +710,26 @@ func runKas() (string, func(), *SDK) { return "grpc://localhost:8080", func() {}, sdk } - signingKeyPair, err := crypto.NewRSAKeyPair(tdf3KeySize) + signingKeyPair, err := ocrypto.NewRSAKeyPair(tdf3KeySize) if err != nil { - panic(fmt.Sprintf("crypto.NewRSAKeyPair: %v", err)) + panic(fmt.Sprintf("ocrypto.NewRSAKeyPair: %v", err)) } signingPubKey, err := signingKeyPair.PublicKeyInPemFormat() if err != nil { - panic(fmt.Sprintf("crypto.PublicKeyInPemFormat failed: %v", err)) + panic(fmt.Sprintf("ocrypto.PublicKeyInPemFormat failed: %v", err)) } signingPrivateKey, err := signingKeyPair.PrivateKeyInPemFormat() if err != nil { - panic(fmt.Sprintf("crypto.PrivateKeyInPemFormat failed: %v", err)) + panic(fmt.Sprintf("ocrypto.PrivateKeyInPemFormat failed: %v", err)) } accessTokenBytes := make([]byte, 10) if _, err := rand.Read(accessTokenBytes); err != nil { panic("failed to create random access token") } - accessToken := crypto.Base64Encode(accessTokenBytes) + accessToken := ocrypto.Base64Encode(accessTokenBytes) server := httptest.NewServer(http.HandlerFunc(getKASRequestHandler(string(accessToken), signingPubKey))) @@ -790,7 +791,7 @@ func getKASRequestHandler(expectedAccessToken, //nolint:gocognit // KAS is prett entityWrappedKey := getRewrappedKey(rewrapRequest) response, err := json.Marshal(map[string]string{ - kEntityWrappedKey: string(crypto.Base64Encode(entityWrappedKey)), + kEntityWrappedKey: string(ocrypto.Base64Encode(entityWrappedKey)), }) if err != nil { panic(fmt.Sprintf("json.Marshal failed: %v", err)) @@ -812,26 +813,26 @@ func getRewrappedKey(rewrapRequest string) []byte { if err != nil { panic(fmt.Sprintf("json.Unmarshal failed: %v", err)) } - wrappedKey, err := crypto.Base64Decode([]byte(bodyData.WrappedKey)) + wrappedKey, err := ocrypto.Base64Decode([]byte(bodyData.WrappedKey)) if err != nil { - panic(fmt.Sprintf("crypto.Base64Decode failed: %v", err)) + panic(fmt.Sprintf("ocrypto.Base64Decode failed: %v", err)) } kasPrivateKey := strings.ReplaceAll(mockKasPrivateKey, "\n\t", "\n") - asymDecrypt, err := crypto.NewAsymDecryption(kasPrivateKey) + asymDecrypt, err := ocrypto.NewAsymDecryption(kasPrivateKey) if err != nil { - panic(fmt.Sprintf("crypto.NewAsymDecryption failed: %v", err)) + panic(fmt.Sprintf("ocrypto.NewAsymDecryption failed: %v", err)) } symmetricKey, err := asymDecrypt.Decrypt(wrappedKey) if err != nil { - panic(fmt.Sprintf("crypto.Decrypt failed: %v", err)) + panic(fmt.Sprintf("ocrypto.Decrypt failed: %v", err)) } - asymEncrypt, err := crypto.NewAsymEncryption(bodyData.ClientPublicKey) + asymEncrypt, err := ocrypto.NewAsymEncryption(bodyData.ClientPublicKey) if err != nil { - panic(fmt.Sprintf("crypto.NewAsymEncryption failed: %v", err)) + panic(fmt.Sprintf("ocrypto.NewAsymEncryption failed: %v", err)) } entityWrappedKey, err := asymEncrypt.Encrypt(symmetricKey) if err != nil { - panic(fmt.Sprintf("crypto.encrypt failed: %v", err)) + panic(fmt.Sprintf("ocrypto.encrypt failed: %v", err)) } return entityWrappedKey } diff --git a/service/go.mod b/service/go.mod index f8f11f8514..9e3517a720 100644 --- a/service/go.mod +++ b/service/go.mod @@ -19,6 +19,7 @@ require ( github.com/lestrrat-go/jwx/v2 v2.0.21 github.com/miekg/pkcs11 v1.1.1 github.com/open-policy-agent/opa v0.62.1 + github.com/opentdf/platform/lib/ocrypto v0.0.0-00010101000000-000000000000 github.com/opentdf/platform/protocol/go v0.0.0-00010101000000-000000000000 github.com/opentdf/platform/sdk v0.0.0-00010101000000-000000000000 github.com/pressly/goose/v3 v3.19.1 @@ -55,11 +56,10 @@ require ( github.com/docker/go-connections v0.5.0 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect - github.com/opentdf/platform/lib/crypto v0.0.0-00010101000000-000000000000 // indirect ) replace ( - github.com/opentdf/platform/lib/crypto => ../lib/crypto + github.com/opentdf/platform/lib/ocrypto => ../lib/ocrypto github.com/opentdf/platform/protocol/go => ../protocol/go github.com/opentdf/platform/sdk => ../sdk ) diff --git a/service/internal/security/crypto_provider.go b/service/internal/security/crypto_provider.go new file mode 100644 index 0000000000..519c1068bd --- /dev/null +++ b/service/internal/security/crypto_provider.go @@ -0,0 +1,34 @@ +package security + +import "crypto" + +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) + RSAPublicKeyAsJSON(keyID string) (string, error) + RSADecrypt(hash crypto.Hash, keyID string, keyLabel string, ciphertext []byte) ([]byte, error) + + ECPublicKey(keyID string) (string, error) + GenerateNanoTDFSymmetricKey(ephemeralPublicKeyBytes []byte) ([]byte, error) + GenerateEphemeralKasKeys() (PrivateKeyEC, []byte, error) + GenerateNanoTDFSessionKey(privateKeyHandle PrivateKeyEC, ephemeralPublicKey []byte) ([]byte, error) + Close() +} + +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) + } +} diff --git a/service/internal/security/hsm.go b/service/internal/security/hsm.go index 240c137e9d..bddc7589d5 100644 --- a/service/internal/security/hsm.go +++ b/service/internal/security/hsm.go @@ -6,6 +6,8 @@ import ( "crypto/rsa" "crypto/sha256" "crypto/x509" + "encoding/json" + "encoding/pem" "errors" "fmt" "io" @@ -14,15 +16,21 @@ import ( "os/exec" "strings" + "github.com/lestrrat-go/jwx/v2/jwk" + "github.com/miekg/pkcs11" "golang.org/x/crypto/hkdf" ) const ( - ErrHSMUnexpected = Error("hsm unexpected") - ErrHSMDecrypt = Error("hsm decrypt error") - ErrHSMNotFound = Error("hsm unavailable") - ErrKeyConfig = Error("key configuration error") + ErrCertNotFound = Error("not found") + 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 @@ -323,17 +331,16 @@ func (h *HSMSession) loadKeys(keys map[string]KeyInfo) error { return nil } -func (s *HSMSession) Destroy() { - if s == nil { +func (h *HSMSession) Close() { + if h == nil { return } - ctx := s.ctx - sh := s.sh + ctx := h.ctx + sh := h.sh if err := ctx.Logout(sh); err != nil { slog.Error("pkcs11 error logging out", "err", err) } - s.destroy() - s = nil + h.destroy() } func (h *HSMSession) findKey(class uint, label string) (pkcs11.ObjectHandle, error) { @@ -493,25 +500,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: @@ -529,7 +517,7 @@ func hashToPKCS11(hashFunction crypto.Hash) (hashAlg uint, mgfAlg uint, hashLen } } -func (h *HSMSession) GenerateNanoTDFSymmetricKey(ephemeralPublicKeyBytes []byte, key PrivateKeyEC) ([]byte, error) { +func (h *HSMSession) GenerateNanoTDFSymmetricKey(ephemeralPublicKeyBytes []byte) ([]byte, error) { template := []*pkcs11.Attribute{ pkcs11.NewAttribute(pkcs11.CKA_TOKEN, false), pkcs11.NewAttribute(pkcs11.CKA_CLASS, pkcs11.CKO_SECRET_KEY), @@ -548,7 +536,7 @@ func (h *HSMSession) GenerateNanoTDFSymmetricKey(ephemeralPublicKeyBytes []byte, pkcs11.NewMechanism(pkcs11.CKM_ECDH1_DERIVE, ¶ms), } - handle, err := h.ctx.DeriveKey(h.sh, mech, pkcs11.ObjectHandle(key), template) + handle, err := h.ctx.DeriveKey(h.sh, mech, pkcs11.ObjectHandle(h.EC.PrivateKey), template) if err != nil { return nil, fmt.Errorf("failed to derive symmetric key: %w", err) } @@ -659,6 +647,89 @@ 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)) + + if h.RSA == nil { + return "", ErrCertNotFound + } + + 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) RSAPublicKeyAsJSON(keyID string) (string, error) { + // TODO: For now ignore the key id + slog.Info("⚠️ Ignoring the", slog.String("key id", keyID)) + + rsaPublicKeyJwk, err := jwk.FromRaw(h.RSA.PublicKey) + if err != nil { + return "", fmt.Errorf("jwk.FromRaw: %w", err) + } + + jsonPublicKey, err := json.Marshal(rsaPublicKeyJwk) + if err != nil { + return "", fmt.Errorf("jwk.FromRaw: %w", err) + } + + return string(jsonPublicKey), nil +} + +func (h *HSMSession) ECPublicKey(string) (string, error) { + pubkeyBytes, err := x509.MarshalPKIXPublicKey(h.EC.PublicKey) + if err != nil { + return "", errors.Join(ErrPublicKeyMarshal, err) + } + pubkeyPem := pem.EncodeToMemory( + &pem.Block{ + Type: "PUBLIC KEY", + Headers: nil, + Bytes: pubkeyBytes, + }, + ) + + return string(pubkeyPem), nil +} + +func (h *HSMSession) RSADecrypt(hash crypto.Hash, keyID string, keyLabel string, ciphertext []byte) ([]byte, error) { + // TODO: For now ignore the key id + slog.Info("⚠️ Ignoring the", slog.String("key id", keyID)) + + 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")) diff --git a/service/internal/security/hsm_test.go b/service/internal/security/hsm_test.go index d66b984199..a9b4c73aaa 100644 --- a/service/internal/security/hsm_test.go +++ b/service/internal/security/hsm_test.go @@ -99,18 +99,13 @@ 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")) + decrypted, err := session.RSADecrypt(crypto.BLAKE2b_384, "unknown", "sample label", []byte("sample ciphertext")) t.Log(err) t.Log(decrypted) diff --git a/service/internal/security/standard_crypto.go b/service/internal/security/standard_crypto.go new file mode 100644 index 0000000000..8f4d310c02 --- /dev/null +++ b/service/internal/security/standard_crypto.go @@ -0,0 +1,151 @@ +package security + +import ( + "crypto" + "encoding/json" + "errors" + "fmt" + "log/slog" + "os" + + "github.com/lestrrat-go/jwx/v2/jwk" + "github.com/opentdf/platform/lib/ocrypto" +) + +var ( + errNotImplemented = errors.New("standard crypto for nano not implemented") + 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 ocrypto.AsymDecryption + asymEncryption ocrypto.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 := ocrypto.NewAsymDecryption(string(privatePemData)) + if err != nil { + return nil, fmt.Errorf("ocrypto.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 := ocrypto.NewAsymEncryption(string(publicPemData)) + if err != nil { + return nil, fmt.Errorf("ocrypto.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(string) (string, error) { + return "", nil +} + +func (s StandardCrypto) RSADecrypt(_ crypto.Hash, keyID string, _ 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 +} + +func (s StandardCrypto) RSAPublicKeyAsJSON(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)) + + rsaPublicKeyJwk, err := jwk.FromRaw(s.rsaKeys[0].asymEncryption.PublicKey) + if err != nil { + return "", fmt.Errorf("jwk.FromRaw: %w", err) + } + + jsonPublicKey, err := json.Marshal(rsaPublicKeyJwk) + if err != nil { + return "", fmt.Errorf("jwk.FromRaw: %w", err) + } + + return string(jsonPublicKey), nil +} + +func (s StandardCrypto) GenerateNanoTDFSymmetricKey([]byte) ([]byte, error) { + return nil, errNotImplemented +} + +func (s StandardCrypto) GenerateEphemeralKasKeys() (PrivateKeyEC, []byte, error) { + return 0, nil, errNotImplemented +} + +func (s StandardCrypto) GenerateNanoTDFSessionKey(PrivateKeyEC, []byte) ([]byte, error) { + return nil, errNotImplemented +} + +func (s StandardCrypto) Close() { +} diff --git a/service/internal/server/server.go b/service/internal/server/server.go index 35ea5ddbce..1af6c98007 100644 --- a/service/internal/server/server.go +++ b/service/internal/server/server.go @@ -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"` @@ -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 } /* @@ -128,11 +128,22 @@ func NewOpenTDFServer(config Config, d *db.Client) (*OpenTDFServer, error) { GRPCInProcess: grpcIPCServer, } - if config.HSM.Enabled { - o.HSM, err = security.New(&config.HSM) + if config.CryptoProvider.HSMConfig.Enabled { + config.CryptoProvider.Type = "hsm" + o.CryptoProvider, err = security.NewCryptoProvider(config.CryptoProvider) if err != nil { - return nil, fmt.Errorf("failed to initialize hsm: %w", err) + return nil, fmt.Errorf("HSM security.NewCryptoProvider: %w", err) } + + slog.Info("✅crypto provider: HSM") + } else { + config.CryptoProvider.Type = "standard" + o.CryptoProvider, err = security.NewCryptoProvider(config.CryptoProvider) + if err != nil { + return nil, fmt.Errorf("standard security.NewCryptoProvider: %w", err) + } + + slog.Info("✅ crypto provider: standard") } return &o, nil diff --git a/service/kas/access/provider.go b/service/kas/access/provider.go index 3f85263ee0..e6c36c610c 100644 --- a/service/kas/access/provider.go +++ b/service/kas/access/provider.go @@ -16,8 +16,9 @@ const ( type Provider struct { kaspb.AccessServiceServer - URI url.URL `json:"uri"` - SDK *otdf.SDK - Session security.HSMSession - OIDCVerifier *oidc.IDTokenVerifier + URI url.URL `json:"uri"` + SDK *otdf.SDK + AttributeSvc *url.URL + CryptoProvider security.CryptoProvider + OIDCVerifier *oidc.IDTokenVerifier } diff --git a/service/kas/access/publicKey.go b/service/kas/access/publicKey.go index 5bb19efdee..8f63df8e2f 100644 --- a/service/kas/access/publicKey.go +++ b/service/kas/access/publicKey.go @@ -5,12 +5,10 @@ import ( "crypto/ecdsa" "crypto/rsa" "crypto/x509" - "encoding/json" "encoding/pem" "errors" "log/slog" - "github.com/lestrrat-go/jwx/v2/jwk" kaspb "github.com/opentdf/platform/protocol/go/kas" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" @@ -28,17 +26,17 @@ func (p *Provider) LegacyPublicKey(ctx context.Context, in *kaspb.LegacyPublicKe var pem string var err error if algorithm == algorithmEc256 { - if p.Session.EC == nil || p.Session.EC.Certificate == nil { - return nil, err404("not found") + pem, err = p.CryptoProvider.ECPublicKey("unknown") + if err != nil { + slog.ErrorContext(ctx, "CryptoProvider.ECPublicKey failed", "err", err) + return nil, errors.Join(ErrConfig, status.Error(codes.Internal, "configuration error")) } - pem, err = exportCertificateAsPemStr(p.Session.EC.Certificate) - slog.DebugContext(ctx, "Legacy EC Public Key Handler found", "cert", pem) } else { - if p.Session.RSA == nil || p.Session.RSA.Certificate == nil { - return nil, err404("not found") + pem, err = p.CryptoProvider.RSAPublicKey("unknown") + if err != nil { + slog.ErrorContext(ctx, "CryptoProvider.RSAPublicKey failed", "err", err) + return nil, errors.Join(ErrConfig, status.Error(codes.Internal, "configuration error")) } - pem, err = exportCertificateAsPemStr(p.Session.RSA.Certificate) - slog.DebugContext(ctx, "Legacy RSA CERT Handler found", "cert", pem) } if err != nil { slog.ErrorContext(ctx, "unable to generate PEM", "err", err) @@ -50,53 +48,40 @@ func (p *Provider) LegacyPublicKey(ctx context.Context, in *kaspb.LegacyPublicKe func (p *Provider) PublicKey(ctx context.Context, in *kaspb.PublicKeyRequest) (*kaspb.PublicKeyResponse, error) { algorithm := in.GetAlgorithm() if algorithm == algorithmEc256 { - if p.Session.EC == nil { - return nil, err404("not found") - } - ecPublicKeyPem, err := exportEcPublicKeyAsPemStr(p.Session.EC.PublicKey) + ecPublicKeyPem, err := p.CryptoProvider.ECPublicKey("unknown") if err != nil { - slog.ErrorContext(ctx, "EC public key from PKCS11", "err", err) + slog.ErrorContext(ctx, "CryptoProvider.ECPublicKey failed", "err", err) return nil, errors.Join(ErrConfig, status.Error(codes.Internal, "configuration error")) } - slog.DebugContext(ctx, "EC Public Key Handler found", "cert", ecPublicKeyPem) + return &kaspb.PublicKeyResponse{PublicKey: ecPublicKeyPem}, nil } - if p.Session.RSA == nil { - return nil, err404("not found") - } if in.GetFmt() == "jwk" { - rsaPublicKeyJwk, err := jwk.FromRaw(p.Session.RSA.PublicKey) - if err != nil { - slog.ErrorContext(ctx, "failed to parse JWK", "err", err) - return nil, errors.Join(ErrConfig, status.Error(codes.Internal, "configuration error")) - } - // Keys can be serialized back to JSON - jsonPublicKey, err := json.Marshal(rsaPublicKeyJwk) + rsaPublicKeyPem, err := p.CryptoProvider.RSAPublicKeyAsJSON("unknown") if err != nil { - slog.ErrorContext(ctx, "failed to marshal JWK", "err", err) + slog.ErrorContext(ctx, "CryptoProvider.RSAPublicKey failed", "err", err) return nil, errors.Join(ErrConfig, status.Error(codes.Internal, "configuration error")) } - slog.DebugContext(ctx, "JWK Public Key Handler found", "cert", jsonPublicKey) - return &kaspb.PublicKeyResponse{PublicKey: string(jsonPublicKey)}, nil + + return &kaspb.PublicKeyResponse{PublicKey: rsaPublicKeyPem}, nil } if in.GetFmt() == "pkcs8" { - certificatePem, err := exportCertificateAsPemStr(p.Session.RSA.Certificate) + rsaPublicKeyPem, err := p.CryptoProvider.RSAPublicKey("unknown") if err != nil { - slog.ErrorContext(ctx, "RSA public key from PKCS11", "err", err) + slog.ErrorContext(ctx, "CryptoProvider.RSAPublicKey failed", "err", err) return nil, errors.Join(ErrConfig, status.Error(codes.Internal, "configuration error")) } - slog.DebugContext(ctx, "RSA Cert Handler found", "cert", certificatePem) - return &kaspb.PublicKeyResponse{PublicKey: certificatePem}, nil + return &kaspb.PublicKeyResponse{PublicKey: rsaPublicKeyPem}, nil } - rsaPublicKeyPem, err := exportRsaPublicKeyAsPemStr(p.Session.RSA.PublicKey) + rsaPublicKeyPem, err := p.CryptoProvider.RSAPublicKey("unknown") if err != nil { - slog.ErrorContext(ctx, "RSA public key export fail", "err", err) + slog.ErrorContext(ctx, "CryptoProvider.RSAPublicKey failed", "err", err) return nil, errors.Join(ErrConfig, status.Error(codes.Internal, "configuration error")) } - slog.DebugContext(ctx, "RSA Public Key Handler found", "cert", rsaPublicKeyPem) + return &kaspb.PublicKeyResponse{PublicKey: rsaPublicKeyPem}, nil } diff --git a/service/kas/access/publicKey_test.go b/service/kas/access/publicKey_test.go index 84c4caadf2..c1a9bb25a8 100644 --- a/service/kas/access/publicKey_test.go +++ b/service/kas/access/publicKey_test.go @@ -17,11 +17,49 @@ import ( "testing" "github.com/opentdf/platform/service/internal/security" + "github.com/stretchr/testify/require" kaspb "github.com/opentdf/platform/protocol/go/kas" "github.com/stretchr/testify/assert" ) +var ( + config = security.Config{ + Type: "hsm", + HSMConfig: security.HSMConfig{ + Enabled: true, + ModulePath: "", + PIN: "12345", + SlotID: 0, + SlotLabel: "dev-token", + Keys: map[string]security.KeyInfo{ + "rsa": { + Name: "rsa", + Label: "development-rsa-kas", + }, + "ec": { + Name: "ec", + Label: "development-ec-kas", + }, + }, + }, + StandardConfig: security.StandardConfig{ + RSAKeys: map[string]security.StandardKeyInfo{ + "rsa": { + PrivateKeyPath: "kas-private.pem", + PublicKeyPath: "kas-cert.pem", + }, + }, + ECKeys: map[string]security.StandardKeyInfo{ + "ec": { + PrivateKeyPath: "kas-ec-private.pem", + PublicKeyPath: "kas-ec-cert.pem", + }, + }, + }, + } +) + func TestExportRsaPublicKeyAsPemStrSuccess(t *testing.T) { mockKey := &rsa.PublicKey{ N: big.NewInt(123), @@ -135,60 +173,72 @@ func TestError(t *testing.T) { const hostname = "localhost" func TestCertificateHandlerEmpty(t *testing.T) { + config.HSMConfig.Keys = map[string]security.KeyInfo{ + "rsa": {}, + "ec": {}, + } + hsmSession, _ := security.NewCryptoProvider(config) + defer hsmSession.Close() kasURI, _ := url.Parse("https://" + hostname + ":5000") + kas := Provider{ - URI: *kasURI, - Session: security.HSMSession{}, - OIDCVerifier: nil, + URI: *kasURI, + CryptoProvider: hsmSession, + OIDCVerifier: nil, } result, err := kas.PublicKey(context.Background(), &kaspb.PublicKeyRequest{Fmt: "pkcs8"}) - assert.Error(t, err, "not found") + require.Error(t, err, "not found") assert.Nil(t, result) } func TestCertificateHandlerWithEc256(t *testing.T) { - curve := elliptic.P256() - privateKey, err := ecdsa.GenerateKey(curve, rand.Reader) - if err != nil { - t.Errorf("Failed to generate a private key: %v", err) - } - + config.HSMConfig.Keys = map[string]security.KeyInfo{ + "rsa": { + Name: "rsa", + Label: "development-rsa-kas", + }, + "ec": { + Name: "ec", + Label: "development-ec-kas", + }, + } + hsmSession, _ := security.NewCryptoProvider(config) + defer hsmSession.Close() kasURI, _ := url.Parse("https://" + hostname + ":5000") kas := Provider{ - URI: *kasURI, - Session: security.HSMSession{}, - OIDCVerifier: nil, - } - kas.Session.EC = &security.ECKeyPair{ - PublicKey: &privateKey.PublicKey, - Certificate: &x509.Certificate{}, + URI: *kasURI, + CryptoProvider: hsmSession, + OIDCVerifier: nil, } result, err := kas.LegacyPublicKey(context.Background(), &kaspb.LegacyPublicKeyRequest{Algorithm: "ec:secp256r1"}) if err != nil { t.Errorf("got %s, but should be nil", err) } - if result == nil || !strings.Contains(result.GetValue(), "BEGIN CERTIFICATE") { + if result == nil || !strings.Contains(result.GetValue(), "BEGIN PUBLIC KEY") { t.Errorf("got %s, but should be cert", result) } } func TestPublicKeyHandlerWithEc256(t *testing.T) { - curve := elliptic.P256() - privateKey, err := ecdsa.GenerateKey(curve, rand.Reader) - if err != nil { - t.Errorf("Failed to generate a private key: %v", err) - } - + config.HSMConfig.Keys = map[string]security.KeyInfo{ + "rsa": { + Name: "rsa", + Label: "development-rsa-kas", + }, + "ec": { + Name: "ec", + Label: "development-ec-kas", + }, + } + hsmSession, _ := security.NewCryptoProvider(config) + defer hsmSession.Close() kasURI, _ := url.Parse("https://" + hostname + ":5000") kas := Provider{ - URI: *kasURI, - Session: security.HSMSession{}, - OIDCVerifier: nil, - } - kas.Session.EC = &security.ECKeyPair{ - PublicKey: &privateKey.PublicKey, + URI: *kasURI, + CryptoProvider: hsmSession, + OIDCVerifier: nil, } result, err := kas.PublicKey(context.Background(), &kaspb.PublicKeyRequest{Algorithm: "ec:secp256r1"}) @@ -201,29 +251,23 @@ func TestPublicKeyHandlerWithEc256(t *testing.T) { } func TestPublicKeyHandlerV2(t *testing.T) { - mockPublicKeyRsa := rsa.PublicKey{ - N: big.NewInt(123), - E: 65537, - } - - curve := elliptic.P256() - privateKey, err := ecdsa.GenerateKey(curve, rand.Reader) - if err != nil { - t.Errorf("Failed to generate a private key: %v", err) - } - + config.HSMConfig.Keys = map[string]security.KeyInfo{ + "rsa": { + Name: "rsa", + Label: "development-rsa-kas", + }, + "ec": { + Name: "ec", + Label: "development-ec-kas", + }, + } + hsmSession, _ := security.NewCryptoProvider(config) + defer hsmSession.Close() kasURI, _ := url.Parse("https://" + hostname + ":5000") kas := Provider{ - URI: *kasURI, - Session: security.HSMSession{}, - OIDCVerifier: nil, - } - kas.Session.EC = &security.ECKeyPair{ - PublicKey: &privateKey.PublicKey, - } - kas.Session.RSA = &security.RSAKeyPair{ - Certificate: &x509.Certificate{}, - PublicKey: &mockPublicKeyRsa, + URI: *kasURI, + CryptoProvider: hsmSession, + OIDCVerifier: nil, } result, err := kas.PublicKey(context.Background(), &kaspb.PublicKeyRequest{Algorithm: "rsa"}) @@ -236,51 +280,43 @@ func TestPublicKeyHandlerV2(t *testing.T) { } func TestPublicKeyHandlerV2Failure(t *testing.T) { - curve := elliptic.P256() - privateKey, err := ecdsa.GenerateKey(curve, rand.Reader) - if err != nil { - t.Errorf("Failed to generate a private key: %v", err) + config.HSMConfig.Keys = map[string]security.KeyInfo{ + "rsa": {}, + "ec": {}, } - + hsmSession, _ := security.NewCryptoProvider(config) + defer hsmSession.Close() kasURI, _ := url.Parse("https://" + hostname + ":5000") kas := Provider{ - URI: *kasURI, - Session: security.HSMSession{}, - OIDCVerifier: nil, - } - kas.Session.EC = &security.ECKeyPair{ - PublicKey: &privateKey.PublicKey, + URI: *kasURI, + CryptoProvider: hsmSession, + OIDCVerifier: nil, } - _, err = kas.PublicKey(context.Background(), &kaspb.PublicKeyRequest{Algorithm: "rsa"}) + _, err := kas.PublicKey(context.Background(), &kaspb.PublicKeyRequest{Algorithm: "rsa"}) if err == nil { t.Errorf("got nil error") } } func TestPublicKeyHandlerV2WithEc256(t *testing.T) { - mockPublicKeyRsa := rsa.PublicKey{ - N: big.NewInt(123), - E: 65537, - } - - curve := elliptic.P256() - privateKey, err := ecdsa.GenerateKey(curve, rand.Reader) - if err != nil { - t.Errorf("Failed to generate a private key: %v", err) - } + config.HSMConfig.Keys = map[string]security.KeyInfo{ + "rsa": { + Name: "rsa", + Label: "development-rsa-kas", + }, + "ec": { + Name: "ec", + Label: "development-ec-kas", + }, + } + hsmSession, _ := security.NewCryptoProvider(config) + defer hsmSession.Close() kasURI, _ := url.Parse("https://" + hostname + ":5000") kas := Provider{ - URI: *kasURI, - Session: security.HSMSession{}, - OIDCVerifier: nil, - } - kas.Session.EC = &security.ECKeyPair{ - PublicKey: &privateKey.PublicKey, - } - kas.Session.RSA = &security.RSAKeyPair{ - Certificate: &x509.Certificate{}, - PublicKey: &mockPublicKeyRsa, + URI: *kasURI, + CryptoProvider: hsmSession, + OIDCVerifier: nil, } result, err := kas.PublicKey(context.Background(), &kaspb.PublicKeyRequest{Algorithm: "ec:secp256r1", @@ -294,28 +330,23 @@ func TestPublicKeyHandlerV2WithEc256(t *testing.T) { } func TestPublicKeyHandlerV2WithJwk(t *testing.T) { - mockPublicKeyRsa := rsa.PublicKey{ - N: big.NewInt(123), - E: 65537, - } - - curve := elliptic.P256() - privateKey, err := ecdsa.GenerateKey(curve, rand.Reader) - if err != nil { - t.Errorf("Failed to generate a private key: %v", err) - } + config.HSMConfig.Keys = map[string]security.KeyInfo{ + "rsa": { + Name: "rsa", + Label: "development-rsa-kas", + }, + "ec": { + Name: "ec", + Label: "development-ec-kas", + }, + } + hsmSession, _ := security.NewCryptoProvider(config) + defer hsmSession.Close() kasURI, _ := url.Parse("https://" + hostname + ":5000") kas := Provider{ - URI: *kasURI, - Session: security.HSMSession{}, - OIDCVerifier: nil, - } - kas.Session.EC = &security.ECKeyPair{ - PublicKey: &privateKey.PublicKey, - } - kas.Session.RSA = &security.RSAKeyPair{ - Certificate: &x509.Certificate{}, - PublicKey: &mockPublicKeyRsa, + URI: *kasURI, + CryptoProvider: hsmSession, + OIDCVerifier: nil, } result, err := kas.PublicKey(context.Background(), &kaspb.PublicKeyRequest{ diff --git a/service/kas/access/rewrap.go b/service/kas/access/rewrap.go index ff363f8485..dbe1c0a935 100644 --- a/service/kas/access/rewrap.go +++ b/service/kas/access/rewrap.go @@ -4,12 +4,9 @@ import ( "bytes" "context" "crypto" - "crypto/aes" - "crypto/cipher" "crypto/ecdh" "crypto/ecdsa" "crypto/hmac" - "crypto/rand" "crypto/rsa" "crypto/sha256" "crypto/x509" @@ -19,14 +16,13 @@ import ( "encoding/pem" "errors" "fmt" - "io" "log/slog" "strings" + "github.com/opentdf/platform/lib/ocrypto" "github.com/opentdf/platform/protocol/go/authorization" kaspb "github.com/opentdf/platform/protocol/go/kas" "github.com/opentdf/platform/service/internal/auth" - "github.com/opentdf/platform/service/internal/security" "github.com/opentdf/platform/service/kas/nanotdf" "github.com/opentdf/platform/service/kas/tdf3" "google.golang.org/grpc/codes" @@ -35,9 +31,6 @@ import ( "gopkg.in/go-jose/go-jose.v2/jwt" ) -const ivSize = 12 -const tagSize = 12 - type RequestBody struct { AuthToken string `json:"authToken"` KeyAccess tdf3.KeyAccess `json:"keyAccess"` @@ -124,7 +117,7 @@ func generateHMACDigest(ctx context.Context, msg, key []byte) ([]byte, error) { } type verifiedRequest struct { - publicKey crypto.PublicKey + publicKey crypto.PublicKey // TODO: Remove this requestBody *RequestBody cl *customClaimsHeader bearerToken string @@ -262,14 +255,13 @@ func (p *Provider) Rewrap(ctx context.Context, in *kaspb.RewrapRequest) (*kaspb. } if body.requestBody.Algorithm == "ec:secp256r1" { - return nanoTDFRewrap(*body, &p.Session, p.Session.EC.PrivateKey) + return p.nanoTDFRewrap(*body) } return p.tdf3Rewrap(ctx, body) } func (p *Provider) tdf3Rewrap(ctx context.Context, body *verifiedRequest) (*kaspb.RewrapResponse, error) { - symmetricKey, err := p.Session.DecryptOAEP( - &p.Session.RSA.PrivateKey, body.requestBody.KeyAccess.WrappedKey, crypto.SHA1, nil) + symmetricKey, err := p.CryptoProvider.RSADecrypt(crypto.SHA1, "UnKnown", "", body.requestBody.KeyAccess.WrappedKey) if err != nil { slog.WarnContext(ctx, "failure to decrypt dek", "err", err) return nil, err400("bad request") @@ -307,9 +299,14 @@ func (p *Provider) tdf3Rewrap(ctx context.Context, body *verifiedRequest) (*kasp return nil, err403("forbidden") } - rewrappedKey, err := tdf3.EncryptWithPublicKey(symmetricKey, body.publicKey.(*rsa.PublicKey)) + asymEncrypt, err := ocrypto.NewAsymEncryption(body.requestBody.ClientPublicKey) + if err != nil { + slog.WarnContext(ctx, "ocrypto.NewAsymEncryption:", "err", err) + } + + rewrappedKey, err := asymEncrypt.Encrypt(symmetricKey) if err != nil { - slog.WarnContext(ctx, "rewrap: encryptWithPublicKey failed", "err", err, "clientPublicKey", &body.publicKey) + slog.WarnContext(ctx, "rewrap: ocrypto.AsymEncryption.encrypt failed", "err", err, "clientPublicKey", &body.publicKey) return nil, err400("bad key for rewrap") } @@ -320,11 +317,7 @@ func (p *Provider) tdf3Rewrap(ctx context.Context, body *verifiedRequest) (*kasp }, nil } -func nanoTDFRewrap( - body verifiedRequest, - session *security.HSMSession, - key security.PrivateKeyEC, -) (*kaspb.RewrapResponse, error) { +func (p *Provider) nanoTDFRewrap(body verifiedRequest) (*kaspb.RewrapResponse, error) { headerReader := bytes.NewReader(body.requestBody.KeyAccess.Header) header, err := nanotdf.ReadNanoTDFHeader(headerReader) @@ -332,7 +325,7 @@ func nanoTDFRewrap( return nil, fmt.Errorf("failed to parse NanoTDF header: %w", err) } - symmetricKey, err := session.GenerateNanoTDFSymmetricKey(header.EphemeralPublicKey.Key, key) + symmetricKey, err := p.CryptoProvider.GenerateNanoTDFSymmetricKey(header.EphemeralPublicKey.Key) if err != nil { return nil, fmt.Errorf("failed to generate symmetric key: %w", err) } @@ -349,11 +342,11 @@ func nanoTDFRewrap( return nil, fmt.Errorf("failed to serialize keypair: %v", pub) } - privateKeyHandle, publicKeyHandle, err := session.GenerateEphemeralKasKeys() + privateKeyHandle, publicKeyHandle, err := p.CryptoProvider.GenerateEphemeralKasKeys() if err != nil { return nil, fmt.Errorf("failed to generate keypair: %w", err) } - sessionKey, err := session.GenerateNanoTDFSessionKey(privateKeyHandle, pubKeyBytes) + sessionKey, err := p.CryptoProvider.GenerateNanoTDFSessionKey(privateKeyHandle, pubKeyBytes) if err != nil { return nil, fmt.Errorf("failed to generate session key: %w", err) } @@ -389,21 +382,15 @@ func nanoTDFRewrap( } func wrapKeyAES(sessionKey, dek []byte) ([]byte, error) { - block, err := aes.NewCipher(sessionKey) + gcm, err := ocrypto.NewAESGcm(sessionKey) if err != nil { - return nil, fmt.Errorf("failed to create cipher block: %w", err) + return nil, fmt.Errorf("crypto.NewAESGcm:%w", err) } - aesGcm, err := cipher.NewGCMWithTagSize(block, tagSize) + cipherText, err := gcm.Encrypt(dek) if err != nil { - return nil, fmt.Errorf("failed to create NewGCMWithTagSize: %w", err) - } - - iv := make([]byte, ivSize) - if _, err = io.ReadFull(rand.Reader, iv); err != nil { - return nil, fmt.Errorf("failed to generate IV: %w", err) + return nil, fmt.Errorf("crypto.AsymEncryption.encrypt:%w", err) } - cipherText := aesGcm.Seal(iv, iv, dek, nil) return cipherText, nil } diff --git a/service/kas/access/rewrap_test.go b/service/kas/access/rewrap_test.go index 19259b6e4c..73d92f76a0 100644 --- a/service/kas/access/rewrap_test.go +++ b/service/kas/access/rewrap_test.go @@ -494,11 +494,12 @@ func TestLegacyBearerTokenEtc(t *testing.T) { } func TestHandlerAuthFailure0(t *testing.T) { + hsmSession, _ := security.New(&security.HSMConfig{}) kasURI, _ := url.Parse("https://" + hostname + ":5000") kas := Provider{ - URI: *kasURI, - Session: security.HSMSession{}, - OIDCVerifier: nil, + URI: *kasURI, + CryptoProvider: hsmSession, + OIDCVerifier: nil, } body := `{"mock": "value"}` @@ -510,11 +511,12 @@ func TestHandlerAuthFailure0(t *testing.T) { } func TestHandlerAuthFailure1(t *testing.T) { + hsmSession, _ := security.New(&security.HSMConfig{}) kasURI, _ := url.Parse("https://" + hostname + ":5000") kas := Provider{ - URI: *kasURI, - Session: security.HSMSession{}, - OIDCVerifier: nil, + URI: *kasURI, + CryptoProvider: hsmSession, + OIDCVerifier: nil, } body := `{"mock": "value"}` @@ -530,11 +532,12 @@ func TestHandlerAuthFailure1(t *testing.T) { } func TestHandlerAuthFailure2(t *testing.T) { + hsmSession, _ := security.New(&security.HSMConfig{}) kasURI, _ := url.Parse("https://" + hostname + ":5000") kas := Provider{ - URI: *kasURI, - Session: security.HSMSession{}, - OIDCVerifier: nil, + URI: *kasURI, + CryptoProvider: hsmSession, + OIDCVerifier: nil, } body := `{"mock": "value"}` diff --git a/service/kas/kas.go b/service/kas/kas.go index cef2dc5bc6..664a019ec4 100644 --- a/service/kas/kas.go +++ b/service/kas/kas.go @@ -57,11 +57,6 @@ func NewRegistration() serviceregistry.Registration { Namespace: "kas", ServiceDesc: &kaspb.AccessService_ServiceDesc, RegisterFunc: func(srp serviceregistry.RegistrationParams) (any, serviceregistry.HandlerServer) { - hsm := srp.OTDF.HSM - if hsm == nil { - slog.Error("hsm not enabled") - panic(fmt.Errorf("hsm not enabled")) - } // FIXME msg="mismatched key access url" keyAccessURL=http://localhost:9000 kasURL=https://:9000 kasURLString := "https://" + srp.OTDF.HTTPServer.Addr kasURI, err := url.Parse(kasURLString) @@ -70,10 +65,10 @@ func NewRegistration() serviceregistry.Registration { } p := access.Provider{ - URI: *kasURI, - SDK: srp.SDK, - Session: *hsm, - OIDCVerifier: loadIdentityProvider(), + URI: *kasURI, + AttributeSvc: nil, + CryptoProvider: srp.OTDF.CryptoProvider, + OIDCVerifier: loadIdentityProvider(), } return &p, func(ctx context.Context, mux *runtime.ServeMux, server any) error { kas, ok := server.(*access.Provider)