Skip to content

[WIP] encrypterdecrypter: Add encryption and decryption routines #119

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 69 additions & 0 deletions encrypterdecrypter/asymmetric/rsa.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package asymmetric

import (
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"fmt"

sv "github.com/secure-systems-lab/go-securesystemslib/signerverifier"
)

type RSAEncrypterDecrypter struct {
keyID string
private *rsa.PrivateKey
public *rsa.PublicKey
}

func NewRSAEncrypterDecrypterFromSSLibKey(key *sv.SSLibKey) (*RSAEncrypterDecrypter, error) {
if len(key.KeyVal.Public) == 0 {
return nil, sv.ErrInvalidKey
}

_, publicParsedKey, err := sv.DecodeAndParsePEM([]byte(key.KeyVal.Public))
if err != nil {
return nil, fmt.Errorf("unable to create RSA encrypterdecrypter: %w", err)
}

if len(key.KeyVal.Private) > 0 {
_, privateParsedKey, err := sv.DecodeAndParsePEM([]byte(key.KeyVal.Private))
if err != nil {
return nil, fmt.Errorf("unable to create RSA encrypterdecrypter: %w", err)
}

return &RSAEncrypterDecrypter{
keyID: key.KeyID,
public: publicParsedKey.(*rsa.PublicKey),
private: privateParsedKey.(*rsa.PrivateKey),
}, nil
}

return &RSAEncrypterDecrypter{
keyID: key.KeyID,
public: publicParsedKey.(*rsa.PublicKey),
private: nil,
}, nil
}

// Encrypt encrypts the provided data with the public key of the RSA
// EncrypterDecrypter instance.
func (ed *RSAEncrypterDecrypter) Encrypt(data []byte) ([]byte, error) {
rng := rand.Reader
return rsa.EncryptOAEP(sha256.New(), rng, ed.public, data, nil)
}

// Decrypt decrypts the provided data with the private key of the RSA
// EncrypterDecrypter instance.
func (ed *RSAEncrypterDecrypter) Decrypt(data []byte) ([]byte, error) {
if ed.private == nil {
return nil, sv.ErrNotPrivateKey
}

return rsa.DecryptOAEP(sha256.New(), nil, ed.private, data, nil)
}

// KeyID returns the key ID of the key used to create the RSA EncrypterDecrypter
// instance.
func (ed *RSAEncrypterDecrypter) KeyID() (string, error) {
return ed.keyID, nil
}
72 changes: 72 additions & 0 deletions encrypterdecrypter/asymmetric/rsa_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package asymmetric

import (
"crypto/rsa"
"path/filepath"
"testing"

sv "github.com/secure-systems-lab/go-securesystemslib/signerverifier"
"github.com/stretchr/testify/assert"
)

var plaintext = []byte("reallyimportant")

func TestNewRSAEncrypterDecrypterFromSSLibKey(t *testing.T) {
key, err := sv.LoadRSAPSSKeyFromFile(filepath.Join("..", "..", "signerverifier", "test-data", "rsa-test-key.pub"))

Check failure on line 15 in encrypterdecrypter/asymmetric/rsa_test.go

View workflow job for this annotation

GitHub Actions / Run staticcheck

sv.LoadRSAPSSKeyFromFile is deprecated: use LoadKey(). The custom serialization format has been deprecated. Use https://github.com/secure-systems-lab/securesystemslib/blob/main/docs/migrate_key.py to convert your key. (SA1019)
if err != nil {
t.Error(err)
}

ed, err := NewRSAEncrypterDecrypterFromSSLibKey(key)
if err != nil {
t.Error(err)
}

expectedPublicString := "-----BEGIN PUBLIC KEY-----\nMIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEA04egZRic+dZMVtiQc56D\nejU4FF1q3aOkUKnD+Q4lTbj1zp6ODKJTcktupmrad68jqtMiSGG8he6ELFs377q8\nbbgEUMWgAf+06Q8oFvUSfOXzZNFI7H5SMPOJY5aDWIMIEZ8DlcO7TfkA7D3iAEJX\nxxTOVS3UAIk5umO7Y7t7yXr8O/C4u78krGazCnoblcekMLJZV4O/5BloWNAe/B1c\nvZdaZUf3brD4ZZrxEtXw/tefhn1aHsSUajVW2wwjSpKhqj7Z0XS3bDS3T95/3xsN\n6+hlS6A7rJfiWpKIRHj0vh2SXLDmmhQl1In8TD/aiycTUyWcBRHVPlYFgYPt6SaT\nVQSgMzSxC43/2fINb2fyt8SbUHJ3Ct+mzRzd/1AQikWhBdstJLxInewzjYE/sb+c\n2CmCxMPQG2BwmAWXaaumeJcXVPBlMgAcjMatM8bPByTbXpKDnQslOE7g/gswDIwn\nEm53T13mZzYUvbLJ0q3aljZVLIC3IZn3ZwA2yCWchBkVAgMBAAE=\n-----END PUBLIC KEY-----"
_, expectedPublicKey, err := sv.DecodeAndParsePEM([]byte(expectedPublicString))
assert.Nil(t, err)

assert.Equal(t, "4e8d20af09fcaed6c388a186427f94a5f7ff5591ec295f4aab2cff49ffe39e9b", ed.keyID)
assert.Equal(t, expectedPublicKey.(*rsa.PublicKey), ed.public)
assert.Nil(t, ed.private)
}

