Skip to content

Commit 01d8bbe

Browse files
✨ Add support for signing and verifying messages using ed25519 (#490)
<!-- Copyright (C) 2020-2022 Arm Limited or its affiliates and Contributors. All rights reserved. SPDX-License-Identifier: Apache-2.0 --> ### Description <!-- Please add any detail or context that would be useful to a reviewer. --> Add support for signing and verifying messages using ed25519 ### Test Coverage <!-- Please put an `x` in the correct box e.g. `[x]` to indicate the testing coverage of this change. --> - [x] This change is covered by existing or additional automated tests. - [ ] Manual testing has been performed (and evidence provided) as automated testing was not feasible. - [ ] Additional tests are not required for this change (e.g. documentation update). --------- Co-authored-by: Adrien CABARBAYE <adrien.cabarbaye@arm.com>
1 parent 3998f0d commit 01d8bbe

File tree

5 files changed

+489
-1
lines changed

5 files changed

+489
-1
lines changed

.secrets.baseline

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,10 @@
6666
{
6767
"path": "detect_secrets.filters.allowlist.is_line_allowlisted"
6868
},
69+
{
70+
"path": "detect_secrets.filters.common.is_baseline_file",
71+
"filename": ".secrets.baseline"
72+
},
6973
{
7074
"path": "detect_secrets.filters.common.is_ignored_due_to_verification_policies",
7175
"min_level": 2
@@ -238,6 +242,22 @@
238242
"line_number": 84
239243
}
240244
],
245+
"utils/signing/signing_test.go": [
246+
{
247+
"type": "Base64 High Entropy String",
248+
"filename": "utils/signing/signing_test.go",
249+
"hashed_secret": "46d495b98efbb227351ba4b3bac4e2fe537270ae",
250+
"is_verified": false,
251+
"line_number": 76
252+
},
253+
{
254+
"type": "Base64 High Entropy String",
255+
"filename": "utils/signing/signing_test.go",
256+
"hashed_secret": "48fb0bae9145db4e31dbe2e7c450e0fca3f8d530",
257+
"is_verified": false,
258+
"line_number": 268
259+
}
260+
],
241261
"utils/strings/strings_test.go": [
242262
{
243263
"type": "Base64 High Entropy String",
@@ -248,5 +268,5 @@
248268
}
249269
]
250270
},
251-
"generated_at": "2023-09-27T08:06:22Z"
271+
"generated_at": "2024-08-14T13:57:27Z"
252272
}

changes/20240814105842.feature

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
:sparkles: Add support for signing and verifying messages using ed25519

utils/signing/interface.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package signing
2+
3+
import "github.com/ARM-software/golang-utils/utils/encryption"
4+
5+
type ICodeSigner interface {
6+
encryption.IKeyPair
7+
// Sign will sign a message and return a signature
8+
Sign(message []byte) (signature []byte, err error)
9+
// Verify will take a message and a signature and verify whether the signature is a valid signature of the message based on the signers public key
10+
Verify(message, signature []byte) (ok bool, err error)
11+
// GenerateSignature will sign a message but return a base64 encoded signature for ease of use
12+
GenerateSignature(message []byte) (signatureBase64 string, err error)
13+
// Verify will take a message and a base64 encoded signature and verify whether the signature is a valid signature of the message based on the signers public key
14+
VerifySignature(message []byte, signatureBase64 string) (ok bool, err error)
15+
}

