From 28ac4a7a87fb290a61143d99e08bdae9a0284114 Mon Sep 17 00:00:00 2001 From: Aliaksandr Mianzhynski Date: Wed, 25 Aug 2021 23:50:38 +0300 Subject: [PATCH] Add EdDSA JWT signing algorithm (#16786) * Add EdDSA signing algorithm * Fix typo Co-authored-by: Lunny Xiao Co-authored-by: techknowlogick --- custom/conf/app.example.ini | 2 +- routers/web/user/oauth.go | 2 +- routers/web/user/oauth_test.go | 2 +- services/auth/source/oauth2/jwtsigningkey.go | 79 ++++++++++++++++++-- 4 files changed, 76 insertions(+), 9 deletions(-) diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini index d0fe6150d6..e262a38ee9 100644 --- a/custom/conf/app.example.ini +++ b/custom/conf/app.example.ini @@ -392,7 +392,7 @@ INTERNAL_TOKEN= ;; Enables OAuth2 provider ENABLE = true ;; -;; Algorithm used to sign OAuth2 tokens. Valid values: HS256, HS384, HS512, RS256, RS384, RS512, ES256, ES384, ES512 +;; Algorithm used to sign OAuth2 tokens. Valid values: HS256, HS384, HS512, RS256, RS384, RS512, ES256, ES384, ES512, EdDSA ;JWT_SIGNING_ALGORITHM = RS256 ;; ;; Private key file path used to sign OAuth2 tokens. The path is relative to APP_DATA_PATH. diff --git a/routers/web/user/oauth.go b/routers/web/user/oauth.go index 771bd90b15..e29826630a 100644 --- a/routers/web/user/oauth.go +++ b/routers/web/user/oauth.go @@ -546,7 +546,7 @@ func AccessTokenOAuth(ctx *context.Context) { signingKey := oauth2.DefaultSigningKey if signingKey.IsSymmetric() { - clientKey, err := oauth2.CreateJWTSingingKey(signingKey.SigningMethod().Alg(), []byte(form.ClientSecret)) + clientKey, err := oauth2.CreateJWTSigningKey(signingKey.SigningMethod().Alg(), []byte(form.ClientSecret)) if err != nil { handleAccessTokenError(ctx, AccessTokenError{ ErrorCode: AccessTokenErrorCodeInvalidRequest, diff --git a/routers/web/user/oauth_test.go b/routers/web/user/oauth_test.go index 0283431960..c2f9ec87b5 100644 --- a/routers/web/user/oauth_test.go +++ b/routers/web/user/oauth_test.go @@ -15,7 +15,7 @@ import ( ) func createAndParseToken(t *testing.T, grant *models.OAuth2Grant) *oauth2.OIDCToken { - signingKey, err := oauth2.CreateJWTSingingKey("HS256", make([]byte, 32)) + signingKey, err := oauth2.CreateJWTSigningKey("HS256", make([]byte, 32)) assert.NoError(t, err) assert.NotNil(t, signingKey) oauth2.DefaultSigningKey = signingKey diff --git a/services/auth/source/oauth2/jwtsigningkey.go b/services/auth/source/oauth2/jwtsigningkey.go index b8f1e40e8c..720a9a33f7 100644 --- a/services/auth/source/oauth2/jwtsigningkey.go +++ b/services/auth/source/oauth2/jwtsigningkey.go @@ -6,6 +6,7 @@ package oauth2 import ( "crypto/ecdsa" + "crypto/ed25519" "crypto/elliptic" "crypto/rand" "crypto/rsa" @@ -129,6 +130,57 @@ func (key rsaSingingKey) PreProcessToken(token *jwt.Token) { token.Header["kid"] = key.id } +type eddsaSigningKey struct { + signingMethod jwt.SigningMethod + key ed25519.PrivateKey + id string +} + +func newEdDSASingingKey(signingMethod jwt.SigningMethod, key ed25519.PrivateKey) (eddsaSigningKey, error) { + kid, err := createPublicKeyFingerprint(key.Public().(ed25519.PublicKey)) + if err != nil { + return eddsaSigningKey{}, err + } + + return eddsaSigningKey{ + signingMethod, + key, + base64.RawURLEncoding.EncodeToString(kid), + }, nil +} + +func (key eddsaSigningKey) IsSymmetric() bool { + return false +} + +func (key eddsaSigningKey) SigningMethod() jwt.SigningMethod { + return key.signingMethod +} + +func (key eddsaSigningKey) SignKey() interface{} { + return key.key +} + +func (key eddsaSigningKey) VerifyKey() interface{} { + return key.key.Public() +} + +func (key eddsaSigningKey) ToJWK() (map[string]string, error) { + pubKey := key.key.Public().(ed25519.PublicKey) + + return map[string]string{ + "alg": key.SigningMethod().Alg(), + "kid": key.id, + "kty": "OKP", + "crv": "Ed25519", + "x": base64.RawURLEncoding.EncodeToString(pubKey), + }, nil +} + +func (key eddsaSigningKey) PreProcessToken(token *jwt.Token) { + token.Header["kid"] = key.id +} + type ecdsaSingingKey struct { signingMethod jwt.SigningMethod key *ecdsa.PrivateKey @@ -194,8 +246,8 @@ func createPublicKeyFingerprint(key interface{}) ([]byte, error) { return checksum[:], nil } -// CreateJWTSingingKey creates a signing key from an algorithm / key pair. -func CreateJWTSingingKey(algorithm string, key interface{}) (JWTSigningKey, error) { +// CreateJWTSigningKey creates a signing key from an algorithm / key pair. +func CreateJWTSigningKey(algorithm string, key interface{}) (JWTSigningKey, error) { var signingMethod jwt.SigningMethod switch algorithm { case "HS256": @@ -218,11 +270,19 @@ func CreateJWTSingingKey(algorithm string, key interface{}) (JWTSigningKey, erro signingMethod = jwt.SigningMethodES384 case "ES512": signingMethod = jwt.SigningMethodES512 + case "EdDSA": + signingMethod = jwt.SigningMethodEdDSA default: return nil, ErrInvalidAlgorithmType{algorithm} } switch signingMethod.(type) { + case *jwt.SigningMethodEd25519: + privateKey, ok := key.(ed25519.PrivateKey) + if !ok { + return nil, jwt.ErrInvalidKeyType + } + return newEdDSASingingKey(signingMethod, privateKey) case *jwt.SigningMethodECDSA: privateKey, ok := key.(*ecdsa.PrivateKey) if !ok { @@ -271,6 +331,8 @@ func InitSigningKey() error { case "ES384": fallthrough case "ES512": + fallthrough + case "EdDSA": key, err = loadOrCreateAsymmetricKey() default: @@ -278,10 +340,10 @@ func InitSigningKey() error { } if err != nil { - return fmt.Errorf("Error while loading or creating symmetric key: %v", err) + return fmt.Errorf("Error while loading or creating JWT key: %v", err) } - signingKey, err := CreateJWTSingingKey(setting.OAuth2.JWTSigningAlgorithm, key) + signingKey, err := CreateJWTSigningKey(setting.OAuth2.JWTSigningAlgorithm, key) if err != nil { return err } @@ -324,10 +386,15 @@ func loadOrCreateAsymmetricKey() (interface{}, error) { if !isExist { err := func() error { key, err := func() (interface{}, error) { - if strings.HasPrefix(setting.OAuth2.JWTSigningAlgorithm, "RS") { + switch { + case strings.HasPrefix(setting.OAuth2.JWTSigningAlgorithm, "RS"): return rsa.GenerateKey(rand.Reader, 4096) + case setting.OAuth2.JWTSigningAlgorithm == "EdDSA": + _, pk, err := ed25519.GenerateKey(rand.Reader) + return pk, err + default: + return ecdsa.GenerateKey(elliptic.P256(), rand.Reader) } - return ecdsa.GenerateKey(elliptic.P256(), rand.Reader) }() if err != nil { return err