func TestRoundtrip(t *testing.T) {
key, err := sv.LoadRSAPSSKeyFromFile(filepath.Join("..", "..", "signerverifier", "test-data", "rsa-test-key"))

Check failure on line 35 in encrypterdecrypter/asymmetric/rsa_test.go

View workflow job for this annotation

GitHub Actions / Run staticcheck

sv.LoadRSAPSSKeyFromFile is deprecated: use LoadKey(). The custom serialization format has been deprecated. Use https://github.com/secure-systems-lab/securesystemslib/blob/main/docs/migrate_key.py to convert your key. (SA1019)
if err != nil {
t.Error(err)
}

ed, err := NewRSAEncrypterDecrypterFromSSLibKey(key)
if err != nil {
t.Error(err)
}

ciphertext, err := ed.Encrypt(plaintext)
assert.Nil(t, err)

decryptedPlaintext, err := ed.Decrypt(ciphertext)
assert.Nil(t, err)

assert.Equal(t, plaintext, decryptedPlaintext)
}

func TestRoundtripCorrupted(t *testing.T) {
key, err := sv.LoadRSAPSSKeyFromFile(filepath.Join("..", "..", "signerverifier", "test-data", "rsa-test-key"))

Check failure on line 55 in encrypterdecrypter/asymmetric/rsa_test.go

View workflow job for this annotation

GitHub Actions / Run staticcheck

sv.LoadRSAPSSKeyFromFile is deprecated: use LoadKey(). The custom serialization format has been deprecated. Use https://github.com/secure-systems-lab/securesystemslib/blob/main/docs/migrate_key.py to convert your key. (SA1019)
if err != nil {
t.Error(err)
}

ed, err := NewRSAEncrypterDecrypterFromSSLibKey(key)
if err != nil {
t.Error(err)
}

ciphertext, err := ed.Encrypt(plaintext)
assert.Nil(t, err)

ciphertext[0] = ^ciphertext[0]

_, err = ed.Decrypt(ciphertext)
assert.ErrorContains(t, err, "decryption error")
}
22 changes: 22 additions & 0 deletions encrypterdecrypter/encrypterdecrypter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package encrypterdecrypter

// Encrypter is the interface for an abstract asymmetric or symmetric block
// encryption algorithm. The Encrypter interface is used to encrypt arbitrary
// byte payloads, and returns the ciphertext, or an error. It is
// cipher-agnostic.
type Encrypter interface {
Encrypt([]byte) ([]byte, error)
KeyID() (string, error)
}

// Decrypter is the interface to decrypt ciphertext.
type Decrypter interface {
Decrypt([]byte) ([]byte, error)
KeyID() (string, error)
}

// EncrypterDecrypter is the combined interface of Encrypter and Decrypter.
type EncrypterDecrypter interface {
Encrypter
Decrypter
}
103 changes: 103 additions & 0 deletions encrypterdecrypter/symmetric/aes.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package symmetric

import (
"crypto/aes"
"crypto/cipher"
"errors"
)

var (
ErrInvalidKeyLength = errors.New("invalid key length")
ErrInvalidMode = errors.New("invalid mode")
)

type AESMode uint8

const (
GCM AESMode = iota
)

type AESEncrypterDecrypter struct {
keyID string
keyBytes []byte
mode AESMode
}

// NewAESEncrypterDecrypterFromSSLibSymmetricKey creates an
// AESEncrypterDecrypter from an SSLibSymmetricKey.
func NewAESEncrypterDecrypterFromSSLibSymmetricKey(key *SSLibSymmetricKey, mode AESMode) (*AESEncrypterDecrypter, error) {
switch mode {
case GCM:
break
default:
return nil, ErrInvalidMode
}

return &AESEncrypterDecrypter{
keyID: key.KeyID,
keyBytes: key.KeyVal,
mode: mode,
}, nil
}

