diff --git a/server/auth/jwt.go b/server/auth/jwt.go index c0ef59f64..09d7f319f 100644 --- a/server/auth/jwt.go +++ b/server/auth/jwt.go @@ -17,6 +17,7 @@ package auth import ( "context" "crypto/ecdsa" + "crypto/ed25519" "crypto/rsa" "errors" "time" @@ -54,6 +55,8 @@ func (t *tokenJWT) info(ctx context.Context, token string, rev uint64) (*AuthInf return &k.PublicKey, nil case *ecdsa.PrivateKey: return &k.PublicKey, nil + case ed25519.PrivateKey: + return k.Public(), nil default: return t.key, nil } @@ -161,6 +164,10 @@ func newTokenProviderJWT(lg *zap.Logger, optMap map[string]string) (*tokenJWT, e if _, ok := t.key.(*ecdsa.PublicKey); ok { t.verifyOnly = true } + case *jwt.SigningMethodEd25519: + if _, ok := t.key.(ed25519.PublicKey); ok { + t.verifyOnly = true + } case *jwt.SigningMethodRSA, *jwt.SigningMethodRSAPSS: if _, ok := t.key.(*rsa.PublicKey); ok { t.verifyOnly = true diff --git a/server/auth/jwt_test.go b/server/auth/jwt_test.go index e7c4840b4..27369092d 100644 --- a/server/auth/jwt_test.go +++ b/server/auth/jwt_test.go @@ -31,6 +31,9 @@ const ( jwtECPubKey = "../../tests/fixtures/server-ecdsa.crt" jwtECPrivKey = "../../tests/fixtures/server-ecdsa.key.insecure" + + jwtEdPubKey = "../../tests/fixtures/ed25519-public-key.pem" + jwtEdPrivKey = "../../tests/fixtures/ed25519-private-key.pem" ) func TestJWTInfo(t *testing.T) { @@ -63,6 +66,15 @@ func TestJWTInfo(t *testing.T) { "priv-key": jwtECPrivKey, "sign-method": "ES256", }, + "Ed25519-priv": { + "priv-key": jwtEdPrivKey, + "sign-method": "EdDSA", + }, + "Ed25519": { + "pub-key": jwtEdPubKey, + "priv-key": jwtEdPrivKey, + "sign-method": "EdDSA", + }, "HMAC": { "priv-key": jwtECPrivKey, // any file, raw bytes used as shared secret "sign-method": "HS256", diff --git a/server/auth/options.go b/server/auth/options.go index 7bc635b0f..01b78316f 100644 --- a/server/auth/options.go +++ b/server/auth/options.go @@ -15,7 +15,9 @@ package auth import ( + "crypto" "crypto/ecdsa" + "crypto/ed25519" "crypto/rsa" "fmt" "os" @@ -100,6 +102,8 @@ func (opts *jwtOptions) Key() (interface{}, error) { return opts.rsaKey() case *jwt.SigningMethodECDSA: return opts.ecKey() + case *jwt.SigningMethodEd25519: + return opts.edKey() case *jwt.SigningMethodHMAC: return opts.hmacKey() default: @@ -189,3 +193,45 @@ func (opts *jwtOptions) ecKey() (interface{}, error) { return priv, nil } + +func (opts *jwtOptions) edKey() (interface{}, error) { + var ( + priv ed25519.PrivateKey + pub ed25519.PublicKey + err error + ) + + if len(opts.PrivateKey) > 0 { + var privKey crypto.PrivateKey + privKey, err = jwt.ParseEdPrivateKeyFromPEM(opts.PrivateKey) + if err != nil { + return nil, err + } + priv = privKey.(ed25519.PrivateKey) + } + + if len(opts.PublicKey) > 0 { + var pubKey crypto.PublicKey + pubKey, err = jwt.ParseEdPublicKeyFromPEM(opts.PublicKey) + if err != nil { + return nil, err + } + pub = pubKey.(ed25519.PublicKey) + } + + if priv == nil { + if pub == nil { + // Neither key given + return nil, ErrMissingKey + } + // Public key only, can verify tokens + return pub, nil + } + + // both keys provided, make sure they match + if pub != nil && !pub.Equal(priv.Public()) { + return nil, ErrKeyMismatch + } + + return priv, nil +} diff --git a/tests/fixtures/ed25519-private-key.pem b/tests/fixtures/ed25519-private-key.pem new file mode 100644 index 000000000..1596cd455 --- /dev/null +++ b/tests/fixtures/ed25519-private-key.pem @@ -0,0 +1,3 @@ +-----BEGIN PRIVATE KEY----- +MC4CAQAwBQYDK2VwBCIEIAtiwQ7KeS1I0otY9gw1Ox4av/zQ+wvs/8AIaTkawQ73 +-----END PRIVATE KEY----- diff --git a/tests/fixtures/ed25519-public-key.pem b/tests/fixtures/ed25519-public-key.pem new file mode 100644 index 000000000..5563956f2 --- /dev/null +++ b/tests/fixtures/ed25519-public-key.pem @@ -0,0 +1,3 @@ +-----BEGIN PUBLIC KEY----- +MCowBQYDK2VwAyEAuOUxC8Bbn1KqYctlim/MHaP5JrtmeK5xcs+9w506btA= +-----END PUBLIC KEY-----