Skip to content

Commit

Permalink
feat(sdk): PLAT-3082 nanotdf encrypt (#744)
Browse files Browse the repository at this point in the history
[PLAT-3082] Adds nanotdf encrypt and decrypt of buffer

- Encrypt of buffer 
- Decrypt of buffer
- Support for Secp256r1
- Updates to go kas to support the above

[PLAT-3082]: https://virtru.atlassian.net/browse/PLAT-3082

Note that this is an early first-pass implementation, there is more work
to do:
- support more curves, 
- dataset mode,
- additional refactoring and cleanup
- creating appropriate unit tests for everything

Resolves #766

---------

Co-authored-by: sujan kota <[email protected]>
Co-authored-by: sujankota <[email protected]>
Co-authored-by: Elizabeth Healy <[email protected]>
Co-authored-by: David Mihalcik <[email protected]>
Co-authored-by: Paul Flynn <[email protected]>
  • Loading branch information
6 people authored May 24, 2024
1 parent 19188d5 commit 6c82536
Show file tree
Hide file tree
Showing 31 changed files with 2,268 additions and 300 deletions.
3 changes: 0 additions & 3 deletions .github/workflows/checks.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ env:

on:
pull_request:
branches:
- main
push:
branches:
- main
Expand Down Expand Up @@ -134,7 +132,6 @@ jobs:
strategy:
matrix:
crypto:
- hsm
- standard
steps:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
Expand Down
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,9 @@ tmp-gen/
service/rttests/*.tdf
coverage.out
coverage.lcov
sdk/nanotest1.ntdf

*.zip
sensitive.txt.tdf
keys/
keys/
/examples/sensitive.txt.ntdf
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ clean:
build: proto-generate opentdf sdk/sdk examples/examples

opentdf: $(shell find service)
go build --tags=opentdf.hsm -o opentdf -v service/main.go
go build -o opentdf -v service/main.go

sdk/sdk: $(shell find sdk)
(cd sdk && go build ./...)
Expand Down
20 changes: 20 additions & 0 deletions examples/cmd/decrypt.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package cmd

import (
"bytes"
"io"
"os"

Expand Down Expand Up @@ -40,6 +41,7 @@ func decrypt(cmd *cobra.Command, args []string) error {
}

defer file.Close()
cmd.Println("# TDF")

tdfreader, err := client.LoadTDF(file)
if err != nil {
Expand All @@ -51,6 +53,24 @@ func decrypt(cmd *cobra.Command, args []string) error {
if err != nil && err != io.EOF {
return err
}
cmd.Println("\n-----\n\n# NANO")

nTdfFile, err := os.Open("sensitive.txt.ntdf")
if err != nil {
return err
}

outBuf := bytes.Buffer{}
_, err = client.ReadNanoTDF(io.Writer(&outBuf), nTdfFile)
if err != nil {
return err
}

if "Hello Virtru" == outBuf.String() {
cmd.Println("✅ NanoTDF test passed!")
} else {
cmd.Println("❌ NanoTDF test failed!")
}

return nil
}
51 changes: 51 additions & 0 deletions examples/cmd/encrypt.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
package cmd

import (
"bytes"
"encoding/json"
"fmt"
"os"
"strings"

"github.com/opentdf/platform/lib/ocrypto"

"github.com/opentdf/platform/sdk"
"github.com/spf13/cobra"
)
Expand Down Expand Up @@ -64,5 +67,53 @@ func encrypt(cmd *cobra.Command, args []string) error {

// Print Manifest
cmd.Println(string(manifestJSON))

//
// NanoTDF
//

attributes := []string{
"https://example.com/attr/attr1/value/value1",
}

nanoTDFCOnfig, err := client.NewNanoTDFConfig()
if err != nil {
return err
}

nanoTDFCOnfig.SetAttributes(attributes)
nanoTDFCOnfig.SetKasURL(fmt.Sprintf("http://%s/kas", cmd.Flag("platformEndpoint").Value.String()))

nTDFile := "sensitive.txt.ntdf"
strReader = strings.NewReader(plainText)
nTdfFile, err := os.Create(nTDFile)
defer nTdfFile.Close()

_, err = client.CreateNanoTDF(nTdfFile, strReader, *nanoTDFCOnfig)
if err != nil {
return err
}

err = dumpNanoTDF(cmd, nTDFile)
if err != nil {
return err
}
return nil
}

func dumpNanoTDF(cmd *cobra.Command, nTdfFile string) error {
f, err := os.Open(nTdfFile)
if err != nil {
return err
}

buf := bytes.Buffer{}
_, err = buf.ReadFrom(f)
if err != nil {
return err
}

cmd.Println(string(ocrypto.Base64Encode(buf.Bytes())))

return nil
}
2 changes: 1 addition & 1 deletion examples/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module github.com/opentdf/platform/examples
go 1.21

require (
github.com/opentdf/platform/lib/ocrypto v0.1.3
github.com/opentdf/platform/protocol/go v0.2.3
github.com/opentdf/platform/sdk v0.2.3
github.com/spf13/cobra v1.8.0
Expand All @@ -25,7 +26,6 @@ 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/ocrypto v0.1.3 // 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
Expand Down
202 changes: 202 additions & 0 deletions go.work.sum

Large diffs are not rendered by default.

20 changes: 20 additions & 0 deletions lib/ocrypto/aes_gcm.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,3 +113,23 @@ func (aesGcm AesGcm) DecryptWithTagSize(data []byte, authTagSize int) ([]byte, e

return plainData, nil
}

// DecryptWithIVAndTagSize decrypts data with symmetric key.
// NOTE: This method expects gcm standard nonce size(12) of iv.
func (aesGcm AesGcm) DecryptWithIVAndTagSize(iv, data []byte, authTagSize int) ([]byte, error) {
if len(iv) != GcmStandardNonceSize {
return nil, errors.New("invalid nonce size, expects GcmStandardNonceSize")
}

gcm, err := cipher.NewGCMWithTagSize(aesGcm.block, authTagSize)
if err != nil {
return nil, fmt.Errorf("cipher.NewGCMWithTagSize failed: %w", err)
}

plainData, err := gcm.Open(nil, iv, data, nil)
if err != nil {
return nil, fmt.Errorf("gcm.Open failed: %w", err)
}

return plainData, nil
}
116 changes: 110 additions & 6 deletions lib/ocrypto/ec_key_pair.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package ocrypto

import (
"bytes"
"crypto/ecdh"
"crypto/ecdsa"
"crypto/elliptic"
Expand Down Expand Up @@ -29,9 +30,9 @@ type ECKeyPair struct {
PrivateKey *ecdsa.PrivateKey
}

// NewECKeyPair Generates an EC key pair of the given bit size.
func NewECKeyPair(mode ECCMode) (ECKeyPair, error) {
func getCurveFromECCMode(mode ECCMode) (elliptic.Curve, error) {
var c elliptic.Curve

switch mode {
case ECCModeSecp256r1:
c = elliptic.P256()
Expand All @@ -41,9 +42,23 @@ func NewECKeyPair(mode ECCMode) (ECKeyPair, error) {
c = elliptic.P521()
case ECCModeSecp256k1:
// TODO FIXME - unsupported?
return ECKeyPair{}, errors.New("unsupported ec key pair mode")
return nil, errors.New("unsupported ec key pair mode")
default:
return ECKeyPair{}, fmt.Errorf("invalid ec key pair mode %d", mode)
return nil, fmt.Errorf("invalid ec key pair mode %d", mode)
}

return c, nil
}

// NewECKeyPair Generates an EC key pair of the given bit size.
func NewECKeyPair(mode ECCMode) (ECKeyPair, error) {
var c elliptic.Curve

var err error

c, err = getCurveFromECCMode(mode)
if err != nil {
return ECKeyPair{}, err
}

privateKey, err := ecdsa.GenerateKey(c, rand.Reader)
Expand Down Expand Up @@ -104,6 +119,18 @@ func (keyPair ECKeyPair) KeySize() (int, error) {
return keyPair.PrivateKey.Params().N.BitLen(), nil
}

// CompressedECPublicKey - return a compressed key from the supplied curve and public key
func CompressedECPublicKey(mode ECCMode, pubKey ecdsa.PublicKey) ([]byte, error) {
curve, err := getCurveFromECCMode(mode)
if err != nil {
return nil, fmt.Errorf("x509.MarshalPKIXPublicKey failed: %w", err)
}

compressedKey := elliptic.MarshalCompressed(curve, pubKey.X, pubKey.Y)

return compressedKey, nil
}

// ConvertToECDHPublicKey convert the ec public key to ECDH public key
func ConvertToECDHPublicKey(key interface{}) (*ecdh.PublicKey, error) {
switch k := key.(type) {
Expand Down Expand Up @@ -133,10 +160,10 @@ func ConvertToECDHPrivateKey(key interface{}) (*ecdh.PrivateKey, error) {
}

// CalculateHKDF generate a key using key derivation function.
func CalculateHKDF(salt []byte, secret []byte, keyLen int) ([]byte, error) {
func CalculateHKDF(salt []byte, secret []byte) ([]byte, error) {
hkdfObj := hkdf.New(sha256.New, secret, salt, nil)

derivedKey := make([]byte, keyLen)
derivedKey := make([]byte, len(secret))
_, err := io.ReadFull(hkdfObj, derivedKey)
if err != nil {
return nil, fmt.Errorf("failed to derive hkdf key: %w", err)
Expand Down Expand Up @@ -222,3 +249,80 @@ func ComputeECDHKey(privateKeyInPem []byte, publicKeyInPem []byte) ([]byte, erro

return sharedKey, nil
}

func ComputeECDHKeyFromEC(publicKey *ecdsa.PublicKey, privateKey *ecdsa.PrivateKey) ([]byte, error) {
ecdhPrivateKey, err := ConvertToECDHPrivateKey(privateKey)
if err != nil {
return nil, fmt.Errorf("ocrypto.ECPrivateKeyFromPem failed: %w", err)
}

ecdhPublicKey, err := ConvertToECDHPublicKey(publicKey)
if err != nil {
return nil, fmt.Errorf("ocrypto.ECPubKeyFromPem failed: %w", err)
}

sharedKey, err := ecdhPrivateKey.ECDH(ecdhPublicKey)
if err != nil {
return nil, fmt.Errorf("there was a problem deriving a shared ECDH key: %w", err)
}

return sharedKey, nil
}

func ComputeECDHKeyFromECDHKeys(publicKey *ecdh.PublicKey, privateKey *ecdh.PrivateKey) ([]byte, error) {
sharedKey, err := privateKey.ECDH(publicKey)
if err != nil {
return nil, fmt.Errorf("there was a problem deriving a shared ECDH key: %w", err)
}

return sharedKey, nil
}

// TODO FIXME
func GetPEMPublicKeyFromPrivateKey(_ /*key*/ []byte, _ /*mode*/ ECCMode) ecdsa.PublicKey {
b := ecdsa.PublicKey{}
return b
}