// Encrypt encrypts the provided data with the key of the AES
// EncrypterDecrypter.
func (ed *AESEncrypterDecrypter) Encrypt(data []byte) ([]byte, error) {
block, err := aes.NewCipher(ed.keyBytes)
if err != nil {
return nil, err
}

var ciphertext []byte

switch ed.mode {
case GCM:
gcm, err := cipher.NewGCMWithRandomNonce(block)

Check failure on line 55 in encrypterdecrypter/symmetric/aes.go

View workflow job for this annotation

GitHub Actions / test (1.23.x, ubuntu-latest)

undefined: cipher.NewGCMWithRandomNonce

Check failure on line 55 in encrypterdecrypter/symmetric/aes.go

View workflow job for this annotation

GitHub Actions / test (1.23.x, macos-latest)

undefined: cipher.NewGCMWithRandomNonce
if err != nil {
return nil, err
}

ciphertext = gcm.Seal(nil, nil, data, nil)
}
return ciphertext, nil
}

// Decrypt decrypts the provided data with the key of the AES
// EncrypterDecrypter.
func (ed *AESEncrypterDecrypter) Decrypt(data []byte) ([]byte, error) {
block, err := aes.NewCipher(ed.keyBytes)
if err != nil {
return nil, err
}

var plaintext []byte
switch ed.mode {
case GCM:
gcm, err := cipher.NewGCMWithRandomNonce(block)

Check failure on line 76 in encrypterdecrypter/symmetric/aes.go

View workflow job for this annotation

GitHub Actions / test (1.23.x, ubuntu-latest)

undefined: cipher.NewGCMWithRandomNonce

Check failure on line 76 in encrypterdecrypter/symmetric/aes.go

View workflow job for this annotation

GitHub Actions / test (1.23.x, macos-latest)

undefined: cipher.NewGCMWithRandomNonce
if err != nil {
return nil, err
}

plaintext, err = gcm.Open(nil, nil, data, nil)
if err != nil {
return nil, err
}
}
return plaintext, nil
}

// KeyID returns the key ID of the key used to create the AES EncrypterDecrypter
// instance.
func (ed *AESEncrypterDecrypter) KeyID() (string, error) {
return ed.keyID, nil
}

func validateAESKeySize(key []byte) (int, error) {
switch len(key) {
// AES-128, AES-192, AES-256
case 16, 24, 32:
return len(key) / 8, nil
default:
return 0, ErrInvalidKeyLength
}
}
53 changes: 53 additions & 0 deletions encrypterdecrypter/symmetric/aes_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package symmetric

import (
"encoding/hex"
"fmt"
"testing"

"github.com/secure-systems-lab/go-securesystemslib/encrypterdecrypter/symmetric/testdata"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

var plaintext = []byte("reallyimportant")

func TestRoundtrip(t *testing.T) {
key, err := hex.DecodeString(string(testdata.AESKey))
require.Nil(t, err)

fmt.Println(key)

ed := &AESEncrypterDecrypter{
keyID: "super secret key",
keyBytes: key,
mode: GCM,
}

ciphertext, err := ed.Encrypt(plaintext)
assert.Nil(t, err)

decryptedPlaintext, err := ed.Decrypt(ciphertext)
assert.Nil(t, err)

assert.Equal(t, plaintext, decryptedPlaintext)
}

func TestRoundtripCorrupted(t *testing.T) {
key, err := hex.DecodeString(string(testdata.AESKey))
require.Nil(t, err)

ed := &AESEncrypterDecrypter{
keyID: "super secret key",
keyBytes: key,
mode: GCM,
}

ciphertext, err := ed.Encrypt(plaintext)
assert.Nil(t, err)

ciphertext[0] = ^ciphertext[0]

_, err = ed.Decrypt(ciphertext)
assert.ErrorContains(t, err, "message authentication failed")
}
56 changes: 56 additions & 0 deletions encrypterdecrypter/symmetric/symmetric.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package symmetric

import (
"encoding/hex"
"errors"
)

var ErrUnknownKeyType = errors.New("unknown key type")

type SSLibSymmetricCipher uint8

const (
AES SSLibSymmetricCipher = iota
)

type SSLibSymmetricKey struct {
KeyID string `json:"keyid"`
Cipher SSLibSymmetricCipher `json:"scheme"`
KeySize int `json:"keysize"`
KeyVal []byte `json:"keyval"`
}

// LoadSymmetricKey returns an SSLibSymmetricKey object when provided a byte
// array and cipher. Currently, AES-128/192/256 are supported.
func LoadSymmetricKey(keyBytes []byte, cipher SSLibSymmetricCipher) (*SSLibSymmetricKey, error) {
var key *SSLibSymmetricKey

switch cipher {
case AES:
keyBytes, err := hex.DecodeString(string(keyBytes))
if err != nil {
return nil, err
}

keySize, err := validateAESKeySize(keyBytes)
if err != nil {
return nil, err
}

key = &SSLibSymmetricKey{
KeyID: "",
Cipher: cipher,
KeySize: keySize,
KeyVal: keyBytes,
}
default:
return nil, ErrUnknownKeyType
}

keyID, err := CalculateSymmetricKeyID(key)
if err != nil {
return nil, err
}
key.KeyID = keyID
return key, nil
}
Loading
Loading