Skip to content

Commit aff18cb

Browse files
committed
[WIP] encrypterdecrypter: Add encryption and decryption routines
1 parent 9d9f015 commit aff18cb

File tree

16 files changed

+460
-24
lines changed

16 files changed

+460
-24
lines changed

encrypterdecrypter/asymmetric/rsa.go

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package asymmetric
2+
3+
import (
4+
"crypto/rand"
5+
"crypto/rsa"
6+
"crypto/sha256"
7+
"fmt"
8+
9+
sv "github.com/secure-systems-lab/go-securesystemslib/signerverifier"
10+
)
11+
12+
type RSAEncrypterDecrypter struct {
13+
keyID string
14+
private *rsa.PrivateKey
15+
public *rsa.PublicKey
16+
}
17+
18+
func NewRSAEncrypterDecrypterFromSSLibKey(key *sv.SSLibKey) (*RSAEncrypterDecrypter, error) {
19+
if len(key.KeyVal.Public) == 0 {
20+
return nil, sv.ErrInvalidKey
21+
}
22+
23+
_, publicParsedKey, err := sv.DecodeAndParsePEM([]byte(key.KeyVal.Public))
24+
if err != nil {
25+
return nil, fmt.Errorf("unable to create RSA encrypterdecrypter: %w", err)
26+
}
27+
28+
if len(key.KeyVal.Private) > 0 {
29+
_, privateParsedKey, err := sv.DecodeAndParsePEM([]byte(key.KeyVal.Private))
30+
if err != nil {
31+
return nil, fmt.Errorf("unable to create RSA encrypterdecrypter: %w", err)
32+
}
33+
34+
return &RSAEncrypterDecrypter{
35+
keyID: key.KeyID,
36+
public: publicParsedKey.(*rsa.PublicKey),
37+
private: privateParsedKey.(*rsa.PrivateKey),
38+
}, nil
39+
}
40+
41+
return &RSAEncrypterDecrypter{
42+
keyID: key.KeyID,
43+
public: publicParsedKey.(*rsa.PublicKey),
44+
private: nil,
45+
}, nil
46+
}
47+
48+
// Encrypt encrypts the provided data with the public key of the RSA
49+
// EncrypterDecrypter instance.
50+
func (ed *RSAEncrypterDecrypter) Encrypt(data []byte) ([]byte, error) {
51+
rng := rand.Reader
52+
return rsa.EncryptOAEP(sha256.New(), rng, ed.public, data, nil)
53+
}
54+
55+
// Decrypt decrypts the provided data with the private key of the RSA
56+
// EncrypterDecrypter instance.
57+
func (ed *RSAEncrypterDecrypter) Decrypt(data []byte) ([]byte, error) {
58+
if ed.private == nil {
59+
return nil, sv.ErrNotPrivateKey
60+
}
61+
62+
return rsa.DecryptOAEP(sha256.New(), nil, ed.private, data, nil)
63+
}
64+
65+
// KeyID returns the key ID of the key used to create the RSA EncrypterDecrypter
66+
// instance.
67+
func (ed *RSAEncrypterDecrypter) KeyID() (string, error) {
68+
return ed.keyID, nil
69+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package asymmetric
2+
3+
import (
4+
"crypto/rsa"
5+
"path/filepath"
6+
"testing"
7+
8+
sv "github.com/secure-systems-lab/go-securesystemslib/signerverifier"
9+
"github.com/stretchr/testify/assert"
10+
)
11+
12+
var plaintext = []byte("reallyimportant")
13+
14+
func TestNewRSAEncrypterDecrypterFromSSLibKey(t *testing.T) {
15+
key, err := sv.LoadRSAPSSKeyFromFile(filepath.Join("..", "..", "signerverifier", "test-data", "rsa-test-key.pub"))
16+
if err != nil {
17+
t.Error(err)
18+
}
19+
20+
ed, err := NewRSAEncrypterDecrypterFromSSLibKey(key)
21+
if err != nil {
22+
t.Error(err)
23+
}
24+
25+
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-----"
26+
_, expectedPublicKey, err := sv.DecodeAndParsePEM([]byte(expectedPublicString))
27+
assert.Nil(t, err)
28+
29+
assert.Equal(t, "4e8d20af09fcaed6c388a186427f94a5f7ff5591ec295f4aab2cff49ffe39e9b", ed.keyID)
30+
assert.Equal(t, expectedPublicKey.(*rsa.PublicKey), ed.public)
31+
assert.Nil(t, ed.private)
32+
}
33+
34+
func TestRoundtrip(t *testing.T) {
35+
key, err := sv.LoadRSAPSSKeyFromFile(filepath.Join("..", "..", "signerverifier", "test-data", "rsa-test-key"))
36+
if err != nil {
37+
t.Error(err)
38+
}
39+
40+
ed, err := NewRSAEncrypterDecrypterFromSSLibKey(key)
41+
if err != nil {
42+
t.Error(err)
43+
}
44+
45+
ciphertext, err := ed.Encrypt(plaintext)
46+
assert.Nil(t, err)
47+
48+
decryptedPlaintext, err := ed.Decrypt(ciphertext)
49+
assert.Nil(t, err)
50+
51+
assert.Equal(t, plaintext, decryptedPlaintext)
52+
}
53+
54+
func TestRoundtripCorrupted(t *testing.T) {
55+
key, err := sv.LoadRSAPSSKeyFromFile(filepath.Join("..", "..", "signerverifier", "test-data", "rsa-test-key"))
56+
if err != nil {
57+
t.Error(err)
58+
}
59+
60+
ed, err := NewRSAEncrypterDecrypterFromSSLibKey(key)
61+
if err != nil {
62+
t.Error(err)
63+
}
64+
65+
ciphertext, err := ed.Encrypt(plaintext)
66+
assert.Nil(t, err)
67+
68+
ciphertext[0] = ^ciphertext[0]
69+
70+
_, err = ed.Decrypt(ciphertext)
71+
assert.ErrorContains(t, err, "decryption error")
72+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package encrypterdecrypter
2+
3+
// Encrypter is the interface for an abstract asymmetric or symmetric block
4+
// encryption algorithm. The Encrypter interface is used to encrypt arbitrary
5+
// byte payloads, and returns the ciphertext, or an error. It is
6+
// cipher-agnostic.
7+
type Encrypter interface {
8+
Encrypt([]byte) ([]byte, error)
9+
KeyID() (string, error)
10+
}
11+
12+
// Decrypter is the interface to decrypt ciphertext.
13+
type Decrypter interface {
14+
Decrypt([]byte) ([]byte, error)
15+
KeyID() (string, error)
16+
}
17+
18+
// EncrypterDecrypter is the combined interface of Encrypter and Decrypter.
19+
type EncrypterDecrypter interface {
20+
Encrypter
21+
Decrypter
22+
}

encrypterdecrypter/symmetric/aes.go

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
package symmetric
2+
3+
import (
4+
"crypto/aes"
5+
"crypto/cipher"
6+
"errors"
7+
)
8+
9+
var (
10+
ErrInvalidKeyLength = errors.New("invalid key length")
11+
ErrInvalidMode = errors.New("invalid mode")
12+
)
13+
14+
type AESMode uint8
15+
16+
const (
17+
GCM AESMode = iota
18+
)
19+
20+
type AESEncrypterDecrypter struct {
21+
keyID string
22+
keyBytes []byte
23+
mode AESMode
24+
}
25+
26+
// NewAESEncrypterDecrypterFromSSLibSymmetricKey creates an
27+
// AESEncrypterDecrypter from an SSLibSymmetricKey.
28+
func NewAESEncrypterDecrypterFromSSLibSymmetricKey(key *SSLibSymmetricKey, mode AESMode) (*AESEncrypterDecrypter, error) {
29+
switch mode {
30+
case GCM:
31+
break
32+
default:
33+
return nil, ErrInvalidMode
34+
}
35+
36+
return &AESEncrypterDecrypter{
37+
keyID: key.KeyID,
38+
keyBytes: key.KeyVal,
39+
mode: mode,
40+
}, nil
41+
}
42+
43+
// Encrypt encrypts the provided data with the key of the AES
44+
// EncrypterDecrypter.
45+
func (ed *AESEncrypterDecrypter) Encrypt(data []byte) ([]byte, error) {
46+
block, err := aes.NewCipher(ed.keyBytes)
47+
if err != nil {
48+
return nil, err
49+
}
50+
51+
var ciphertext []byte
52+
53+
switch ed.mode {
54+
case GCM:
55+
gcm, err := cipher.NewGCMWithRandomNonce(block)
56+
if err != nil {
57+
return nil, err
58+
}
59+
60+
ciphertext = gcm.Seal(nil, nil, data, nil)
61+
}
62+
return ciphertext, nil
63+
}
64+
65+
// Decrypt decrypts the provided data with the key of the AES
66+
// EncrypterDecrypter.
67+
func (ed *AESEncrypterDecrypter) Decrypt(data []byte) ([]byte, error) {
68+
block, err := aes.NewCipher(ed.keyBytes)
69+
if err != nil {
70+
return nil, err
71+
}
72+
73+
var plaintext []byte
74+
switch ed.mode {
75+
case GCM:
76+
gcm, err := cipher.NewGCMWithRandomNonce(block)
77+
if err != nil {
78+
return nil, err
79+
}
80+
81+
plaintext, err = gcm.Open(nil, nil, data, nil)
82+
if err != nil {
83+
return nil, err
84+
}
85+
}
86+
return plaintext, nil
87+
}
88+
89+
// KeyID returns the key ID of the key used to create the AES EncrypterDecrypter
90+
// instance.
91+
func (ed *AESEncrypterDecrypter) KeyID() (string, error) {
92+
return ed.keyID, nil
93+
}
94+
95+
func validateAESKeySize(key []byte) (int, error) {
96+
switch len(key) {
97+
// AES-128, AES-192, AES-256
98+
case 16, 24, 32:
99+
return len(key) / 8, nil
100+
default:
101+
return 0, ErrInvalidKeyLength
102+
}
103+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package symmetric
2+
3+
import (
4+
"encoding/hex"
5+
"fmt"
6+
"testing"
7+
8+
"github.com/secure-systems-lab/go-securesystemslib/encrypterdecrypter/symmetric/testdata"
9+
"github.com/stretchr/testify/assert"
10+
"github.com/stretchr/testify/require"
11+
)
12+
13+
var plaintext = []byte("reallyimportant")
14+
15+
func TestRoundtrip(t *testing.T) {
16+
key, err := hex.DecodeString(string(testdata.AESKey))
17+
require.Nil(t, err)
18+
19+
fmt.Println(key)
20+
21+
ed := &AESEncrypterDecrypter{
22+
keyID: "super secret key",
23+
keyBytes: key,
24+
mode: GCM,
25+
}
26+
27+
ciphertext, err := ed.Encrypt(plaintext)
28+
assert.Nil(t, err)
29+
30+
decryptedPlaintext, err := ed.Decrypt(ciphertext)
31+
assert.Nil(t, err)
32+
33+
assert.Equal(t, plaintext, decryptedPlaintext)
34+
}
35+
36+
func TestRoundtripCorrupted(t *testing.T) {
37+
key, err := hex.DecodeString(string(testdata.AESKey))
38+
require.Nil(t, err)
39+
40+
ed := &AESEncrypterDecrypter{
41+
keyID: "super secret key",
42+
keyBytes: key,
43+
mode: GCM,
44+
}
45+
46+
ciphertext, err := ed.Encrypt(plaintext)
47+
assert.Nil(t, err)
48+
49+
ciphertext[0] = ^ciphertext[0]
50+
51+
_, err = ed.Decrypt(ciphertext)
52+
assert.ErrorContains(t, err, "message authentication failed")
53+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package symmetric
2+
3+
import (
4+
"encoding/hex"
5+
"errors"
6+
)
7+
8+
var ErrUnknownKeyType = errors.New("unknown key type")
9+
10+
type SSLibSymmetricCipher uint8
11+
12+
const (
13+
AES SSLibSymmetricCipher = iota
14+
)
15+
16+
type SSLibSymmetricKey struct {
17+
KeyID string `json:"keyid"`
18+
Cipher SSLibSymmetricCipher `json:"scheme"`
19+
KeySize int `json:"keysize"`
20+
KeyVal []byte `json:"keyval"`
21+
}
22+
23+
// LoadSymmetricKey returns an SSLibSymmetricKey object when provided a byte
24+
// array and cipher. Currently, AES-128/192/256 are supported.
25+
func LoadSymmetricKey(keyBytes []byte, cipher SSLibSymmetricCipher) (*SSLibSymmetricKey, error) {
26+
var key *SSLibSymmetricKey
27+
28+
switch cipher {
29+
case AES:
30+
keyBytes, err := hex.DecodeString(string(keyBytes))
31+
if err != nil {
32+
return nil, err
33+
}
34+
35+
keySize, err := validateAESKeySize(keyBytes)
36+
if err != nil {
37+
return nil, err
38+
}
39+
40+
key = &SSLibSymmetricKey{
41+
KeyID: "",
42+
Cipher: cipher,
43+
KeySize: keySize,
44+
KeyVal: keyBytes,
45+
}
46+
default:
47+
return nil, ErrUnknownKeyType
48+
}
49+
50+
keyID, err := CalculateSymmetricKeyID(key)
51+
if err != nil {
52+
return nil, err
53+
}
54+
key.KeyID = keyID
55+
return key, nil
56+
}

0 commit comments

Comments
 (0)