utils/signing/signing.go

Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
package signing
2+
3+
import (
4+
"crypto/ed25519"
5+
"crypto/rand"
6+
"encoding/base64"
7+
"fmt"
8+
"io"
9+
10+
"github.com/ARM-software/golang-utils/utils/commonerrors"
11+
)
12+
13+
type Ed25519Signer struct {
14+
Public ed25519.PublicKey `json:"public"`
15+
private ed25519.PrivateKey `json:"-"`
16+
seed []byte `json:"-"`
17+
}
18+
19+
func (k *Ed25519Signer) String() string {
20+
return fmt.Sprintf("{Public: %v}", k.GetPublicKey())
21+
}
22+
23+
func (k *Ed25519Signer) GoString() string {
24+
return fmt.Sprintf("KeyPair(%q)", k.String())
25+
}
26+
27+
func (k *Ed25519Signer) MarshalJSON() (jsonBytes []byte, err error) {
28+
return []byte(fmt.Sprintf("{\"public\":%q}", k.GetPublicKey())), nil
29+
}
30+
31+
func (k *Ed25519Signer) GetPublicKey() string {
32+
return base64.StdEncoding.EncodeToString(k.Public)
33+
}
34+
35+
func (k *Ed25519Signer) GetPrivateKey() string {
36+
return base64.StdEncoding.EncodeToString(k.private)
37+
}
38+
39+
func (k *Ed25519Signer) Sign(message []byte) (signature []byte, err error) {
40+
if len(k.private) == 0 {
41+
err = fmt.Errorf("%w: missing private key", commonerrors.ErrUndefined)
42+
return
43+
}
44+
if len(k.private) != ed25519.PrivateKeySize {
45+
err = fmt.Errorf("%w: invalid private key length %v", commonerrors.ErrInvalid, len(k.private))
46+
return
47+
}
48+
49+
signature, err = k.private.Sign(nil, message, &ed25519.Options{})
50+
if err != nil {
51+
err = fmt.Errorf("%w: error occured whilst signing: %v", commonerrors.ErrUnexpected, err.Error())
52+
return
53+
}
54+
55+
return
56+
}
57+
58+
func (k *Ed25519Signer) GenerateSignature(message []byte) (signatureBase64 string, err error) {
59+
signature, err := k.Sign(message)
60+
if err != nil {
61+
return
62+
}
63+
signatureBase64 = base64.StdEncoding.EncodeToString(signature)
64+
return
65+
}
66+
67+
func (k *Ed25519Signer) Verify(message, signature []byte) (ok bool, err error) {
68+
if len(k.Public) == 0 {
69+
err = fmt.Errorf("%w: missing public key", commonerrors.ErrUndefined)
70+
return
71+
}
72+
if len(k.Public) != ed25519.PublicKeySize {
73+
err = fmt.Errorf("%w: invalid public key length %v", commonerrors.ErrInvalid, len(k.Public))
74+
return
75+
}
76+
77+
ok = ed25519.Verify(k.Public, message, signature)
78+
return
79+
}
80+
81+
func (k *Ed25519Signer) VerifySignature(message []byte, signatureBase64 string) (ok bool, err error) {
82+
signature, err := base64.StdEncoding.DecodeString(signatureBase64)
83+
if err != nil {
84+
return
85+
}
86+
87+
ok, err = k.Verify(message, signature)
88+
return
89+
}
90+
91+
// NewEd25519Signer will create a Ed25519Signer that can both sign new messages as well as verify them
92+
func NewEd25519Signer(privateKey ed25519.PrivateKey) (signer *Ed25519Signer, err error) {
93+
if privateKey == nil {
94+
err = fmt.Errorf("%w: privateKey must be defined", commonerrors.ErrUndefined)
95+
return
96+
}
97+
if len(privateKey) != ed25519.PrivateKeySize {
98+
err = fmt.Errorf("%w: private key must have length %v, it has length %v", commonerrors.ErrInvalid, ed25519.PrivateKeySize, len(privateKey))
99+
return
100+
}
101+
102+
publicKey, ok := privateKey.Public().(ed25519.PublicKey)
103+
if !ok {
104+
err = fmt.Errorf("%w: could not extract public key from private key", commonerrors.ErrUnexpected)
105+
return
106+
}
107+
108+
signer = &Ed25519Signer{
109+
Public: publicKey,
110+
private: privateKey,
111+
seed: privateKey.Seed(),
112+
}
113+
114+
return
115+
}
116+
117+
// NewEd25519Verifier will create a Ed25519Signer with only a public key meaning it can only verify messages
118+
func NewEd25519Verifier(publicKey ed25519.PublicKey) (signer *Ed25519Signer, err error) {
119+
if publicKey == nil {
120+
err = fmt.Errorf("%w: publicKey must be defined", commonerrors.ErrUndefined)
121+
return
122+
}
123+
if len(publicKey) != ed25519.PublicKeySize {
124+
err = fmt.Errorf("%w: public key must have length %v, it has length %v", commonerrors.ErrInvalid, ed25519.PrivateKeySize, len(publicKey))
125+
return
126+
}
127+
128+
signer = &Ed25519Signer{
129+
Public: publicKey,
130+
}
131+
132+
return
133+
}
134+
135+
// NewEd25519SignerFromBase64 will create a Ed25519Signer that can both sign new messages as well as verify them
136+
// It will take a private key encoded as base64
137+
func NewEd25519SignerFromBase64(privateKeyB64 string) (signer *Ed25519Signer, err error) {
138+
privateKey, err := base64.StdEncoding.DecodeString(privateKeyB64)
139+
if err != nil {
140+
err = fmt.Errorf("%w: could not decode private key from base64: %v", commonerrors.ErrInvalid, err.Error())
141+
return
142+
}
143+
144+
return NewEd25519Signer(privateKey)
145+
}
146+
147+
// NewEd25519VerifierFromBase64 will create a Ed25519Signer with only a public key meaning it can only verify messages
148+
// It will take a public key encoded as base64
149+
func NewEd25519VerifierFromBase64(publicKeyB64 string) (signer *Ed25519Signer, err error) {
150+
publicKey, err := base64.StdEncoding.DecodeString(publicKeyB64)
151+
if err != nil {
152+
err = fmt.Errorf("%w: could not decode public key from base64: %v", commonerrors.ErrInvalid, err.Error())
153+
return
154+
}
155+
156+
return NewEd25519Verifier(publicKey)
157+
}
158+
159+
// NewEd25519SignerFromSeed will create an Ed25519Signer based on a seed. It will automatically pad the seed to the correct length
160+
// A seed for Ed25519 should be 32 characters long. Anything shorter will be padded with zeros and anything longer will be truncated
161+
func NewEd25519SignerFromSeed(inputSeed string) (pair *Ed25519Signer, err error) {
162+
seed := make([]byte, ed25519.SeedSize)
163+
if inputSeed == "" {
164+
_, err = io.ReadFull(rand.Reader, seed)
165+
if err != nil {
166+
return
167+
}
168+
} else {
169+
for i := range seed {
170+
if i < len(inputSeed) {
171+
seed[i] = inputSeed[i]
172+
} else {
173+
seed[i] = '0'
174+
}
175+
}
176+
}
177+
178+
privateKey := ed25519.NewKeyFromSeed(seed)
179+
pair, err = NewEd25519Signer(privateKey)
180+
pair.seed = seed
181+
return
182+
}

0 commit comments

Comments
 (0)