Skip to content

Commit 3248367

Browse files
authored
add ed25519 support (#36)
Signed-off-by: Vasiliy Tolstov <[email protected]>
1 parent 860640e commit 3248367

File tree

6 files changed

+250
-0
lines changed

6 files changed

+250
-0
lines changed

cmd/jwt/app.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,8 @@ func verifyToken() error {
136136
return jwt.ParseECPublicKeyFromPEM(data)
137137
} else if isRs() {
138138
return jwt.ParseRSAPublicKeyFromPEM(data)
139+
} else if isEd() {
140+
return jwt.ParseEdPublicKeyFromPEM(data)
139141
}
140142
return data, nil
141143
})
@@ -229,6 +231,15 @@ func signToken() error {
229231
return err
230232
}
231233
}
234+
} else if isEd() {
235+
if k, ok := key.([]byte); !ok {
236+
return fmt.Errorf("Couldn't convert key data to key")
237+
} else {
238+
key, err = jwt.ParseEdPrivateKeyFromPEM(k)
239+
if err != nil {
240+
return err
241+
}
242+
}
232243
}
233244

234245
if out, err := token.SignedString(key); err == nil {
@@ -280,3 +291,7 @@ func isEs() bool {
280291
func isRs() bool {
281292
return strings.HasPrefix(*flagAlg, "RS") || strings.HasPrefix(*flagAlg, "PS")
282293
}
294+
295+
func isEd() bool {
296+
return strings.HasPrefix(strings.ToUpper(*flagAlg), "Ed")
297+
}

ed25519.go

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
package jwt
2+
3+
import (
4+
"errors"
5+
6+
"crypto/ed25519"
7+
)
8+
9+
var (
10+
ErrEd25519Verification = errors.New("ed25519: verification error")
11+
)
12+
13+
// Implements the EdDSA family
14+
// Expects ed25519.PrivateKey for signing and ed25519.PublicKey for verification
15+
type SigningMethodEd25519 struct{}
16+
17+
// Specific instance for EdDSA
18+
var (
19+
SigningMethodEdDSA *SigningMethodEd25519
20+
)
21+
22+
func init() {
23+
SigningMethodEdDSA = &SigningMethodEd25519{}
24+
RegisterSigningMethod(SigningMethodEdDSA.Alg(), func() SigningMethod {
25+
return SigningMethodEdDSA
26+
})
27+
}
28+
29+
func (m *SigningMethodEd25519) Alg() string {
30+
return "EdDSA"
31+
}
32+
33+
// Implements the Verify method from SigningMethod
34+
// For this verify method, key must be an ed25519.PublicKey
35+
func (m *SigningMethodEd25519) Verify(signingString, signature string, key interface{}) error {
36+
var err error
37+
var ed25519Key ed25519.PublicKey
38+
var ok bool
39+
40+
if ed25519Key, ok = key.(ed25519.PublicKey); !ok {
41+
return ErrInvalidKeyType
42+
}
43+
44+
if len(ed25519Key) != ed25519.PublicKeySize {
45+
return ErrInvalidKey
46+
}
47+
48+
// Decode the signature
49+
var sig []byte
50+
if sig, err = DecodeSegment(signature); err != nil {
51+
return err
52+
}
53+
54+
// Verify the signature
55+
if !ed25519.Verify(ed25519Key, []byte(signingString), sig) {
56+
return ErrEd25519Verification
57+
}
58+
59+
return nil
60+
}
61+
62+
// Implements the Sign method from SigningMethod
63+
// For this signing method, key must be an ed25519.PrivateKey
64+
func (m *SigningMethodEd25519) Sign(signingString string, key interface{}) (string, error) {
65+
var ed25519Key ed25519.PrivateKey
66+
var ok bool
67+
68+
if ed25519Key, ok = key.(ed25519.PrivateKey); !ok {
69+
return "", ErrInvalidKeyType
70+
}
71+
72+
// ed25519.Sign panics if private key not equal to ed25519.PrivateKeySize
73+
// this allows to avoid recover usage
74+
if len(ed25519Key) != ed25519.PrivateKeySize {
75+
return "", ErrInvalidKey
76+
}
77+
78+
// Sign the string and return the encoded result
79+
sig := ed25519.Sign(ed25519Key, []byte(signingString))
80+
return EncodeSegment(sig), nil
81+
}

ed25519_test.go

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
package jwt_test
2+
3+
import (
4+
"io/ioutil"
5+
"strings"
6+
"testing"
7+
8+
"github.com/golang-jwt/jwt"
9+
)
10+
11+
var ed25519TestData = []struct {
12+
name string
13+
keys map[string]string
14+
tokenString string
15+
alg string
16+
claims map[string]interface{}
17+
valid bool
18+
}{
19+
{
20+
"Basic Ed25519",
21+
map[string]string{"private": "test/ed25519-private.pem", "public": "test/ed25519-public.pem"},
22+
"eyJhbGciOiJFRDI1NTE5IiwidHlwIjoiSldUIn0.eyJmb28iOiJiYXIifQ.ESuVzZq1cECrt9Od_gLPVG-_6uRP_8Nq-ajx6CtmlDqRJZqdejro2ilkqaQgSL-siE_3JMTUW7UwAorLaTyFCw",
23+
"EdDSA",
24+
map[string]interface{}{"foo": "bar"},
25+
true,
26+
},
27+
{
28+
"Basic Ed25519",
29+
map[string]string{"private": "test/ed25519-private.pem", "public": "test/ed25519-public.pem"},
30+
"eyJhbGciOiJFRDI1NTE5IiwidHlwIjoiSldUIn0.eyJmb28iOiJiYXoifQ.ESuVzZq1cECrt9Od_gLPVG-_6uRP_8Nq-ajx6CtmlDqRJZqdejro2ilkqaQgSL-siE_3JMTUW7UwAorLaTyFCw",
31+
"EdDSA",
32+
map[string]interface{}{"foo": "bar"},
33+
false,
34+
},
35+
}
36+
37+
func TestEd25519Verify(t *testing.T) {
38+
for _, data := range ed25519TestData {
39+
var err error
40+
41+
key, _ := ioutil.ReadFile(data.keys["public"])
42+
43+
ed25519Key, err := jwt.ParseEdPublicKeyFromPEM(key)
44+
if err != nil {
45+
t.Errorf("Unable to parse Ed25519 public key: %v", err)
46+
}
47+
48+
parts := strings.Split(data.tokenString, ".")
49+
50+
method := jwt.GetSigningMethod(data.alg)
51+
52+
err = method.Verify(strings.Join(parts[0:2], "."), parts[2], ed25519Key)
53+
if data.valid && err != nil {
54+
t.Errorf("[%v] Error while verifying key: %v", data.name, err)
55+
}
56+
if !data.valid && err == nil {
57+
t.Errorf("[%v] Invalid key passed validation", data.name)
58+
}
59+
}
60+
}
61+
62+
func TestEd25519Sign(t *testing.T) {
63+
for _, data := range ed25519TestData {
64+
var err error
65+
key, _ := ioutil.ReadFile(data.keys["private"])
66+
67+
ed25519Key, err := jwt.ParseEdPrivateKeyFromPEM(key)
68+
if err != nil {
69+
t.Errorf("Unable to parse Ed25519 private key: %v", err)
70+
}
71+
72+
parts := strings.Split(data.tokenString, ".")
73+
74+
method := jwt.GetSigningMethod(data.alg)
75+
76+
sig, err := method.Sign(strings.Join(parts[0:2], "."), ed25519Key)
77+
if err != nil {
78+
t.Errorf("[%v] Error signing token: %v", data.name, err)
79+
}
80+
if sig == parts[2] && !data.valid {
81+
t.Errorf("[%v] Identical signatures\nbefore:\n%v\nafter:\n%v", data.name, parts[2], sig)
82+
}
83+
}
84+
}

ed25519_utils.go

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
package jwt
2+
3+
import (
4+
"crypto"
5+
"crypto/ed25519"
6+
"crypto/x509"
7+
"encoding/pem"
8+
"errors"
9+
)
10+
11+
var (
12+
ErrNotEdPrivateKey = errors.New("Key is not a valid Ed25519 private key")
13+
ErrNotEdPublicKey = errors.New("Key is not a valid Ed25519 public key")
14+
)
15+
16+
// Parse PEM-encoded Edwards curve private key
17+
func ParseEdPrivateKeyFromPEM(key []byte) (crypto.PrivateKey, error) {
18+
var err error
19+
20+
// Parse PEM block
21+
var block *pem.Block
22+
if block, _ = pem.Decode(key); block == nil {
23+
return nil, ErrKeyMustBePEMEncoded
24+
}
25+
26+
// Parse the key
27+
var parsedKey interface{}
28+
if parsedKey, err = x509.ParsePKCS8PrivateKey(block.Bytes); err != nil {
29+
return nil, err
30+
}
31+
32+
var pkey ed25519.PrivateKey
33+
var ok bool
34+
if pkey, ok = parsedKey.(ed25519.PrivateKey); !ok {
35+
return nil, ErrNotEdPrivateKey
36+
}
37+
38+
return pkey, nil
39+
}
40+
41+
// Parse PEM-encoded Edwards curve public key
42+
func ParseEdPublicKeyFromPEM(key []byte) (crypto.PublicKey, error) {
43+
var err error
44+
45+
// Parse PEM block
46+
var block *pem.Block
47+
if block, _ = pem.Decode(key); block == nil {
48+
return nil, ErrKeyMustBePEMEncoded
49+
}
50+
51+
// Parse the key
52+
var parsedKey interface{}
53+
if parsedKey, err = x509.ParsePKIXPublicKey(block.Bytes); err != nil {
54+
return nil, err
55+
}
56+
57+
var pkey ed25519.PublicKey
58+
var ok bool
59+
if pkey, ok = parsedKey.(ed25519.PublicKey); !ok {
60+
return nil, ErrNotEdPublicKey
61+
}
62+
63+
return pkey, nil
64+
}

test/ed25519-private.pem

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
-----BEGIN PRIVATE KEY-----
2+
MC4CAQAwBQYDK2VwBCIEIEFMEZrmlYxczXKFxIlNvNGR5JQvDhTkLovJYxwQd3ua
3+
-----END PRIVATE KEY-----

test/ed25519-public.pem

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
-----BEGIN PUBLIC KEY-----
2+
MCowBQYDK2VwAyEAWH7z6hpYqvPns2i4n9yymwvB3APhi4LyQ7iHOT6crtE=
3+
-----END PUBLIC KEY-----

0 commit comments

Comments
 (0)