Repository: coreos/go-oidc
Pull request #378 (2023-05-15 22:45:12)

Add EdDSA algorithm support
Allows for providers to use EdDSA algorithm but does not support verifying access tokens.

lritter14: > Out of curiosity, what providers support this?
> 
> Would it be possible to add some tests that actually exercise the signing and verification? E.g. take a look at jwks_test.go verify_test.go
> 
> If not, I can take a crack at it

Here is an example provider that supports EdDSA. I was not originally planning on adding verification functionality as it is more complicated for ed25519 protocol.
lritter14: > Did you mean to hyperlink something in that comment?
> 
> I don't think I'd want to merge this if it only implements partial support. I can add verification functionality when I get a sec.


lritter14: I added some additional tests and took your suggestion for using sha512.New() to verify the hash. I am not super knowledgeable in this space so I am not confident in that change.

P.S. closing the PR was a mistake
ericchiang: If there are any issues with the at_hash support, we can always fix it up later. I'm not super worried about it.

Patch:
diff --git a/oidc/jose.go b/oidc/jose.go
index 8afa895..b7bd092 100644
--- a/oidc/jose.go
+++ b/oidc/jose.go
@@ -13,4 +13,5 @@ const (
 	PS256 = "PS256" // RSASSA-PSS using SHA256 and MGF1-SHA256
 	PS384 = "PS384" // RSASSA-PSS using SHA384 and MGF1-SHA384
 	PS512 = "PS512" // RSASSA-PSS using SHA512 and MGF1-SHA512
+	EdDSA = "EdDSA" // Ed25519 using SHA-512
 )
diff --git a/oidc/jwks.go b/oidc/jwks.go
index 524f4e6..539933b 100644
--- a/oidc/jwks.go
+++ b/oidc/jwks.go
@@ -4,6 +4,7 @@ import (
 	"context"
 	"crypto"
 	"crypto/ecdsa"
+	"crypto/ed25519"
 	"crypto/rsa"
 	"errors"
 	"fmt"
@@ -32,6 +33,7 @@ func (s *StaticKeySet) VerifySignature(ctx context.Context, jwt string) ([]byte,
 		switch pub.(type) {
 		case *rsa.PublicKey:
 		case *ecdsa.PublicKey:
+		case ed25519.PublicKey:
 		default:
 			return nil, fmt.Errorf("invalid public key type provided: %T", pub)
 		}
diff --git a/oidc/oidc.go b/oidc/oidc.go
index 3026c77..b159d1c 100644
--- a/oidc/oidc.go
+++ b/oidc/oidc.go
@@ -149,6 +149,7 @@ var supportedAlgorithms = map[string]bool{
 	PS256: true,
 	PS384: true,
 	PS512: true,
+	EdDSA: true,
 }
 
 // ProviderConfig allows creating providers when discovery isn't supported. It's
@@ -448,7 +449,7 @@ func (i *IDToken) VerifyAccessToken(accessToken string) error {
 		h = sha256.New()
 	case RS384, ES384, PS384:
 		h = sha512.New384()
-	case RS512, ES512, PS512:
+	case RS512, ES512, PS512, EdDSA:
 		h = sha512.New()
 	default:
 		return fmt.Errorf("oidc: unsupported signing algorithm %q", i.sigAlgorithm)


Test patch:
diff --git a/oidc/jwks_test.go b/oidc/jwks_test.go
index 5bdbdc8..704bdc3 100644
--- a/oidc/jwks_test.go
+++ b/oidc/jwks_test.go
@@ -4,6 +4,7 @@ import (
 	"bytes"
 	"context"
 	"crypto/ecdsa"
+	"crypto/ed25519"
 	"crypto/elliptic"
 	"crypto/rand"
 	"crypto/rsa"
@@ -79,6 +80,14 @@ func newECDSAKey(t *testing.T) *signingKey {
 	return &signingKey{"", priv, priv.Public(), jose.ES256}
 }
 
+func newEdDSAKey(t *testing.T) *signingKey {
+	publicKey, privateKey, err := ed25519.GenerateKey(rand.Reader)
+	if err != nil {
+		t.Fatal(err)
+	}
+	return &signingKey{"", privateKey, publicKey, jose.EdDSA}
+}
+
 func TestRSAVerify(t *testing.T) {
 	good := newRSAKey(t)
 	bad := newRSAKey(t)
@@ -92,6 +101,12 @@ func TestECDSAVerify(t *testing.T) {
 	testKeyVerify(t, good, bad, good)
 }
 
+func TestEdDSAVerify(t *testing.T) {
+	good := newEdDSAKey(t)
+	bad := newEdDSAKey(t)
+	testKeyVerify(t, good, bad, good)
+}
+
 func TestMultipleKeysVerify(t *testing.T) {
 	key1 := newRSAKey(t)
 	key2 := newRSAKey(t)
diff --git a/oidc/oidc_test.go b/oidc/oidc_test.go
index 342cf07..8a813e3 100644
--- a/oidc/oidc_test.go
+++ b/oidc/oidc_test.go
@@ -90,6 +90,12 @@ func TestAccessTokenVerification(t *testing.T) {
 			googleAccessToken,
 			assertMsg("id token did not have an access token hash"),
 		},
+		{
+			"EdDSA",
+			newToken("EdDSA", computed512TokenHash),
+			googleAccessToken,
+			assertNil,
+		},
 		{
 			"badSignAlgo",
 			newToken("none", "xxx"),
@@ -135,11 +141,11 @@ func TestNewProvider(t *testing.T) {
 				"authorization_endpoint": "https://example.com/auth",
 				"token_endpoint": "https://example.com/token",
 				"jwks_uri": "https://example.com/keys",
-				"id_token_signing_alg_values_supported": ["RS256", "RS384", "ES256"]
+				"id_token_signing_alg_values_supported": ["RS256", "RS384", "ES256", "EdDSA"]
 			}`,
 			wantAuthURL:    "https://example.com/auth",
 			wantTokenURL:   "https://example.com/token",
-			wantAlgorithms: []string{"RS256", "RS384", "ES256"},
+			wantAlgorithms: []string{"RS256", "RS384", "ES256", "EdDSA"},
 		},
 		{
 			name: "unsupported_algorithms",
diff --git a/oidc/verify_test.go b/oidc/verify_test.go
index 3a84b91..f2e2433 100644
--- a/oidc/verify_test.go
+++ b/oidc/verify_test.go
@@ -25,6 +25,16 @@ func TestVerify(t *testing.T) {
 			},
 			signKey: newRSAKey(t),
 		},
+		{
+			name:    "good eddsa token",
+			idToken: `{"iss":"https://foo"}`,
+			config: Config{
+				SkipClientIDCheck:    true,
+				SkipExpiryCheck:      true,
+				SupportedSigningAlgs: []string{EdDSA},
+			},
+			signKey: newEdDSAKey(t),
+		},
 		{
 			name:    "invalid issuer",
 			issuer:  "https://bar",
@@ -214,6 +224,16 @@ func TestVerifySigningAlg(t *testing.T) {
 			},
 			signKey: newECDSAKey(t),
 		},
+		{
+			name:    "eddsa signing",
+			idToken: `{"iss":"https://foo"}`,
+			config: Config{
+				SkipClientIDCheck:    true,
+				SkipExpiryCheck:      true,
+				SupportedSigningAlgs: []string{EdDSA},
+			},
+			signKey: newEdDSAKey(t),
+		},
 		{
 			name:    "one of many supported",
 			idToken: `{"iss":"https://foo"}`,