authelia/internal/oidc/keys_blackbox_test.go

494 lines
14 KiB
Go
Raw Permalink Normal View History

package oidc_test
import (
"context"
"crypto"
"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"
"github.com/authelia/authelia/v4/internal/oidc"
)
func TestKeyManager(t *testing.T) {
config := &schema.OpenIDConnect{
IssuerPrivateKeys: []schema.JWK{
{
Use: oidc.KeyUseSignature,
Algorithm: oidc.SigningAlgRSAUsingSHA256,
Key: keyRSA2048,
CertificateChain: certRSA2048,
},
{
Use: oidc.KeyUseSignature,
Algorithm: oidc.SigningAlgRSAUsingSHA384,
Key: keyRSA2048,
CertificateChain: certRSA2048,
},
{
Use: oidc.KeyUseSignature,
Algorithm: oidc.SigningAlgRSAUsingSHA512,
Key: keyRSA4096,
CertificateChain: certRSA4096,
},
{
Use: oidc.KeyUseSignature,
Algorithm: oidc.SigningAlgRSAPSSUsingSHA256,
Key: keyRSA2048,
CertificateChain: certRSA2048,
},
{
Use: oidc.KeyUseSignature,
Algorithm: oidc.SigningAlgRSAPSSUsingSHA384,
Key: keyRSA2048,
CertificateChain: certRSA2048,
},
{
Use: oidc.KeyUseSignature,
Algorithm: oidc.SigningAlgRSAPSSUsingSHA512,
Key: keyRSA4096,
CertificateChain: certRSA4096,
},
{
Use: oidc.KeyUseSignature,
Algorithm: oidc.SigningAlgECDSAUsingP256AndSHA256,
Key: keyECDSAP256,
CertificateChain: certECDSAP256,
},
{
Use: oidc.KeyUseSignature,
Algorithm: oidc.SigningAlgECDSAUsingP384AndSHA384,
Key: keyECDSAP384,
CertificateChain: certECDSAP384,
},
{
Use: oidc.KeyUseSignature,
Algorithm: oidc.SigningAlgECDSAUsingP521AndSHA512,
Key: keyECDSAP521,
CertificateChain: certECDSAP521,
},
},
}
config.Discovery.DefaultKeyIDs = map[string]string{}
for i, key := range config.IssuerPrivateKeys {
kid := fmt.Sprintf("kid-%s-%s", key.Algorithm, key.Use)
config.IssuerPrivateKeys[i].KeyID = kid
if _, ok := config.Discovery.DefaultKeyIDs[key.Algorithm]; !ok {
config.Discovery.DefaultKeyIDs[key.Algorithm] = kid
}
}
manager := oidc.NewKeyManager(config)
assert.NotNil(t, manager)
ctx := context.Background()
assert.Equal(t, "kid-RS256-sig", manager.GetDefaultKeyID(ctx))
require.NotNil(t, manager.Get(ctx, "kid-RS256-sig", oidc.SigningAlgRSAUsingSHA256))
assert.Equal(t, "kid-RS256-sig", manager.Get(ctx, "kid-RS256-sig", oidc.SigningAlgRSAUsingSHA256).KeyID())
assert.Equal(t, "kid-RS256-sig", manager.Get(ctx, "", oidc.SigningAlgRSAUsingSHA256).KeyID())
assert.Nil(t, manager.Get(ctx, "", "NOKEY"))
assert.Equal(t, "kid-RS256-sig", manager.GetKeyID(ctx, "", oidc.SigningAlgRSAUsingSHA256))
assert.Equal(t, "kid-RS256-sig", manager.GetKeyID(ctx, "kid-RS256-sig", oidc.SigningAlgRSAPSSUsingSHA256))
assert.Equal(t, "kid-RS256-sig", manager.GetKeyID(ctx, "", ""))
assert.Equal(t, "kid-PS256-sig", manager.GetKeyID(ctx, "kid-PS256-sig", oidc.SigningAlgRSAPSSUsingSHA256))
assert.Equal(t, "kid-PS256-sig", manager.GetKeyID(ctx, "", oidc.SigningAlgRSAPSSUsingSHA256))
var (
jwk *oidc.JWK
tokenString, sig string
sum []byte
token *fjwt.Token
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.DefaultKeyIDs[oidc.SigningAlgRSAUsingSHA256], jwk.KeyID())
jwk, err = manager.GetByHeader(ctx, &fjwt.Headers{Extra: map[string]any{oidc.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.GetKeyIDFromAlgStrict(ctx, "notalg")
assert.EqualError(t, err, "alg not found")
assert.Equal(t, "", kid)
kid = manager.GetKeyIDFromAlg(ctx, "notalg")
assert.Equal(t, config.Discovery.DefaultKeyIDs[oidc.SigningAlgRSAUsingSHA256], kid)
set := manager.Set(ctx)
assert.NotNil(t, set)
assert.Len(t, set.Keys, len(config.IssuerPrivateKeys))
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)
jwk, err = manager.GetByTokenString(ctx, badTokenString)
assert.EqualError(t, err, "token contains an invalid number of segments")
assert.Nil(t, jwk)
tokenString, sig, err = manager.Generate(ctx, nil, nil)
assert.EqualError(t, err, "error getting jwk from header: jwt header was nil")
assert.Equal(t, "", tokenString)
assert.Equal(t, "", sig)
sig, err = manager.Validate(ctx, badTokenString)
assert.EqualError(t, err, "error getting jwk from token string: token contains an invalid number of segments")
assert.Equal(t, "", sig)
token, err = manager.Decode(ctx, badTokenString)
assert.EqualError(t, err, "error getting jwk from token string: token contains an invalid number of segments")
assert.Nil(t, token)
sum, err = manager.Hash(ctx, []byte("abc"))
assert.NoError(t, err)
assert.Equal(t, "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad", fmt.Sprintf("%x", sum))
assert.Equal(t, crypto.SHA256.Size(), manager.GetSigningMethodLength(ctx))
for _, alg := range []string{oidc.SigningAlgRSAUsingSHA256, oidc.SigningAlgRSAUsingSHA384, oidc.SigningAlgRSAPSSUsingSHA512, oidc.SigningAlgRSAPSSUsingSHA256, oidc.SigningAlgRSAPSSUsingSHA384, oidc.SigningAlgRSAPSSUsingSHA512, oidc.SigningAlgECDSAUsingP256AndSHA256, oidc.SigningAlgECDSAUsingP384AndSHA384, oidc.SigningAlgECDSAUsingP521AndSHA512} {
t.Run(alg, func(t *testing.T) {
expectedKID := fmt.Sprintf("kid-%s-%s", alg, oidc.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.GetSigningMethod().Alg())
assert.Equal(t, expectedKID, jwk.KeyID())
kid, err = manager.GetKeyIDFromAlgStrict(ctx, alg)
assert.NoError(t, err)
assert.Equal(t, expectedKID, kid)
kid = manager.GetKeyIDFromAlg(ctx, alg)
assert.Equal(t, expectedKID, kid)
jwk, err = manager.GetByHeader(ctx, &fjwt.Headers{Extra: map[string]any{oidc.JWTHeaderKeyIdentifier: expectedKID}})
assert.NoError(t, err)
assert.NotNil(t, jwk)
assert.Equal(t, expectedKID, jwk.KeyID())
})
t.Run("ShouldUseCorrectSigner", func(t *testing.T) {
var sigb string
tokenString, sig, err = manager.Generate(ctx, fjwt.MapClaims{}, &fjwt.Headers{Extra: map[string]any{oidc.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[oidc.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: oidc.KeyUseSignature,
Algorithm: oidc.SigningAlgRSAUsingSHA256,
Key: keyRSA2048,
CertificateChain: certRSA2048,
},
},
{
schema.JWK{
KeyID: "rsa2048-rs384",
Use: oidc.KeyUseSignature,
Algorithm: oidc.SigningAlgRSAUsingSHA384,
Key: keyRSA2048,
CertificateChain: certRSA2048,
},
},
{
schema.JWK{
KeyID: "rsa2048-rs512",
Use: oidc.KeyUseSignature,
Algorithm: oidc.SigningAlgRSAUsingSHA512,
Key: keyRSA2048,
CertificateChain: certRSA2048,
},
},
{
schema.JWK{
KeyID: "rsa4096-rs256",
Use: oidc.KeyUseSignature,
Algorithm: oidc.SigningAlgRSAUsingSHA256,
Key: keyRSA4096,
CertificateChain: certRSA4096,
},
},
{
schema.JWK{
KeyID: "rsa4096-rs384",
Use: oidc.KeyUseSignature,
Algorithm: oidc.SigningAlgRSAUsingSHA384,
Key: keyRSA4096,
CertificateChain: certRSA4096,
},
},
{
schema.JWK{
KeyID: "rsa4096-rs512",
Use: oidc.KeyUseSignature,
Algorithm: oidc.SigningAlgRSAUsingSHA512,
Key: keyRSA4096,
CertificateChain: certRSA4096,
},
},
{
schema.JWK{
KeyID: "rsa2048-rs256",
Use: oidc.KeyUseSignature,
Algorithm: oidc.SigningAlgRSAPSSUsingSHA256,
Key: keyRSA2048,
CertificateChain: certRSA2048,
},
},
{
schema.JWK{
KeyID: "rsa2048-ps384",
Use: oidc.KeyUseSignature,
Algorithm: oidc.SigningAlgRSAPSSUsingSHA384,
Key: keyRSA2048,
CertificateChain: certRSA2048,
},
},
{
schema.JWK{
KeyID: "rsa2048-ps512",
Use: oidc.KeyUseSignature,
Algorithm: oidc.SigningAlgRSAPSSUsingSHA512,
Key: keyRSA2048,
CertificateChain: certRSA2048,
},
},
{
schema.JWK{
KeyID: "rsa4096-ps256",
Use: oidc.KeyUseSignature,
Algorithm: oidc.SigningAlgRSAPSSUsingSHA256,
Key: keyRSA4096,
CertificateChain: certRSA4096,
},
},
{
schema.JWK{
KeyID: "rsa4096-ps384",
Use: oidc.KeyUseSignature,
Algorithm: oidc.SigningAlgRSAPSSUsingSHA384,
Key: keyRSA4096,
CertificateChain: certRSA4096,
},
},
{
schema.JWK{
KeyID: "rsa4096-ps512",
Use: oidc.KeyUseSignature,
Algorithm: oidc.SigningAlgRSAPSSUsingSHA512,
Key: keyRSA4096,
CertificateChain: certRSA4096,
},
},
{
schema.JWK{
KeyID: "ecdsaP256",
Use: oidc.KeyUseSignature,
Algorithm: oidc.SigningAlgECDSAUsingP256AndSHA256,
Key: keyECDSAP256,
CertificateChain: certECDSAP256,
},
},
{
schema.JWK{
KeyID: "ecdsaP384",
Use: oidc.KeyUseSignature,
Algorithm: oidc.SigningAlgECDSAUsingP384AndSHA384,
Key: keyECDSAP384,
CertificateChain: certECDSAP384,
},
},
{
schema.JWK{
KeyID: "ecdsaP521",
Use: oidc.KeyUseSignature,
Algorithm: oidc.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 *oidc.JWK
)
ctx := context.Background()
jwk = oidc.NewJWK(tc.have)
signer := jwk.Strategy()
claims := fjwt.MapClaims{}
header := &fjwt.Headers{
Extra: map[string]any{
oidc.JWTHeaderKeyIdentifier: jwk.KeyID(),
},
}
tokenString, sig, err := signer.Generate(ctx, nil, nil)
assert.EqualError(t, err, "either claims or header is nil")
assert.Equal(t, "", tokenString)
assert.Equal(t, "", sig)
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.GetSigningMethod().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 *oidc.JWK
out jose.JSONWebKey
data []byte
err error
)
jwk = oidc.NewJWK(tc.have)
strategy := jwk.Strategy()
assert.NotNil(t, strategy)
signer, ok := strategy.(*oidc.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())
})
})
}
}