Skip to content

Commit

Permalink
[db] Support encrypted JSON in Go
Browse files Browse the repository at this point in the history
  • Loading branch information
easyCZ authored and roboquat committed Dec 20, 2022
1 parent 33b135f commit 9ca833a
Show file tree
Hide file tree
Showing 9 changed files with 157 additions and 25 deletions.
28 changes: 28 additions & 0 deletions components/gitpod-db/go/dbtest/encryption.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Copyright (c) 2022 Gitpod GmbH. All rights reserved.
// Licensed under the GNU Affero General Public License (AGPL).
// See License.AGPL.txt in the project root for license information.

package dbtest

import (
"encoding/base64"
db "github.com/gitpod-io/gitpod/components/gitpod-db/go"
"github.com/stretchr/testify/require"
"testing"
)

func GetTestCipher(t *testing.T) (*db.AES256CBC, db.CipherMetadata) {
t.Helper()

// This is a test key also used in server tests - see components/gitpod-protocol/src/encryption/encryption-engine.spec.ts
key, err := base64.StdEncoding.DecodeString("ZMaTPrF7s9gkLbY45zP59O0LTpLvDd/cgqPE9Ptghh8=")
require.NoError(t, err)

metadata := db.CipherMetadata{
Name: "default",
Version: 1,
}
cipher, err := db.NewAES256CBCCipher(string(key), metadata)
require.NoError(t, err)
return cipher, metadata
}
6 changes: 5 additions & 1 deletion components/gitpod-db/go/dbtest/oidc_client_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,15 @@ import (
func NewOIDCClientConfig(t *testing.T, record db.OIDCClientConfig) db.OIDCClientConfig {
t.Helper()

cipher, _ := GetTestCipher(t)
encrypted, err := db.EncryptJSON(cipher, db.OIDCSpec{})
require.NoError(t, err)

now := time.Now().UTC().Truncate(time.Millisecond)
result := db.OIDCClientConfig{
ID: uuid.New(),
Issuer: "issuer",
Data: []byte("{}"),
Data: encrypted,
LastModified: now,
}

Expand Down
14 changes: 11 additions & 3 deletions components/gitpod-db/go/encryption.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,19 @@ import (
"fmt"
)

type Cipher interface {
type Encryptor interface {
Encrypt(data []byte) (EncryptedData, error)
}

type Decryptor interface {
Decrypt(data EncryptedData) ([]byte, error)
}

type Cipher interface {
Encryptor
Decryptor
}

func NewAES256CBCCipher(secret string, metadata CipherMetadata) (*AES256CBC, error) {
block, err := aes.NewCipher([]byte(secret))
if err != nil {
Expand All @@ -37,7 +45,7 @@ type AES256CBC struct {
}

func (c *AES256CBC) Encrypt(data []byte) (EncryptedData, error) {
iv, err := generateInitializationVector(16)
iv, err := GenerateInitializationVector(16)
if err != nil {
return EncryptedData{}, err
}
Expand Down Expand Up @@ -109,7 +117,7 @@ type EncryptedData struct {
Metadata CipherMetadata `json:"keyMetadata"`
}

func generateInitializationVector(size int) ([]byte, error) {
func GenerateInitializationVector(size int) ([]byte, error) {
buf := make([]byte, size)
_, err := rand.Read(buf)
if err != nil {
Expand Down
28 changes: 9 additions & 19 deletions components/gitpod-db/go/encryption_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,26 @@
// Licensed under the GNU Affero General Public License (AGPL).
// See License.AGPL.txt in the project root for license information.

package db
package db_test

import (
"encoding/base64"
"fmt"
db "github.com/gitpod-io/gitpod/components/gitpod-db/go"
"github.com/gitpod-io/gitpod/components/gitpod-db/go/dbtest"
"github.com/stretchr/testify/require"
"testing"
)

func TestAES256CBCCipher_Encrypt_Decrypt(t *testing.T) {
secret, err := generateInitializationVector(32)
require.NoError(t, err)
secret := "testtesttesttesttesttesttesttest"

metadata := CipherMetadata{
metadata := db.CipherMetadata{
Name: "general",
Version: 1,
}

cipher, err := NewAES256CBCCipher(string(secret), metadata)
cipher, err := db.NewAES256CBCCipher(secret, metadata)
require.NoError(t, err)

data := []byte(`{ "foo": "bar", "another": "one" }`)
Expand All @@ -45,26 +46,15 @@ func TestAES256CBCCipher_Encrypt_Decrypt(t *testing.T) {
}

func TestAES256CBCCipher_EncryptedByServer(t *testing.T) {
// This is a test key also used in server tests - see components/gitpod-protocol/src/encryption/encryption-engine.spec.ts
key, err := base64.StdEncoding.DecodeString("ZMaTPrF7s9gkLbY45zP59O0LTpLvDd/cgqPE9Ptghh8=")
require.NoError(t, err)

metadata := CipherMetadata{
Name: "general",
Version: 1,
}
encrypted := EncryptedData{

cipher, metadata := dbtest.GetTestCipher(t)
encrypted := db.EncryptedData{
EncodedData: "YpgOY8ZNV64oG1DXiuCUXKy0thVySbN7uXTQxtC2j2A=",
Params: KeyParams{
Params: db.KeyParams{
InitializationVector: "vpTOAFN5v4kOPsAHBKk+eg==",
},
Metadata: metadata,
}

cipher, err := NewAES256CBCCipher(string(key), metadata)
require.NoError(t, err)

decrypted, err := cipher.Decrypt(encrypted)
fmt.Println(err)
require.NoError(t, err)
Expand Down
66 changes: 66 additions & 0 deletions components/gitpod-db/go/json.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// Copyright (c) 2022 Gitpod GmbH. All rights reserved.
// Licensed under the GNU Affero General Public License (AGPL).
// See License.AGPL.txt in the project root for license information.

package db

import (
"encoding/json"
"fmt"
"gorm.io/datatypes"
)

type EncryptedJSON[T any] datatypes.JSON

func (j *EncryptedJSON[T]) EncryptedData() (EncryptedData, error) {
var data EncryptedData
err := json.Unmarshal(*j, &data)
if err != nil {
return EncryptedData{}, fmt.Errorf("failed to unmarshal encrypted json: %w", err)
}

return data, nil
}

func (j *EncryptedJSON[T]) Decrypt(decryptor Decryptor) (T, error) {
var out T
data, err := j.EncryptedData()
if err != nil {
return out, fmt.Errorf("failed to obtain encrypted data: %w", err)
}

b, err := decryptor.Decrypt(data)
if err != nil {
return out, fmt.Errorf("failed to decrypt encrypted json: %w", err)
}

err = json.Unmarshal(b, &out)
if err != nil {
return out, fmt.Errorf("failed to unmarshal encrypted json: %w", err)
}

return out, nil
}

func EncryptJSON[T any](encryptor Encryptor, data T) (EncryptedJSON[T], error) {
b, err := json.Marshal(data)
if err != nil {
return nil, fmt.Errorf("failed to marshal data into json: %w", err)
}

encrypted, err := encryptor.Encrypt(b)
if err != nil {
return nil, fmt.Errorf("failed to encrypt json: %w", err)
}

return NewEncryptedJSON[T](encrypted)
}

func NewEncryptedJSON[T any](data EncryptedData) (EncryptedJSON[T], error) {
b, err := json.Marshal(data)
if err != nil {
return nil, fmt.Errorf("failed to serialize encrypted data into json: %w", err)
}

return b, nil
}
34 changes: 34 additions & 0 deletions components/gitpod-db/go/json_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Copyright (c) 2022 Gitpod GmbH. All rights reserved.
// Licensed under the GNU Affero General Public License (AGPL).
// See License.AGPL.txt in the project root for license information.

package db_test

import (
db "github.com/gitpod-io/gitpod/components/gitpod-db/go"
"github.com/gitpod-io/gitpod/components/gitpod-db/go/dbtest"
"github.com/stretchr/testify/require"
"testing"
)

func TestEncryptJSON_DecryptJSON(t *testing.T) {
cipher, _ := dbtest.GetTestCipher(t)

type Data struct {
First string
Second int
}

data := Data{
First: "first",
Second: 2,
}

encrypted, err := db.EncryptJSON(cipher, data)
require.NoError(t, err)

decrypted, err := encrypted.Decrypt(cipher)
require.NoError(t, err)

require.Equal(t, data, decrypted)
}
6 changes: 4 additions & 2 deletions components/gitpod-db/go/oidc_client_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import (
"errors"
"fmt"
"github.com/google/uuid"
"gorm.io/datatypes"
"gorm.io/gorm"
"time"
)
Expand All @@ -19,7 +18,7 @@ type OIDCClientConfig struct {

Issuer string `gorm:"column:issuer;type:char;size:255;" json:"issuer"`

Data datatypes.JSON `gorm:"column:data;type:text;size:65535" json:"data"`
Data EncryptedJSON[OIDCSpec] `gorm:"column:data;type:text;size:65535" json:"data"`

LastModified time.Time `gorm:"column:_lastModified;type:timestamp;default:CURRENT_TIMESTAMP(6);" json:"_lastModified"`
// deleted is reserved for use by db-sync.
Expand All @@ -30,6 +29,9 @@ func (c *OIDCClientConfig) TableName() string {
return "d_b_oidc_client_config"
}

type OIDCSpec struct {
}

func CreateOIDCCLientConfig(ctx context.Context, conn *gorm.DB, cfg OIDCClientConfig) (OIDCClientConfig, error) {
if cfg.ID == uuid.Nil {
return OIDCClientConfig{}, errors.New("OIDC Client Config ID must be set")
Expand Down
File renamed without changes.
File renamed without changes.

0 comments on commit 9ca833a

Please sign in to comment.