// TODO FIXME
const (
kDummyLength = 128
)

func ComputeECDSASig(_ /*digest*/ [32]byte, _ /*key*/ []byte) []byte {
b := bytes.NewBuffer(make([]byte, 0, kDummyLength))
return b.Bytes()
}

// ECPrivateKeyInPemFormat Returns private key in pem format.
func ECPrivateKeyInPemFormat(privateKey ecdsa.PrivateKey) (string, error) {
privateKeyBytes, err := x509.MarshalPKCS8PrivateKey(privateKey)
if err != nil {
return "", fmt.Errorf("x509.MarshalPKCS8PrivateKey failed: %w", err)
}

privateKeyPem := pem.EncodeToMemory(
&pem.Block{
Type: "PRIVATE KEY",
Bytes: privateKeyBytes,
},
)
return string(privateKeyPem), nil
}

// ECPublicKeyInPemFormat Returns public key in pem format.
func ECPublicKeyInPemFormat(publicKey ecdsa.PublicKey) (string, error) {
publicKeyBytes, err := x509.MarshalPKIXPublicKey(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
}
4 changes: 2 additions & 2 deletions lib/ocrypto/ec_key_pair_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,13 +94,13 @@ func TestNanoTDFRewrapKeyGenerate(t *testing.T) {
digest := sha256.New()
digest.Write([]byte("L1L"))

kasSymmetricKey, err := CalculateHKDF(digest.Sum(nil), kasECDHKey, 32)
kasSymmetricKey, err := CalculateHKDF(digest.Sum(nil), kasECDHKey)
require.NoError(t, err, "fail to calculate HKDF key")

sdkECDHKey, err := ComputeECDHKey([]byte(sdkPrivateKeyAsPem), []byte(kasPubKeyAsPem))
require.NoError(t, err, "fail to calculate ecdh key")

sdkSymmetricKey, err := CalculateHKDF(digest.Sum(nil), sdkECDHKey, 32)
sdkSymmetricKey, err := CalculateHKDF(digest.Sum(nil), sdkECDHKey)
require.NoError(t, err, "fail to calculate HKDF key")

if string(kasSymmetricKey) != string(sdkSymmetricKey) {
Expand Down
Loading

0 comments on commit 6c82536

Please sign in to comment.