451 lines
12 KiB
Go
451 lines
12 KiB
Go
package oidc
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"testing"
|
|
|
|
fjwt "github.com/ory/fosite/token/jwt"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"gopkg.in/square/go-jose.v2"
|
|
|
|
"github.com/authelia/authelia/v4/internal/configuration/schema"
|
|
)
|
|
|
|
func TestKeyManager(t *testing.T) {
|
|
config := &schema.OpenIDConnectConfiguration{
|
|
Discovery: schema.OpenIDConnectDiscovery{
|
|
DefaultKeyID: "kid-RS256-sig",
|
|
},
|
|
IssuerJWKS: []schema.JWK{
|
|
{
|
|
Use: KeyUseSignature,
|
|
Algorithm: SigningAlgRSAUsingSHA256,
|
|
Key: keyRSA2048,
|
|
CertificateChain: certRSA2048,
|
|
},
|
|
{
|
|
Use: KeyUseSignature,
|
|
Algorithm: SigningAlgRSAUsingSHA384,
|
|
Key: keyRSA2048,
|
|
CertificateChain: certRSA2048,
|
|
},
|
|
{
|
|
Use: KeyUseSignature,
|
|
Algorithm: SigningAlgRSAUsingSHA512,
|
|
Key: keyRSA4096,
|
|
CertificateChain: certRSA4096,
|
|
},
|
|
{
|
|
Use: KeyUseSignature,
|
|
Algorithm: SigningAlgRSAPSSUsingSHA256,
|
|
Key: keyRSA2048,
|
|
CertificateChain: certRSA2048,
|
|
},
|
|
{
|
|
Use: KeyUseSignature,
|
|
Algorithm: SigningAlgRSAPSSUsingSHA384,
|
|
Key: keyRSA2048,
|
|
CertificateChain: certRSA2048,
|
|
},
|
|
{
|
|
Use: KeyUseSignature,
|
|
Algorithm: SigningAlgRSAPSSUsingSHA512,
|
|
Key: keyRSA4096,
|
|
CertificateChain: certRSA4096,
|
|
},
|
|
{
|
|
Use: KeyUseSignature,
|
|
Algorithm: SigningAlgECDSAUsingP256AndSHA256,
|
|
Key: keyECDSAP256,
|
|
CertificateChain: certECDSAP256,
|
|
},
|
|
{
|
|
Use: KeyUseSignature,
|
|
Algorithm: SigningAlgECDSAUsingP384AndSHA384,
|
|
Key: keyECDSAP384,
|
|
CertificateChain: certECDSAP384,
|
|
},
|
|
{
|
|
Use: KeyUseSignature,
|
|
Algorithm: SigningAlgECDSAUsingP521AndSHA512,
|
|
Key: keyECDSAP521,
|
|
CertificateChain: certECDSAP521,
|
|
},
|
|
},
|
|
}
|
|
|
|
for i, key := range config.IssuerJWKS {
|
|
config.IssuerJWKS[i].KeyID = fmt.Sprintf("kid-%s-%s", key.Algorithm, key.Use)
|
|
}
|
|
|
|
manager := NewKeyManager(config)
|
|
|
|
assert.NotNil(t, manager)
|
|
|
|
assert.Len(t, manager.kids, len(config.IssuerJWKS))
|
|
assert.Len(t, manager.algs, len(config.IssuerJWKS))
|
|
|
|
assert.Equal(t, "kid-RS256-sig", manager.kid)
|
|
|
|
ctx := context.Background()
|
|
|
|
var (
|
|
jwk *JWK
|
|
err error
|
|
)
|
|
|
|
jwk = manager.GetByAlg(ctx, "notalg")
|
|
assert.Nil(t, jwk)
|
|
|
|
jwk = manager.GetByKID(ctx, "notalg")
|
|
assert.Nil(t, jwk)
|
|
|
|
jwk = manager.GetByKID(ctx, "")
|
|
assert.NotNil(t, jwk)
|
|
assert.Equal(t, config.Discovery.DefaultKeyID, jwk.KeyID())
|
|
|
|
jwk, err = manager.GetByHeader(ctx, &fjwt.Headers{Extra: map[string]any{JWTHeaderKeyIdentifier: "notalg"}})
|
|
assert.EqualError(t, err, "jwt header 'kid' with value 'notalg' does not match a managed jwk")
|
|
assert.Nil(t, jwk)
|
|
|
|
jwk, err = manager.GetByHeader(ctx, &fjwt.Headers{Extra: map[string]any{}})
|
|
assert.EqualError(t, err, "jwt header did not have a kid")
|
|
assert.Nil(t, jwk)
|
|
|
|
jwk, err = manager.GetByHeader(ctx, nil)
|
|
assert.EqualError(t, err, "jwt header was nil")
|
|
assert.Nil(t, jwk)
|
|
|
|
kid, err := manager.GetKIDFromAlgStrict(ctx, "notalg")
|
|
assert.EqualError(t, err, "alg not found")
|
|
assert.Equal(t, "", kid)
|
|
|
|
kid = manager.GetKIDFromAlg(ctx, "notalg")
|
|
assert.Equal(t, config.Discovery.DefaultKeyID, kid)
|
|
|
|
set := manager.Set(ctx)
|
|
|
|
assert.NotNil(t, set)
|
|
assert.Len(t, set.Keys, len(config.IssuerJWKS))
|
|
|
|
data, err := json.Marshal(&set)
|
|
assert.NoError(t, err)
|
|
assert.NotNil(t, data)
|
|
|
|
out := jose.JSONWebKeySet{}
|
|
assert.NoError(t, json.Unmarshal(data, &out))
|
|
assert.Equal(t, *set, out)
|
|
|
|
for _, alg := range []string{SigningAlgRSAUsingSHA256, SigningAlgRSAUsingSHA384, SigningAlgRSAPSSUsingSHA512, SigningAlgRSAPSSUsingSHA256, SigningAlgRSAPSSUsingSHA384, SigningAlgRSAPSSUsingSHA512, SigningAlgECDSAUsingP256AndSHA256, SigningAlgECDSAUsingP384AndSHA384, SigningAlgECDSAUsingP521AndSHA512} {
|
|
t.Run(alg, func(t *testing.T) {
|
|
expectedKID := fmt.Sprintf("kid-%s-%s", alg, KeyUseSignature)
|
|
|
|
t.Run("ShouldGetCorrectKey", func(t *testing.T) {
|
|
jwk = manager.GetByKID(ctx, expectedKID)
|
|
assert.NotNil(t, jwk)
|
|
assert.Equal(t, expectedKID, jwk.KeyID())
|
|
|
|
jwk = manager.GetByAlg(ctx, alg)
|
|
assert.NotNil(t, jwk)
|
|
|
|
assert.Equal(t, alg, jwk.alg.Alg())
|
|
assert.Equal(t, expectedKID, jwk.KeyID())
|
|
|
|
kid, err = manager.GetKIDFromAlgStrict(ctx, alg)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, expectedKID, kid)
|
|
|
|
kid = manager.GetKIDFromAlg(ctx, alg)
|
|
assert.Equal(t, expectedKID, kid)
|
|
|
|
jwk, err = manager.GetByHeader(ctx, &fjwt.Headers{Extra: map[string]any{JWTHeaderKeyIdentifier: expectedKID}})
|
|
assert.NoError(t, err)
|
|
assert.NotNil(t, jwk)
|
|
|
|
assert.Equal(t, expectedKID, jwk.KeyID())
|
|
})
|
|
|
|
t.Run("ShouldUseCorrectSigner", func(t *testing.T) {
|
|
var tokenString, sig, sigb string
|
|
var token *fjwt.Token
|
|
|
|
tokenString, sig, err = manager.Generate(ctx, fjwt.MapClaims{}, &fjwt.Headers{Extra: map[string]any{JWTHeaderKeyIdentifier: expectedKID}})
|
|
assert.NoError(t, err)
|
|
|
|
sigb, err = manager.GetSignature(ctx, tokenString)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, sig, sigb)
|
|
|
|
sigb, err = manager.Validate(ctx, tokenString)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, sig, sigb)
|
|
|
|
token, err = manager.Decode(ctx, tokenString)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, expectedKID, token.Header[JWTHeaderKeyIdentifier])
|
|
|
|
jwk, err = manager.GetByTokenString(ctx, tokenString)
|
|
|
|
assert.NoError(t, err)
|
|
|
|
sigb, err = jwk.Strategy().Validate(ctx, tokenString)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, sig, sigb)
|
|
})
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestJWKFunctionality(t *testing.T) {
|
|
testCases := []struct {
|
|
have schema.JWK
|
|
}{
|
|
{
|
|
schema.JWK{
|
|
KeyID: "rsa2048-rs256",
|
|
Use: KeyUseSignature,
|
|
Algorithm: SigningAlgRSAUsingSHA256,
|
|
Key: keyRSA2048,
|
|
CertificateChain: certRSA2048,
|
|
},
|
|
},
|
|
{
|
|
schema.JWK{
|
|
KeyID: "rsa2048-rs384",
|
|
Use: KeyUseSignature,
|
|
Algorithm: SigningAlgRSAUsingSHA384,
|
|
Key: keyRSA2048,
|
|
CertificateChain: certRSA2048,
|
|
},
|
|
},
|
|
{
|
|
schema.JWK{
|
|
KeyID: "rsa2048-rs512",
|
|
Use: KeyUseSignature,
|
|
Algorithm: SigningAlgRSAUsingSHA512,
|
|
Key: keyRSA2048,
|
|
CertificateChain: certRSA2048,
|
|
},
|
|
},
|
|
{
|
|
schema.JWK{
|
|
KeyID: "rsa4096-rs256",
|
|
Use: KeyUseSignature,
|
|
Algorithm: SigningAlgRSAUsingSHA256,
|
|
Key: keyRSA4096,
|
|
CertificateChain: certRSA4096,
|
|
},
|
|
},
|
|
{
|
|
schema.JWK{
|
|
KeyID: "rsa4096-rs384",
|
|
Use: KeyUseSignature,
|
|
Algorithm: SigningAlgRSAUsingSHA384,
|
|
Key: keyRSA4096,
|
|
CertificateChain: certRSA4096,
|
|
},
|
|
},
|
|
{
|
|
schema.JWK{
|
|
KeyID: "rsa4096-rs512",
|
|
Use: KeyUseSignature,
|
|
Algorithm: SigningAlgRSAUsingSHA512,
|
|
Key: keyRSA4096,
|
|
CertificateChain: certRSA4096,
|
|
},
|
|
},
|
|
{
|
|
schema.JWK{
|
|
KeyID: "rsa2048-rs256",
|
|
Use: KeyUseSignature,
|
|
Algorithm: SigningAlgRSAPSSUsingSHA256,
|
|
Key: keyRSA2048,
|
|
CertificateChain: certRSA2048,
|
|
},
|
|
},
|
|
{
|
|
schema.JWK{
|
|
KeyID: "rsa2048-ps384",
|
|
Use: KeyUseSignature,
|
|
Algorithm: SigningAlgRSAPSSUsingSHA384,
|
|
Key: keyRSA2048,
|
|
CertificateChain: certRSA2048,
|
|
},
|
|
},
|
|
{
|
|
schema.JWK{
|
|
KeyID: "rsa2048-ps512",
|
|
Use: KeyUseSignature,
|
|
Algorithm: SigningAlgRSAPSSUsingSHA512,
|
|
Key: keyRSA2048,
|
|
CertificateChain: certRSA2048,
|
|
},
|
|
},
|
|
{
|
|
schema.JWK{
|
|
KeyID: "rsa4096-ps256",
|
|
Use: KeyUseSignature,
|
|
Algorithm: SigningAlgRSAPSSUsingSHA256,
|
|
Key: keyRSA4096,
|
|
CertificateChain: certRSA4096,
|
|
},
|
|
},
|
|
{
|
|
schema.JWK{
|
|
KeyID: "rsa4096-ps384",
|
|
Use: KeyUseSignature,
|
|
Algorithm: SigningAlgRSAPSSUsingSHA384,
|
|
Key: keyRSA4096,
|
|
CertificateChain: certRSA4096,
|
|
},
|
|
},
|
|
{
|
|
schema.JWK{
|
|
KeyID: "rsa4096-ps512",
|
|
Use: KeyUseSignature,
|
|
Algorithm: SigningAlgRSAPSSUsingSHA512,
|
|
Key: keyRSA4096,
|
|
CertificateChain: certRSA4096,
|
|
},
|
|
},
|
|
{
|
|
schema.JWK{
|
|
KeyID: "ecdsaP256",
|
|
Use: KeyUseSignature,
|
|
Algorithm: SigningAlgECDSAUsingP256AndSHA256,
|
|
Key: keyECDSAP256,
|
|
CertificateChain: certECDSAP256,
|
|
},
|
|
},
|
|
{
|
|
schema.JWK{
|
|
KeyID: "ecdsaP384",
|
|
Use: KeyUseSignature,
|
|
Algorithm: SigningAlgECDSAUsingP384AndSHA384,
|
|
Key: keyECDSAP384,
|
|
CertificateChain: certECDSAP384,
|
|
},
|
|
},
|
|
{
|
|
schema.JWK{
|
|
KeyID: "ecdsaP521",
|
|
Use: KeyUseSignature,
|
|
Algorithm: SigningAlgECDSAUsingP521AndSHA512,
|
|
Key: keyECDSAP521,
|
|
CertificateChain: certECDSAP521,
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.have.KeyID, func(t *testing.T) {
|
|
t.Run("Generating", func(t *testing.T) {
|
|
var (
|
|
jwk *JWK
|
|
)
|
|
|
|
ctx := context.Background()
|
|
|
|
jwk = NewJWK(tc.have)
|
|
|
|
signer := jwk.Strategy()
|
|
|
|
claims := fjwt.MapClaims{}
|
|
header := &fjwt.Headers{
|
|
Extra: map[string]any{
|
|
"kid": jwk.kid,
|
|
},
|
|
}
|
|
|
|
tokenString, sig, err := signer.Generate(ctx, claims, header)
|
|
|
|
assert.NoError(t, err)
|
|
assert.NotEqual(t, "", tokenString)
|
|
assert.NotEqual(t, "", sig)
|
|
|
|
sigd, err := signer.GetSignature(ctx, tokenString)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, sig, sigd)
|
|
|
|
token, err := signer.Decode(ctx, tokenString)
|
|
assert.NoError(t, err)
|
|
assert.NotNil(t, token)
|
|
fmt.Println(tokenString)
|
|
|
|
assert.True(t, token.Valid())
|
|
assert.Equal(t, jwk.alg.Alg(), string(token.Method))
|
|
|
|
sigv, err := signer.Validate(ctx, tokenString)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, sig, sigv)
|
|
})
|
|
|
|
t.Run("Marshalling", func(t *testing.T) {
|
|
var (
|
|
jwk *JWK
|
|
out jose.JSONWebKey
|
|
data []byte
|
|
err error
|
|
)
|
|
|
|
jwk = NewJWK(tc.have)
|
|
|
|
strategy := jwk.Strategy()
|
|
|
|
assert.NotNil(t, strategy)
|
|
|
|
signer, ok := strategy.(*Signer)
|
|
|
|
require.True(t, ok)
|
|
|
|
assert.NotNil(t, signer)
|
|
|
|
key, err := signer.GetPublicKey(context.Background())
|
|
assert.NoError(t, err)
|
|
assert.NotNil(t, key)
|
|
|
|
key, err = jwk.GetPrivateKey(context.Background())
|
|
assert.NoError(t, err)
|
|
assert.NotNil(t, key)
|
|
|
|
data, err = json.Marshal(jwk.JWK())
|
|
|
|
assert.NoError(t, err)
|
|
require.NotNil(t, data)
|
|
|
|
assert.NoError(t, json.Unmarshal(data, &out))
|
|
|
|
assert.True(t, out.IsPublic())
|
|
assert.Equal(t, tc.have.KeyID, out.KeyID)
|
|
assert.Equal(t, tc.have.KeyID, jwk.KeyID())
|
|
assert.Equal(t, tc.have.Use, out.Use)
|
|
assert.Equal(t, tc.have.Algorithm, out.Algorithm)
|
|
assert.NotNil(t, out.Key)
|
|
assert.NotNil(t, out.Certificates)
|
|
assert.NotNil(t, out.CertificateThumbprintSHA1)
|
|
assert.NotNil(t, out.CertificateThumbprintSHA256)
|
|
assert.True(t, out.Valid())
|
|
|
|
data, err = json.Marshal(jwk.PrivateJWK())
|
|
|
|
assert.NoError(t, err)
|
|
require.NotNil(t, data)
|
|
assert.NoError(t, json.Unmarshal(data, &out))
|
|
|
|
assert.False(t, out.IsPublic())
|
|
assert.Equal(t, tc.have.KeyID, out.KeyID)
|
|
assert.Equal(t, tc.have.Use, out.Use)
|
|
assert.Equal(t, tc.have.Algorithm, out.Algorithm)
|
|
assert.NotNil(t, out.Key)
|
|
assert.NotNil(t, out.Certificates)
|
|
assert.NotNil(t, out.CertificateThumbprintSHA1)
|
|
assert.NotNil(t, out.CertificateThumbprintSHA256)
|
|
assert.True(t, out.Valid())
|
|
})
|
|
})
|
|
}
|
|
}
|