Skip to content

Commit 0581e17

Browse files
committed
Add RSS PSS SignerVerifier
Signed-off-by: Aditya Sirish <[email protected]>
1 parent 307123d commit 0581e17

File tree

9 files changed

+387
-47
lines changed

9 files changed

+387
-47
lines changed

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
module github.com/secure-systems-lab/go-securesystemslib
22

3-
go 1.17
3+
go 1.20
44

55
require (
66
github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb

signerverifier/ed25519.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"crypto"
66
"crypto/ed25519"
77
"encoding/hex"
8+
"os"
89
)
910

1011
const Ed25519KeyType = "ed25519"
@@ -77,3 +78,12 @@ func (sv Ed25519SignerVerifier) KeyID() (string, error) {
7778
func (sv Ed25519SignerVerifier) Public() crypto.PublicKey {
7879
return sv.public
7980
}
81+
82+
func LoadEd25519KeyFromFile(path string) (*SSLibKey, error) {
83+
contents, err := os.ReadFile(path)
84+
if err != nil {
85+
return nil, err
86+
}
87+
88+
return loadKeyFromSSLibBytes(contents)
89+
}

signerverifier/ed25519_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import (
1111
)
1212

1313
func TestNewEd25519SignerVerifierFromSSLibKey(t *testing.T) {
14-
key, err := LoadKeyFromFile(filepath.Join("test-data", "ed25519-test-key.pub"))
14+
key, err := LoadEd25519KeyFromFile(filepath.Join("test-data", "ed25519-test-key.pub"))
1515
if err != nil {
1616
t.Error(err)
1717
}
@@ -30,7 +30,7 @@ func TestNewEd25519SignerVerifierFromSSLibKey(t *testing.T) {
3030
}
3131

3232
func TestEd25519SignerVerifierSign(t *testing.T) {
33-
key, err := LoadKeyFromFile(filepath.Join("test-data", "ed25519-test-key"))
33+
key, err := LoadEd25519KeyFromFile(filepath.Join("test-data", "ed25519-test-key"))
3434
if err != nil {
3535
t.Fatal(err)
3636
}
@@ -50,7 +50,7 @@ func TestEd25519SignerVerifierSign(t *testing.T) {
5050
expectedSignature := []byte{0x80, 0x72, 0xb4, 0x31, 0xc5, 0xa3, 0x7e, 0xc, 0xf3, 0x91, 0x22, 0x3, 0x60, 0xbf, 0x92, 0xa4, 0x46, 0x31, 0x84, 0x83, 0xf1, 0x31, 0x3, 0xdc, 0xbc, 0x5, 0x6f, 0xab, 0x84, 0xe4, 0xdc, 0xe9, 0xf5, 0x1c, 0xa9, 0xb3, 0x95, 0xa5, 0xa0, 0x16, 0xd3, 0xaa, 0x4d, 0xe7, 0xde, 0xaf, 0xc2, 0x5e, 0x1e, 0x9a, 0x9d, 0xc8, 0xb2, 0x5c, 0x1c, 0x68, 0xf7, 0x28, 0xb4, 0x1, 0x4d, 0x9f, 0xc8, 0x4}
5151
assert.Equal(t, expectedSignature, signature)
5252

53-
key, err = LoadKeyFromFile(filepath.Join("test-data", "ed25519-test-key.pub"))
53+
key, err = LoadEd25519KeyFromFile(filepath.Join("test-data", "ed25519-test-key.pub"))
5454
if err != nil {
5555
t.Fatal(err)
5656
}
@@ -65,7 +65,7 @@ func TestEd25519SignerVerifierSign(t *testing.T) {
6565
}
6666

6767
func TestEd25519SignerVerifierVerify(t *testing.T) {
68-
key, err := LoadKeyFromFile(filepath.Join("test-data", "ed25519-test-key.pub"))
68+
key, err := LoadEd25519KeyFromFile(filepath.Join("test-data", "ed25519-test-key.pub"))
6969
if err != nil {
7070
t.Fatal(err)
7171
}

signerverifier/rsa.go

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
package signerverifier
2+
3+
import (
4+
"context"
5+
"crypto"
6+
"crypto/rand"
7+
"crypto/rsa"
8+
"crypto/sha256"
9+
"crypto/x509"
10+
"errors"
11+
"os"
12+
)
13+
14+
// ErrNoPEMBlock gets triggered when there is no PEM block in the provided file
15+
var ErrNoPEMBlock = errors.New("failed to decode the data as PEM block (are you sure this is a pem file?)")
16+
17+
// ErrFailedPEMParsing gets returned when PKCS1, PKCS8 or PKIX key parsing fails
18+
var ErrFailedPEMParsing = errors.New("failed parsing the PEM block: unsupported PEM type")
19+
20+
const (
21+
RSAKeyType = "rsa"
22+
RSAKeyScheme = "rsassa-pss-sha256"
23+
RSAPublicKeyPEM = "PUBLIC KEY"
24+
RSAPrivateKeyPEM = "RSA PRIVATE KEY"
25+
)
26+
27+
type RSAPSSSignerVerifier struct {
28+
keyID string
29+
private *rsa.PrivateKey
30+
public *rsa.PublicKey
31+
}
32+
33+
func NewRSAPSSSignerVerifierFromSSLibKey(key *SSLibKey) (*RSAPSSSignerVerifier, error) {
34+
_, publicParsedKey, err := decodeAndParsePEM([]byte(key.KeyVal.Public))
35+
if err != nil {
36+
return nil, err
37+
}
38+
39+
if len(key.KeyVal.Private) > 0 {
40+
_, privateParsedKey, err := decodeAndParsePEM([]byte(key.KeyVal.Private))
41+
if err != nil {
42+
return nil, err
43+
}
44+
45+
return &RSAPSSSignerVerifier{
46+
keyID: key.KeyID(),
47+
public: publicParsedKey.(*rsa.PublicKey),
48+
private: privateParsedKey.(*rsa.PrivateKey),
49+
}, nil
50+
}
51+
52+
return &RSAPSSSignerVerifier{
53+
keyID: key.KeyID(),
54+
public: publicParsedKey.(*rsa.PublicKey),
55+
private: nil,
56+
}, nil
57+
}
58+
59+
func (sv *RSAPSSSignerVerifier) Sign(ctx context.Context, data []byte) ([]byte, error) {
60+
if sv.private == nil {
61+
return nil, ErrNotPrivateKey
62+
}
63+
64+
hashedData := hashBeforeSigning(data)
65+
66+
return rsa.SignPSS(rand.Reader, sv.private, crypto.SHA256, hashedData, &rsa.PSSOptions{SaltLength: sha256.Size, Hash: crypto.SHA256})
67+
}
68+
69+
func (sv RSAPSSSignerVerifier) Verify(ctx context.Context, data []byte, sig []byte) error {
70+
hashedData := hashBeforeSigning(data)
71+
72+
if err := rsa.VerifyPSS(sv.public, crypto.SHA256, hashedData, sig, &rsa.PSSOptions{SaltLength: sha256.Size, Hash: crypto.SHA256}); err != nil {
73+
return ErrSignatureVerificationFailed
74+
}
75+
76+
return nil
77+
}
78+
79+
func (sv RSAPSSSignerVerifier) KeyID() (string, error) {
80+
return sv.keyID, nil
81+
}
82+
83+
func (sv RSAPSSSignerVerifier) Public() crypto.PublicKey {
84+
return sv.public
85+
}
86+
87+
func LoadRSAPSSKeyFromFile(path string) (*SSLibKey, error) {
88+
contents, err := os.ReadFile(path)
89+
if err != nil {
90+
return nil, err
91+
}
92+
93+
pemData, keyObj, err := decodeAndParsePEM(contents)
94+
if err != nil {
95+
return nil, err
96+
}
97+
98+
key := &SSLibKey{
99+
KeyType: RSAKeyType,
100+
Scheme: RSAKeyScheme,
101+
KeyIDHashAlgorithms: KeyIDHashAlgorithms,
102+
KeyVal: KeyVal{},
103+
}
104+
105+
switch k := keyObj.(type) {
106+
case *rsa.PublicKey:
107+
pubKeyBytes, err := x509.MarshalPKIXPublicKey(k)
108+
if err != nil {
109+
return nil, err
110+
}
111+
key.KeyVal.Public = string(generatePEMBlock(pubKeyBytes, RSAPublicKeyPEM))
112+
113+
case *rsa.PrivateKey:
114+
pubKeyBytes, err := x509.MarshalPKIXPublicKey(k.Public())
115+
if err != nil {
116+
return nil, err
117+
}
118+
key.KeyVal.Public = string(generatePEMBlock(pubKeyBytes, RSAPublicKeyPEM))
119+
key.KeyVal.Private = string(generatePEMBlock(pemData.Bytes, RSAPrivateKeyPEM))
120+
}
121+
122+
keyID, err := calculateKeyID(key)
123+
if err != nil {
124+
return nil, err
125+
}
126+
key.keyID = keyID
127+
128+
return key, nil
129+
}

signerverifier/rsa_test.go

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package signerverifier
2+
3+
import (
4+
"context"
5+
"crypto/rsa"
6+
"path/filepath"
7+
"testing"
8+
9+
"github.com/stretchr/testify/assert"
10+
)
11+
12+
func TestNewRSAPSSSignerVerifierFromSSLibKey(t *testing.T) {
13+
key, err := LoadRSAPSSKeyFromFile(filepath.Join("test-data", "rsa-test-key.pub"))
14+
if err != nil {
15+
t.Error(err)
16+
}
17+
18+
sv, err := NewRSAPSSSignerVerifierFromSSLibKey(key)
19+
if err != nil {
20+
t.Error(err)
21+
}
22+
23+
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-----"
24+
_, expectedPublicKey, err := decodeAndParsePEM([]byte(expectedPublicString))
25+
assert.Nil(t, err)
26+
27+
assert.Equal(t, "966c5d84ba73ccded42eb473c939d77336e4def253ffaf6739f8e983ef73dad8", sv.keyID) // FIXME: mismatch?
28+
assert.Equal(t, expectedPublicKey.(*rsa.PublicKey), sv.public)
29+
assert.Nil(t, sv.private)
30+
}
31+
32+
func TestRSAPSSSignerVerifierSignAndVerify(t *testing.T) {
33+
t.Run("using valid key", func(t *testing.T) {
34+
key, err := LoadRSAPSSKeyFromFile(filepath.Join("test-data", "rsa-test-key"))
35+
if err != nil {
36+
t.Error(err)
37+
}
38+
39+
sv, err := NewRSAPSSSignerVerifierFromSSLibKey(key)
40+
if err != nil {
41+
t.Error(err)
42+
}
43+
44+
message := []byte("test message")
45+
46+
signature, err := sv.Sign(context.Background(), message)
47+
assert.Nil(t, err)
48+
49+
err = sv.Verify(context.Background(), message, signature)
50+
assert.Nil(t, err)
51+
})
52+
53+
t.Run("using invalid key", func(t *testing.T) {
54+
key, err := LoadRSAPSSKeyFromFile(filepath.Join("test-data", "rsa-test-key.pub"))
55+
if err != nil {
56+
t.Error(err)
57+
}
58+
59+
sv, err := NewRSAPSSSignerVerifierFromSSLibKey(key)
60+
if err != nil {
61+
t.Error(err)
62+
}
63+
64+
message := []byte("test message")
65+
66+
_, err = sv.Sign(context.Background(), message)
67+
assert.ErrorIs(t, err, ErrNotPrivateKey)
68+
})
69+
}

signerverifier/signerverifier.go

Lines changed: 2 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,11 @@
11
package signerverifier
22

33
import (
4-
"crypto/sha256"
5-
"encoding/hex"
6-
"encoding/json"
74
"errors"
8-
"os"
9-
10-
"github.com/secure-systems-lab/go-securesystemslib/cjson"
115
)
126

7+
var KeyIDHashAlgorithms = []string{"sha256", "sha512"}
8+
139
var (
1410
ErrNotPrivateKey = errors.New("loaded key is not a private key")
1511
ErrSignatureVerificationFailed = errors.New("failed to verify signature")
@@ -34,39 +30,3 @@ type KeyVal struct {
3430
Public string `json:"public"`
3531
Certificate string `json:"certificate,omitempty"`
3632
}
37-
38-
// LoadKeyFromBytes returns a pointer to a Key instance created from the
39-
// contents of the bytes. The key contents are expected to be in the custom
40-
// securesystemslib format.
41-
func LoadKeyFromBytes(contents []byte) (*SSLibKey, error) {
42-
var key *SSLibKey
43-
if err := json.Unmarshal(contents, &key); err != nil {
44-
return nil, err
45-
}
46-
47-
keyID, err := calculateKeyID(key)
48-
if err != nil {
49-
return nil, err
50-
}
51-
key.keyID = keyID
52-
53-
return key, nil
54-
}
55-
56-
func LoadKeyFromFile(path string) (*SSLibKey, error) {
57-
contents, err := os.ReadFile(path)
58-
if err != nil {
59-
return nil, err
60-
}
61-
62-
return LoadKeyFromBytes(contents)
63-
}
64-
65-
func calculateKeyID(k *SSLibKey) (string, error) {
66-
canonical, err := cjson.EncodeCanonical(k)
67-
if err != nil {
68-
return "", err
69-
}
70-
digest := sha256.Sum256(canonical)
71-
return hex.EncodeToString(digest[:]), nil
72-
}

signerverifier/test-data/rsa-test-key

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
-----BEGIN RSA PRIVATE KEY-----
2+
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
39+
-----END RSA PRIVATE KEY-----
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
-----BEGIN PUBLIC KEY-----
2+
MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEA04egZRic+dZMVtiQc56D
3+
ejU4FF1q3aOkUKnD+Q4lTbj1zp6ODKJTcktupmrad68jqtMiSGG8he6ELFs377q8
4+
bbgEUMWgAf+06Q8oFvUSfOXzZNFI7H5SMPOJY5aDWIMIEZ8DlcO7TfkA7D3iAEJX
5+
xxTOVS3UAIk5umO7Y7t7yXr8O/C4u78krGazCnoblcekMLJZV4O/5BloWNAe/B1c
6+
vZdaZUf3brD4ZZrxEtXw/tefhn1aHsSUajVW2wwjSpKhqj7Z0XS3bDS3T95/3xsN
7+
6+hlS6A7rJfiWpKIRHj0vh2SXLDmmhQl1In8TD/aiycTUyWcBRHVPlYFgYPt6SaT
8+
VQSgMzSxC43/2fINb2fyt8SbUHJ3Ct+mzRzd/1AQikWhBdstJLxInewzjYE/sb+c
9+
2CmCxMPQG2BwmAWXaaumeJcXVPBlMgAcjMatM8bPByTbXpKDnQslOE7g/gswDIwn
10+
Em53T13mZzYUvbLJ0q3aljZVLIC3IZn3ZwA2yCWchBkVAgMBAAE=
11+
-----END PUBLIC KEY-----

0 commit comments

Comments
 (0)