Skip to content
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

add ed25519 support #36

Merged
merged 3 commits into from
Jul 29, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
15 changes: 15 additions & 0 deletions cmd/jwt/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,8 @@ func verifyToken() error {
return jwt.ParseECPublicKeyFromPEM(data)
} else if isRs() {
return jwt.ParseRSAPublicKeyFromPEM(data)
} else if isEd() {
return jwt.ParseEdPublicKeyFromPEM(data)
}
return data, nil
})
Expand Down Expand Up @@ -229,6 +231,15 @@ func signToken() error {
return err
}
}
} else if isEd() {
if k, ok := key.([]byte); !ok {
return fmt.Errorf("Couldn't convert key data to key")
} else {
key, err = jwt.ParseEdPrivateKeyFromPEM(k)
if err != nil {
return err
}
}
}

if out, err := token.SignedString(key); err == nil {
Expand Down Expand Up @@ -280,3 +291,7 @@ func isEs() bool {
func isRs() bool {
return strings.HasPrefix(*flagAlg, "RS") || strings.HasPrefix(*flagAlg, "PS")
}

func isEd() bool {
return strings.HasPrefix(strings.ToUpper(*flagAlg), "ED")
}
81 changes: 81 additions & 0 deletions ed25519.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package jwt

import (
"errors"

"golang.org/x/crypto/ed25519"
)

var (
ErrEd25519Verification = errors.New("ed25519: verification error")
)

// Implements the EdDSA family
// Expects *ed25519.PrivateKey for signing and *ed25519.PublicKey for verification
type SigningMethodEd25519 struct{}

// Specific instance for EdDSA
var (
SigningMethodEdDSA *SigningMethodEd25519
)

func init() {
SigningMethodEdDSA = &SigningMethodEd25519{}
RegisterSigningMethod(SigningMethodEdDSA.Alg(), func() SigningMethod {
return SigningMethodEdDSA
})
}

func (m *SigningMethodEd25519) Alg() string {
return "EdDSA"
}

// Implements the Verify method from SigningMethod
// For this verify method, key must be an Ed25519.PublicKey struct
func (m *SigningMethodEd25519) Verify(signingString, signature string, key interface{}) error {
var err error
var ed25519Key *ed25519.PublicKey
var ok bool

if ed25519Key, ok = key.(*ed25519.PublicKey); !ok {
return ErrInvalidKeyType
}

if len(*ed25519Key) != ed25519.PublicKeySize {
return ErrInvalidKey
}

// Decode the signature
var sig []byte
if sig, err = DecodeSegment(signature); err != nil {
return err
}

// Verify the signature
if !ed25519.Verify(*ed25519Key, []byte(signingString), sig) {
return ErrEd25519Verification
}

return nil
}

// Implements the Sign method from SigningMethod
// For this signing method, key must be an Ed25519.PrivateKey struct
func (m *SigningMethodEd25519) Sign(signingString string, key interface{}) (string, error) {
var ed25519Key *ed25519.PrivateKey
var ok bool

if ed25519Key, ok = key.(*ed25519.PrivateKey); !ok {
return "", ErrInvalidKeyType
}

// ed25519.Sign panics if private key not equal to ed25519.PrivateKeySize
// this allows to avoid recover usage
if len(*ed25519Key) != ed25519.PrivateKeySize {
return "", ErrInvalidKey
}

// Sign the string and return the encoded result
sig := ed25519.Sign(*ed25519Key, []byte(signingString))
return EncodeSegment(sig), nil
}
84 changes: 84 additions & 0 deletions ed25519_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package jwt_test

import (
"io/ioutil"
"strings"
"testing"

"github.com/golang-jwt/jwt"
)

var ed25519TestData = []struct {
name string
keys map[string]string
tokenString string
alg string
claims map[string]interface{}
valid bool
}{
{
"Basic Ed25519",
map[string]string{"private": "test/ed25519-private.pem", "public": "test/ed25519-public.pem"},
"eyJhbGciOiJFRDI1NTE5IiwidHlwIjoiSldUIn0.eyJmb28iOiJiYXIifQ.ESuVzZq1cECrt9Od_gLPVG-_6uRP_8Nq-ajx6CtmlDqRJZqdejro2ilkqaQgSL-siE_3JMTUW7UwAorLaTyFCw",
"EdDSA",
map[string]interface{}{"foo": "bar"},
true,
},
{
"Basic Ed25519",
map[string]string{"private": "test/ed25519-private.pem", "public": "test/ed25519-public.pem"},
"eyJhbGciOiJFRDI1NTE5IiwidHlwIjoiSldUIn0.eyJmb28iOiJiYXoifQ.ESuVzZq1cECrt9Od_gLPVG-_6uRP_8Nq-ajx6CtmlDqRJZqdejro2ilkqaQgSL-siE_3JMTUW7UwAorLaTyFCw",
"EdDSA",
map[string]interface{}{"foo": "bar"},
false,
},
}

func TestED25519Verify(t *testing.T) {
for _, data := range ed25519TestData {
var err error

key, _ := ioutil.ReadFile(data.keys["public"])

ed25519Key, err := jwt.ParseEdPublicKeyFromPEM(key)
if err != nil {
t.Errorf("Unable to parse ED25519 public key: %v", err)
}

parts := strings.Split(data.tokenString, ".")

method := jwt.GetSigningMethod(data.alg)

err = method.Verify(strings.Join(parts[0:2], "."), parts[2], ed25519Key)
if data.valid && err != nil {
t.Errorf("[%v] Error while verifying key: %v", data.name, err)
}
if !data.valid && err == nil {
t.Errorf("[%v] Invalid key passed validation", data.name)
}
}
}

func TestED25519Sign(t *testing.T) {
for _, data := range ed25519TestData {
var err error
key, _ := ioutil.ReadFile(data.keys["private"])

ed25519Key, err := jwt.ParseEdPrivateKeyFromPEM(key)
if err != nil {
t.Errorf("Unable to parse ED25519 private key: %v", err)
}

parts := strings.Split(data.tokenString, ".")

method := jwt.GetSigningMethod(data.alg)

sig, err := method.Sign(strings.Join(parts[0:2], "."), ed25519Key)
if err != nil {
t.Errorf("[%v] Error signing token: %v", data.name, err)
}
if sig == parts[2] && !data.valid {
t.Errorf("[%v] Identical signatures\nbefore:\n%v\nafter:\n%v", data.name, parts[2], sig)
}
}
}
53 changes: 53 additions & 0 deletions ed25519_utils.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package jwt

import (
"crypto"
"encoding/asn1"
"encoding/pem"

"golang.org/x/crypto/ed25519"
)

type OI struct {
ObjectIdentifier asn1.ObjectIdentifier
}

type ed25519PrivKey struct {
Version int
ObjectIdentifier OI
PrivateKey []byte
}

type ed25519PubKey struct {
OBjectIdentifier OI
PublicKey asn1.BitString
}

// Parse PEM-encoded Edwards curve private key
func ParseEdPrivateKeyFromPEM(key []byte) (crypto.PrivateKey, error) {
var block *pem.Block
if block, _ = pem.Decode(key); block == nil {
return nil, ErrKeyMustBePEMEncoded
}
var asn1PrivKey ed25519PrivKey
if _, err := asn1.Unmarshal(block.Bytes, &asn1PrivKey); err != nil {
return nil, err
}
// we don't need the tag and length bytes
privKey := ed25519.NewKeyFromSeed(asn1PrivKey.PrivateKey[2:])
return &privKey, nil
}

// Parse PEM-encoded Edwards curve public key
func ParseEdPublicKeyFromPEM(key []byte) (crypto.PublicKey, error) {
var block *pem.Block
if block, _ = pem.Decode(key); block == nil {
return nil, ErrKeyMustBePEMEncoded
}
var asn1PubKey ed25519PubKey
if _, err := asn1.Unmarshal(block.Bytes, &asn1PubKey); err != nil {
return nil, err
}
pkey := ed25519.PublicKey(asn1PubKey.PublicKey.Bytes)
return &pkey, nil
}
3 changes: 3 additions & 0 deletions test/ed25519-private.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
-----BEGIN PRIVATE KEY-----
MC4CAQAwBQYDK2VwBCIEIEFMEZrmlYxczXKFxIlNvNGR5JQvDhTkLovJYxwQd3ua
-----END PRIVATE KEY-----
3 changes: 3 additions & 0 deletions test/ed25519-public.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
-----BEGIN PUBLIC KEY-----
MCowBQYDK2VwAyEAWH7z6hpYqvPns2i4n9yymwvB3APhi4LyQ7iHOT6crtE=
-----END PUBLIC KEY-----