feat(oidc): multiple jwk algorithms

This adds support for multiple JWK algorithms and keys and allows for per-client algorithm choices.

Signed-off-by: James Elliott <james-d-elliott@users.noreply.github.com>
fix-pkce-flow
James Elliott 2023-04-19 14:24:05 +10:00
parent 435d8e35fd
commit 602041d37d
No known key found for this signature in database
GPG Key ID: 0F1C4A096E857E49
65 changed files with 3165 additions and 660 deletions

View File

@ -28,12 +28,12 @@ More information about the beta can be found in the [roadmap](../../roadmap/acti
## Configuration
{{< config-alert-example >}}
The following snippet provides a sample-configuration for the OIDC identity provider explaining each field in detail.
```yaml
identity_providers:
oidc:
hmac_secret: 'this_is_a_secret_abc123abc123abc'
hmac_secret: this_is_a_secret_abc123abc123abc
issuer_certificate_chain: |
-----BEGIN CERTIFICATE-----
MIIC5jCCAc6gAwIBAgIRAK4Sj7FiN6PXo/urPfO4E7owDQYJKoZIhvcNAQELBQAw
@ -101,54 +101,59 @@ identity_providers:
27GoE2i5mh6Yez6VAYbUuns3FcwIsMyWLq043Tu2DNkx9ijOOAuQzw^invalid..
DO NOT USE==
-----END RSA PRIVATE KEY-----
access_token_lifespan: '1h'
authorize_code_lifespan: '1m'
id_token_lifespan: '1h'
refresh_token_lifespan: '90m'
issuer_jwks:
- key_id: ''
algorithm: 'RS256'
key: |
<private key data>
certificate_chain: |
<certificate chain data>
access_token_lifespan: 1h
authorize_code_lifespan: 1m
id_token_lifespan: 1h
refresh_token_lifespan: 90m
enable_client_debug_messages: false
enforce_pkce: 'public_clients_only'
enforce_pkce: public_clients_only
cors:
endpoints:
- 'authorization'
- 'token'
- 'revocation'
- 'introspection'
- authorization
- token
- revocation
- introspection
allowed_origins:
- 'https://example.com'
- https://example.com
allowed_origins_from_client_redirect_uris: false
clients:
- id: 'myapp'
description: 'My Application'
- id: myapp
description: My Application
secret: '$pbkdf2-sha512$310000$c8p78n7pUMln0jzvd4aK4Q$JNRBzwAo0ek5qKn50cFzzvE9RXV88h1wJn5KGiHrD0YKtZaR/nCb2CJPOsKaPK0hjf.9yHxzQGZziziccp6Yng' # The digest of 'insecure_secret'.
sector_identifier: ''
public: false
authorization_policy: 'two_factor'
consent_mode: 'explicit'
pre_configured_consent_duration: '1w'
authorization_policy: two_factor
consent_mode: explicit
pre_configured_consent_duration: 1w
audience: []
scopes:
- 'openid'
- 'groups'
- 'email'
- 'profile'
- openid
- groups
- email
- profile
redirect_uris:
- 'https://oidc.example.com:8080/oauth2/callback'
- https://oidc.example.com:8080/oauth2/callback
grant_types:
- 'refresh_token'
- 'authorization_code'
- refresh_token
- authorization_code
response_types:
- 'code'
- code
response_modes:
- 'form_post'
- 'query'
- 'fragment'
userinfo_signing_algorithm: 'none'
- form_post
- query
- fragment
userinfo_signing_algorithm: none
```
## Options
This section describes the individual configuration options.
### hmac_secret
{{< confkey type="string" required="yes" >}}
@ -163,23 +168,6 @@ It's __strongly recommended__ this is a
[Random Alphanumeric String](../../reference/guides/generating-secure-values.md#generating-a-random-alphanumeric-string)
with 64 or more characters.
### issuer_certificate_chain
{{< confkey type="string" required="no" >}}
The certificate chain/bundle to be used with the [issuer_private_key](#issuer_private_key) DER base64 ([RFC4648])
encoded PEM format used to sign/encrypt the [OpenID Connect 1.0] [JWT]'s. When configured it enables the [x5c] and [x5t]
JSON key's in the JWKs [Discoverable Endpoint](../../integration/openid-connect/introduction.md#discoverable-endpoints)
as per [RFC7517].
[RFC7517]: https://datatracker.ietf.org/doc/html/rfc7517
[x5c]: https://datatracker.ietf.org/doc/html/rfc7517#section-4.7
[x5t]: https://datatracker.ietf.org/doc/html/rfc7517#section-4.8
The first certificate in the chain must have the public key for the [issuer_private_key](#issuerprivatekey), each
certificate in the chain must be valid for the current date, and each certificate in the chain should be signed by the
certificate immediately following it if present.
### issuer_private_key
{{< confkey type="string" required="yes" >}}
@ -199,13 +187,85 @@ The private key *__MUST__*:
If the [issuer_certificate_chain](#issuercertificatechain) is provided the private key must include matching public
key data for the first certificate in the chain.
### issuer_certificate_chain
{{< confkey type="string" required="no" >}}
The certificate chain/bundle to be used with the [issuer_private_key](#issuer_private_key) DER base64 ([RFC4648])
encoded PEM format used to sign/encrypt the [OpenID Connect 1.0] [JWT]'s. When configured it enables the [x5c] and [x5t]
JSON key's in the JWKs [Discoverable Endpoint](../../integration/openid-connect/introduction.md#discoverable-endpoints)
as per [RFC7517].
[RFC7517]: https://datatracker.ietf.org/doc/html/rfc7517
[x5c]: https://datatracker.ietf.org/doc/html/rfc7517#section-4.7
[x5t]: https://datatracker.ietf.org/doc/html/rfc7517#section-4.8
The first certificate in the chain must have the public key for the [issuer_private_key](#issuerprivatekey), each
certificate in the chain must be valid for the current date, and each certificate in the chain should be signed by the
certificate immediately following it if present.
### issuer_jwks
{{< confkey type="list(object" required="no" >}}
The list of JWKS instead of or in addition to the [issuer_private_key](#issuerprivatekey) and
[issuer_certificate_chain](#issuercertificatechain). Can also accept ECDSA Private Key's and Certificates.
#### key_id
{{< confkey type="string" default="<thumbprint of public key>" required="no" >}}
Completely optional, and generally discouraged unless there is a collision between the automatically generated key id's.
If provided must be a unique string with 7 or less alphanumeric characters.
This value is the first 7 characters of the public key thumbprint (SHA1) encoded into hexadecimal.
#### algorithm
{{< confkey type="string" required="no" >}}
The algorithm for this key. This value must be unique. It's automatically detected based on the type of key.
#### key
{{< confkey type="string" required="yes" >}}
The private key associated with this key entry.
The private key used to sign/encrypt the [OpenID Connect 1.0] issued [JWT]'s. The key must be generated by the administrator
and can be done by following the
[Generating an RSA Keypair](../../reference/guides/generating-secure-values.md#generating-an-rsa-keypair) guide.
The private key *__MUST__*:
* Be a PEM block encoded in the DER base64 format ([RFC4648]).
* Be one of:
* An RSA key with a key size of at least 2048 bits.
* An ECDSA private key with one of the P-256, P-384, or P-521 elliptical curves.
If the [certificate_chain](#certificatechain) is provided the private key must include matching public
key data for the first certificate in the chain.
#### certificate_chain
{{< confkey type="string" required="no" >}}
The certificate chain/bundle to be used with the [key](#key) DER base64 ([RFC4648])
encoded PEM format used to sign/encrypt the [OpenID Connect 1.0] [JWT]'s. When configured it enables the [x5c] and [x5t]
JSON key's in the JWKs [Discoverable Endpoint](../../integration/openid-connect/introduction.md#discoverable-endpoints)
as per [RFC7517].
[RFC7517]: https://datatracker.ietf.org/doc/html/rfc7517
[x5c]: https://datatracker.ietf.org/doc/html/rfc7517#section-4.7
[x5t]: https://datatracker.ietf.org/doc/html/rfc7517#section-4.8
The first certificate in the chain must have the public key for the [key](#key), each certificate in the chain must be
valid for the current date, and each certificate in the chain should be signed by the certificate immediately following
it if present.
### access_token_lifespan
{{< confkey type="duration" default="1h" required="no" >}}
*__Reference Note:__ This configuration option uses the [duration common syntax](../prologue/common.md#duration).
Please see the [documentation](../prologue/common.md#duration) on this format for more information.*
The maximum lifetime of an access token. It's generally recommended keeping this short similar to the default.
For more information read these docs about [token lifespan].
@ -213,9 +273,6 @@ For more information read these docs about [token lifespan].
{{< confkey type="duration" default="1m" required="no" >}}
*__Reference Note:__ This configuration option uses the [duration common syntax](../prologue/common.md#duration).
Please see the [documentation](../prologue/common.md#duration) on this format for more information.*
The maximum lifetime of an authorize code. This can be rather short, as the authorize code should only be needed to
obtain the other token types. For more information read these docs about [token lifespan].
@ -223,18 +280,12 @@ obtain the other token types. For more information read these docs about [token
{{< confkey type="duration" default="1h" required="no" >}}
*__Reference Note:__ This configuration option uses the [duration common syntax](../prologue/common.md#duration).
Please see the [documentation](../prologue/common.md#duration) on this format for more information.*
The maximum lifetime of an ID token. For more information read these docs about [token lifespan].
### refresh_token_lifespan
{{< confkey type="string" default="90m" required="no" >}}
*__Reference Note:__ This configuration option uses the [duration common syntax](../prologue/common.md#duration).
Please see the [documentation](../prologue/common.md#duration) on this format for more information.*
The maximum lifetime of a refresh token. The
refresh token can be used to obtain new refresh tokens as well as access tokens or id tokens with an
up-to-date expiration. For more information read these docs about [token lifespan].
@ -300,9 +351,6 @@ When enabled all authorization requests must use the [Pushed Authorization Reque
{{< confkey type="duration" default="5m" required="no" >}}
*__Reference Note:__ This configuration option uses the [duration common syntax](../prologue/common.md#duration).
Please see the [documentation](../prologue/common.md#duration) on this format for more information.*
The maximum amount of time between the [Pushed Authorization Requests] flow being initiated and the generated
`request_uri` being utilized by a client.
@ -590,8 +638,8 @@ Configures the consent mode. The following table describes the different modes:
{{< confkey type="duration" default="1w" required="no" >}}
*__Reference Note:__ This configuration option uses the [duration common syntax](../prologue/common.md#duration).
Please see the [documentation](../prologue/common.md#duration) on this format for more information.*
*__Note:__ This setting uses the [duration notation format](../prologue/common.md#duration-notation-format). Please see
the [common options](../prologue/common.md#duration-notation-format) documentation for information on this format.*
Specifying this in the configuration without a consent [consent_mode] enables the `pre-configured` mode. If this is
specified as well as the [consent_mode] then it only has an effect if the [consent_mode] is `pre-configured` or `auto`.

View File

@ -101,6 +101,39 @@ This scope includes the profile information the authentication backend reports a
| preferred_username | string | username | The username the user used to login with |
| name | string | display_name | The users display name |
## Signing and Encryption Algorithms
[OpenID Connect 1.0] and OAuth 2.0 support a wide variety of signature and encryption algorithms. Authelia supports
a subset of these.
### Response Object
Authelia's response objects can have the following signature algorithms:
| Algorithm | Key Type | Hashing Algorithm | Use | JWK Default Conditions | Notes |
|:---------:|:-----------:|:-----------------:|:---------:|:--------------------------------------------:|:----------------------------------------------------:|
| RS256 | RSA | SHA-256 | Signature | RSA Private Key without a specific algorithm | Requires an RSA Private Key with 2048 bits or more |
| RS384 | RSA | SHA-384 | Signature | N/A | Requires an RSA Private Key with 2048 bits or more |
| RS512 | RSA | SHA-512 | Signature | N/A | Requires an RSA Private Key with 2048 bits or more |
| ES256 | ECDSA P-256 | SHA-256 | Signature | ECDSA Private Key with the P-256 curve | |
| ES384 | ECDSA P-384 | SHA-384 | Signature | ECDSA Private Key with the P-384 curve | |
| ES512 | ECDSA P-521 | SHA-512 | Signature | ECDSA Private Key with the P-521 curve | Requires an ECDSA Private Key with 2048 bits or more |
| PS256 | RSA (MGF1) | SHA-256 | Signature | N/A | Requires an RSA Private Key with 2048 bits or more |
| PS384 | RSA (MGF1) | SHA-384 | Signature | N/A | Requires an RSA Private Key with 2048 bits or more |
| PS512 | RSA (MGF1) | SHA-512 | Signature | N/A | Requires an RSA Private Key with 2048 bits or more |
### Request Object
| Algorithm | Key Type | Hashing Algorithm | Use | Notes |
|:---------:|:------------------:|:-----------------:|:---------:|:--------------------------------------------------:|
| none | None | None | N/A | N/A |
| HS256 | HMAC Shared Secret | SHA-256 | Signature | [Client Authentication Method] `client_secret_jwt` |
| HS384 | HMAC Shared Secret | SHA-384 | Signature | [Client Authentication Method] `client_secret_jwt` |
| HS512 | HMAC Shared Secret | SHA-512 | Signature | [Client Authentication Method] `client_secret_jwt` |
[Client Authentication Method]: #client-authentication-method
## Parameters
The following section describes advanced parameters which can be used in various endpoints as well as their related

View File

@ -162,9 +162,7 @@ func (ctx *CmdCtx) LoadProviders() (warns, errs []error) {
ctx.providers.Notifier = notification.NewFileNotifier(*ctx.config.Notifier.FileSystem)
}
if ctx.providers.OpenIDConnect, err = oidc.NewOpenIDConnectProvider(ctx.config.IdentityProviders.OIDC, ctx.providers.StorageProvider, ctx.providers.Templates); err != nil {
errs = append(errs, err)
}
ctx.providers.OpenIDConnect = oidc.NewOpenIDConnectProvider(ctx.config.IdentityProviders.OIDC, ctx.providers.StorageProvider, ctx.providers.Templates)
if ctx.config.Telemetry.Metrics.Enabled {
ctx.providers.Metrics = metrics.NewPrometheus()

View File

@ -335,7 +335,7 @@ func (ctx *CmdCtx) CryptoCertificateRequestRunE(cmd *cobra.Command, _ []string)
return err
}
if err = utils.WriteCertificateBytesToPEM(csrPath, true, csr); err != nil {
if err = utils.WriteCertificateBytesAsPEMToPath(csrPath, true, csr); err != nil {
return err
}
@ -430,7 +430,7 @@ func (ctx *CmdCtx) CryptoCertificateGenerateRunE(cmd *cobra.Command, _ []string,
return err
}
if err = utils.WriteCertificateBytesToPEM(certificatePath, false, certificate); err != nil {
if err = utils.WriteCertificateBytesAsPEMToPath(certificatePath, false, certificate); err != nil {
return err
}

View File

@ -198,7 +198,7 @@ func cryptoGenerateCertificateBundlesFromCmd(cmd *cobra.Command, b *strings.Buil
b.WriteString(fmt.Sprintf("\tCertificate (chain): %s\n", pathPEM))
if err = utils.WritePEM(pathPEM, blocks...); err != nil {
if err = utils.WritePEMBlocksToPath(pathPEM, blocks...); err != nil {
return err
}
}
@ -220,7 +220,7 @@ func cryptoGenerateCertificateBundlesFromCmd(cmd *cobra.Command, b *strings.Buil
b.WriteString(fmt.Sprintf("\tCertificate (priv-chain): %s\n", pathPEM))
if err = utils.WritePEM(pathPEM, blocks...); err != nil {
if err = utils.WritePEMBlocksToPath(pathPEM, blocks...); err != nil {
return err
}
}

View File

@ -513,6 +513,30 @@ func StringToCryptoPrivateKeyHookFunc() mapstructure.DecodeHookFuncType {
}
}
// StringToCryptographicKeyHookFunc decodes strings to schema.CryptographicKey's.
func StringToCryptographicKeyHookFunc() mapstructure.DecodeHookFuncType {
return func(f reflect.Type, t reflect.Type, data any) (value any, err error) {
if f.Kind() != reflect.String {
return data, nil
}
field, _ := reflect.TypeOf(schema.JWK{}).FieldByName("Key")
expectedType := field.Type
if t != expectedType {
return data, nil
}
dataStr := data.(string)
if value, err = utils.ParseX509FromPEM([]byte(dataStr)); err != nil {
return nil, fmt.Errorf(errFmtDecodeHookCouldNotParseBasic, "", expectedType, err)
}
return value, nil
}
}
// StringToPrivateKeyHookFunc decodes strings to rsa.PrivateKey's.
func StringToPrivateKeyHookFunc() mapstructure.DecodeHookFuncType {
return func(f reflect.Type, t reflect.Type, data any) (value any, err error) {

View File

@ -21,6 +21,7 @@ func TestKoanfEnvironmentCallback(t *testing.T) {
keyMap := map[string]string{
DefaultEnvPrefix + "KEY_EXAMPLE_UNDERSCORE": "key.example_underscore",
}
ignoredKeys := []string{DefaultEnvPrefix + "SOME_SECRET"}
callback := koanfEnvironmentCallback(keyMap, ignoredKeys, DefaultEnvPrefix, DefaultEnvDelimiter)

View File

@ -65,6 +65,7 @@ func unmarshal(ko *koanf.Koanf, val *schema.StructValidator, path string, o any)
StringToX509CertificateChainHookFunc(),
StringToPrivateKeyHookFunc(),
StringToCryptoPrivateKeyHookFunc(),
StringToCryptographicKeyHookFunc(),
StringToTLSVersionHookFunc(),
StringToPasswordDigestHookFunc(),
ToTimeDurationHookFunc(),

View File

@ -245,6 +245,37 @@ func TestShouldLoadURLList(t *testing.T) {
assert.Equal(t, "https://example.com", config.IdentityProviders.OIDC.CORS.AllowedOrigins[1].String())
}
/*
func TestShouldLoadNewOIDCConfig(t *testing.T) {
val := schema.NewStructValidator()
_, config, err := Load(val, NewDefaultSources([]string{"./test_resources/config_oidc_modern.yml"}, DefaultEnvPrefix, DefaultEnvDelimiter)...)
assert.NoError(t, err)
assert.Len(t, val.Errors(), 0)
assert.Len(t, val.Warnings(), 0)
val.Clear()
validator.ValidateIdentityProviders(&config.IdentityProviders, val)
assert.Len(t, val.Errors(), 0)
assert.Len(t, config.IdentityProviders.OIDC.IssuerJWKS.Keys, 2)
assert.Equal(t, "keya", config.IdentityProviders.OIDC.IssuerJWKS.DefaultKeyID)
assert.Equal(t, oidc.KeyUseSignature, config.IdentityProviders.OIDC.IssuerJWKS.Keys["keya"].Use)
assert.Equal(t, oidc.SigningAlgRSAUsingSHA256, config.IdentityProviders.OIDC.IssuerJWKS.Keys["keya"].Algorithm)
assert.Equal(t, oidc.KeyUseSignature, config.IdentityProviders.OIDC.IssuerJWKS.Keys["ec521"].Use)
assert.Equal(t, oidc.SigningAlgECDSAUsingP521AndSHA512, config.IdentityProviders.OIDC.IssuerJWKS.Keys["ec521"].Algorithm)
assert.Contains(t, config.IdentityProviders.OIDC.Discovery.RegisteredJWKSigningAlgs, oidc.SigningAlgRSAUsingSHA256)
assert.Contains(t, config.IdentityProviders.OIDC.Discovery.RegisteredJWKSigningAlgs, oidc.SigningAlgECDSAUsingP521AndSHA512)
}.
*/
func TestShouldConfigureConsent(t *testing.T) {
val := schema.NewStructValidator()
keys, config, err := Load(val, NewDefaultSources([]string{"./test_resources/config_oidc.yml"}, DefaultEnvPrefix, DefaultEnvDelimiter)...)

View File

@ -17,6 +17,8 @@ type OpenIDConnectConfiguration struct {
IssuerCertificateChain X509CertificateChain `koanf:"issuer_certificate_chain"`
IssuerPrivateKey *rsa.PrivateKey `koanf:"issuer_private_key"`
IssuerJWKS []JWK `koanf:"issuer_jwks"`
AccessTokenLifespan time.Duration `koanf:"access_token_lifespan"`
AuthorizeCodeLifespan time.Duration `koanf:"authorize_code_lifespan"`
IDTokenLifespan time.Duration `koanf:"id_token_lifespan"`
@ -32,6 +34,13 @@ type OpenIDConnectConfiguration struct {
PAR OpenIDConnectPARConfiguration `koanf:"pushed_authorizations"`
Clients []OpenIDConnectClientConfiguration `koanf:"clients"`
Discovery OpenIDConnectDiscovery // MetaData value. Not configurable by users.
}
type OpenIDConnectDiscovery struct {
DefaultKeyID string
RegisteredJWKSigningAlgs []string
}
// OpenIDConnectPARConfiguration represents an OpenID Connect PAR config.
@ -67,13 +76,15 @@ type OpenIDConnectClientConfiguration struct {
TokenEndpointAuthMethod string `koanf:"token_endpoint_auth_method"`
TokenEndpointAuthSigningAlg string `koanf:"token_endpoint_auth_signing_alg"`
IDTokenSigningAlg string `koanf:"id_token_signing_alg"`
Policy string `koanf:"authorization_policy"`
EnforcePAR bool `koanf:"enforce_par"`
EnforcePKCE bool `koanf:"enforce_pkce"`
PKCEChallengeMethod string `koanf:"pkce_challenge_method"`
UserinfoSigningAlgorithm string `koanf:"userinfo_signing_algorithm"`
UserinfoSigningAlg string `koanf:"userinfo_signing_algorithm"`
ConsentMode string `koanf:"consent_mode"`
ConsentPreConfiguredDuration *time.Duration `koanf:"pre_configured_consent_duration"`
@ -97,7 +108,8 @@ var DefaultOpenIDConnectClientConfiguration = OpenIDConnectClientConfiguration{
ResponseTypes: []string{"code"},
ResponseModes: []string{"form_post"},
UserinfoSigningAlgorithm: "none",
IDTokenSigningAlg: "RS256",
UserinfoSigningAlg: "none",
ConsentMode: "auto",
ConsentPreConfiguredDuration: &defaultOIDCClientConsentPreConfiguredDuration,
}

View File

@ -20,6 +20,12 @@ var Keys = []string{
"identity_providers.oidc.hmac_secret",
"identity_providers.oidc.issuer_certificate_chain",
"identity_providers.oidc.issuer_private_key",
"identity_providers.oidc.issuer_jwks",
"identity_providers.oidc.issuer_jwks[].key_id",
"identity_providers.oidc.issuer_jwks[]",
"identity_providers.oidc.issuer_jwks[].algorithm",
"identity_providers.oidc.issuer_jwks[].key",
"identity_providers.oidc.issuer_jwks[].certificate_chain",
"identity_providers.oidc.access_token_lifespan",
"identity_providers.oidc.authorize_code_lifespan",
"identity_providers.oidc.id_token_lifespan",
@ -47,6 +53,7 @@ var Keys = []string{
"identity_providers.oidc.clients[].response_modes",
"identity_providers.oidc.clients[].token_endpoint_auth_method",
"identity_providers.oidc.clients[].token_endpoint_auth_signing_alg",
"identity_providers.oidc.clients[].id_token_signing_alg",
"identity_providers.oidc.clients[].authorization_policy",
"identity_providers.oidc.clients[].enforce_par",
"identity_providers.oidc.clients[].enforce_pkce",
@ -54,6 +61,7 @@ var Keys = []string{
"identity_providers.oidc.clients[].userinfo_signing_algorithm",
"identity_providers.oidc.clients[].consent_mode",
"identity_providers.oidc.clients[].pre_configured_consent_duration",
"identity_providers.oidc",
"authentication_backend.password_reset.disable",
"authentication_backend.password_reset.custom_url",
"authentication_backend.refresh_interval",

View File

@ -34,3 +34,12 @@ type ServerBuffers struct {
Read int `koanf:"read"`
Write int `koanf:"write"`
}
// JWK represents a JWK.
type JWK struct {
KeyID string `koanf:"key_id"`
Use string
Algorithm string `koanf:"algorithm"`
Key CryptographicKey `koanf:"key"`
CertificateChain X509CertificateChain `koanf:"certificate_chain"`
}

View File

@ -1,6 +1,7 @@
package schema
import (
"bytes"
"crypto"
"crypto/ecdsa"
"crypto/ed25519"
@ -101,6 +102,10 @@ func NewX509CertificateChain(in string) (chain *X509CertificateChain, err error)
return chain, nil
}
func NewX509CertificateChainFromCerts(in []*x509.Certificate) (chain X509CertificateChain) {
return X509CertificateChain{certs: in}
}
// NewTLSVersion returns a new TLSVersion given a string.
func NewTLSVersion(input string) (version *TLSVersion, err error) {
switch strings.ReplaceAll(strings.ToUpper(input), " ", "") {
@ -166,6 +171,9 @@ type CryptographicPrivateKey interface {
Equal(x crypto.PrivateKey) bool
}
// CryptographicKey represents an artificial cryptographic public or private key.
type CryptographicKey any
// X509CertificateChain is a helper struct that holds a list of *x509.Certificate's.
type X509CertificateChain struct {
certs []*x509.Certificate
@ -277,6 +285,24 @@ func (c *X509CertificateChain) Leaf() (leaf *x509.Certificate) {
return c.certs[0]
}
// EncodePEM encodes the entire chain as PEM bytes.
func (c *X509CertificateChain) EncodePEM() (encoded []byte, err error) {
buf := &bytes.Buffer{}
for _, cert := range c.certs {
block := pem.Block{
Type: blockCERTIFICATE,
Bytes: cert.Raw,
}
if err = pem.Encode(buf, &block); err != nil {
return nil, err
}
}
return buf.Bytes(), nil
}
// Validate the X509CertificateChain ensuring the certificates were provided in the correct order
// (with nth being signed by the nth+1), and that all of the certificates are valid based on the current time.
func (c *X509CertificateChain) Validate() (err error) {

View File

@ -0,0 +1,211 @@
---
default_redirection_url: https://home.example.com:8080/
server:
host: 127.0.0.1
port: 9091
log:
level: debug
totp:
issuer: authelia.com
duo_api:
hostname: api-123456789.example.com
integration_key: ABCDEF
authentication_backend:
ldap:
url: ldap://127.0.0.1
base_dn: dc=example,dc=com
username_attribute: uid
additional_users_dn: ou=users
users_filter: (&({username_attribute}={input})(objectCategory=person)(objectClass=user))
additional_groups_dn: ou=groups
groups_filter: (&(member={dn})(objectClass=groupOfNames))
group_name_attribute: cn
mail_attribute: mail
user: cn=admin,dc=example,dc=com
access_control:
default_policy: deny
rules:
# Rules applied to everyone
- domain: public.example.com
policy: bypass
- domain: secure.example.com
policy: one_factor
# Network based rule, if not provided any network matches.
networks:
- 192.168.1.0/24
- domain: secure.example.com
policy: two_factor
- domain: [singlefactor.example.com, onefactor.example.com]
policy: one_factor
# Rules applied to 'admins' group
- domain: "mx2.mail.example.com"
subject: "group:admins"
policy: deny
- domain: "*.example.com"
subject: "group:admins"
policy: two_factor
# Rules applied to 'dev' group
- domain: dev.example.com
resources:
- "^/groups/dev/.*$"
subject: "group:dev"
policy: two_factor
# Rules applied to user 'john'
- domain: dev.example.com
resources:
- "^/users/john/.*$"
subject: "user:john"
policy: two_factor
# Rules applied to 'dev' group and user 'john'
- domain: dev.example.com
resources:
- "^/deny-all.*$"
subject: ["group:dev", "user:john"]
policy: deny
# Rules applied to user 'harry'
- domain: dev.example.com
resources:
- "^/users/harry/.*$"
subject: "user:harry"
policy: two_factor
# Rules applied to user 'bob'
- domain: "*.mail.example.com"
subject: "user:bob"
policy: two_factor
- domain: "dev.example.com"
resources:
- "^/users/bob/.*$"
subject: "user:bob"
policy: two_factor
session:
name: authelia_session
expiration: 3600000 # 1 hour
inactivity: 300000 # 5 minutes
domain: example.com
redis:
host: 127.0.0.1
port: 6379
high_availability:
sentinel_name: test
regulation:
max_retries: 3
find_time: 120
ban_time: 300
storage:
mysql:
host: 127.0.0.1
port: 3306
database: authelia
username: authelia
notifier:
smtp:
username: test
host: 127.0.0.1
port: 1025
sender: admin@example.com
disable_require_tls: true
identity_providers:
oidc:
hmac_secret: 1nb2j3kh1b23kjh1b23jh1b23j1h2b3
issuer_jwks:
keys:
keya:
key: |
-----BEGIN RSA PRIVATE KEY-----
MIIEpQIBAAKCAQEAs5BZdREjkceDvty5c+qBski4XXiMubVyGFLazoNumhMbgjA7
DoCLglCrIRcYd4Wn4CEe4KJzghIdfijDCIQ6V+wrm/KR3iMvBdkPYVC7vGXYY5kx
WtAT5qejuxKXHQK2/jWSO6tOxFRGxA/nE4A9cm8FH6/lfz05ci1h63gAOVQpkvcj
JMHlGqTHt93HOTBVFQpi9zpTdDKQx3yq4ttfh49vUwWBsXe640Y+soaGjTMJS8IT
kGktwOxwRfGJ1PRHVF9FnRm7nhf53hFat/k3mbbyV8rnlmVLPqZ+KIqH5/rjYh3K
Rr71WAptFnHtoiT2SNfwDh+8iqo3QlWtW24iAwIDAQABAoIBAQCLKVkbMEA3z79b
4SZdHqaLbG5uCmpN1sBo93WaTSQfhqVwHT73u0njoe8ugv60SsJTIngSsfQBH1b6
Gk8kv42T7HXTs4e299+Oka2oxu/oT6oHbodgkRiLTurGpd61XhBCLXR6iAZQg9wg
QQ7d/yogEMiQyTp8hQ+LXH6iBetugW0l0Uz2pbJNi4c4qqXm5BYoQaJ0RjqQI5C5
5ZPiX/1yn3bWbJRhSK5FfnEdO/3LclfQMvMOaipH4CXOjYcFSxVP1vVL6Jli92+j
tVApsnSZiEZ3kB4jRqDZnV9xQhfTXVyfopCNL1a3LkbE171GStd5eib7ESydTik7
DhFqTdpZAoGBAOgEPAmFKi9z40umcN27+dd6CAXfjy2dqkuM8hGshQiGnH9bAAl+
hhr3u5AvbxZ/qsD0KAAEReD3cY0OFTPfl2qD0nsleqUt6N6gM+j6596jVNnOlW9A
0y0Lssobh8DrvXoDTBCL1wdcQXknoyUm8lkpVhSm6PmLFAS2lnBFJBJvAoGBAMYg
FGeSQ0kRWmmVnfibcHqHerD5FK6AvciYg/ycjKUA9Fde0gon1v/M1TWAhT6mqjjv
uSX2c3eEpzAjoJxd5bPgxfEWIM6ygn1N5fka1re5d5pHOAMJB9AAz3A3PZJP8ANt
ES51dO0rYAuKdBuza9eSyj9/JPDh7QdE47ZvP6OtAoGBAI4aAddm2uaDWOP9hcUY
mzXRBNbsDJpIpYNuSNhwTG5jW7hYuNYXyvT7Y8I0expRiPhy0YjpFQ9rHf3hcTT7
LZbMM/6+frZqPuUTQ5ffDGJ8sLxR3Y5tKqm9L3y/jc6n073GBTFhJIragzM8Bpz7
lJTtT06Ix8oG13Tni44pmqU7AoGBAJPS56aHSNDBs9XHnkAZqgiiAPb+QWIaCIAc
242lOIL8fVKbGtgc9ZuSNxpeNAyUybkFk/0xLuHkBeIzEujYXkSh1s6UlhHiut3H
O2lrjv0x0n032iDZogyeLigp7zS1k/zaadFiLcWvcU/rE8p/Sl1j1qcdtHBOAU5F
Jim+Q5tZAoGAbNaUs05FPastfdmZCp5Pj1sbnRZdwUZH1AeZuhuSfbS6aA/wB3VA
i/LTu4Z3iR0M0p8HeNy/YBKl0MrRc0nE/UkV66kOQH3az/N39i2KsTBzMsvlByWd
ofwOgCjgkIviNDIXikLBEWZFwr/4mQkN91YoFY37pteS3sVYQpVbQeA=
-----END RSA PRIVATE KEY-----
certificate_chain: |
-----BEGIN CERTIFICATE-----
MIIC5jCCAc6gAwIBAgIRAMMH7qhte0VDXdnbHMy43dUwDQYJKoZIhvcNAQELBQAw
EzERMA8GA1UEChMIQXV0aGVsaWEwHhcNMjMwNDE2MTEyMjU0WhcNMjQwNDE1MTEy
MjU0WjATMREwDwYDVQQKEwhBdXRoZWxpYTCCASIwDQYJKoZIhvcNAQEBBQADggEP
ADCCAQoCggEBALOQWXURI5HHg77cuXPqgbJIuF14jLm1chhS2s6DbpoTG4IwOw6A
i4JQqyEXGHeFp+AhHuCic4ISHX4owwiEOlfsK5vykd4jLwXZD2FQu7xl2GOZMVrQ
E+ano7sSlx0Ctv41kjurTsRURsQP5xOAPXJvBR+v5X89OXItYet4ADlUKZL3IyTB
5Rqkx7fdxzkwVRUKYvc6U3QykMd8quLbX4ePb1MFgbF3uuNGPrKGho0zCUvCE5Bp
LcDscEXxidT0R1RfRZ0Zu54X+d4RWrf5N5m28lfK55ZlSz6mfiiKh+f642Idyka+
9VgKbRZx7aIk9kjX8A4fvIqqN0JVrVtuIgMCAwEAAaM1MDMwDgYDVR0PAQH/BAQD
AgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcN
AQELBQADggEBAKT2CQNx/JlutnsMHYBoOLfr8Vpz3/PTx0rAgxptgp1CDkTBxIah
7rmGuHX0qDKbqINr4DmFhkKeIPSMux11xzW6MW83ZN8WZCTkAiPPIGTNGWccJONl
lpX5dIPdpXGoXA7T31Gto3FPmsgxQ2jK/mpok20J6EFkkpUuXxSxjwO1zd56iMxQ
FuSLo8J/uI/T4aj7Wrk6fFI5z7gjP8BjVFAsYkTYUhkLatnbuQstx+R2p7hjv50G
weBOw5YW8JWLRRJ2A5FBJsZekiNdpIa+CmH7v0SICAHaTKdR3RVgQfa8zvbzW/d8
qXsSjjBkmEkKUFoFi/fxTQuqseQC0h+P5N8=
-----END CERTIFICATE-----
ec521:
key: |
-----BEGIN EC PRIVATE KEY-----
MIHcAgEBBEIA5s1+7OZClSDG3Ro7FFJjR2J6cBFimlR/sNcZ4ljFjDaef4vNw2DU
Eq1x5gkj888I1/BXV+/KVc+dtYDGKeGSxvagBwYFK4EEACOhgYkDgYYABADXFb5h
KymYeOH8Em1VJvOsc9mUi6Gr0AAiseu5G0HofN+GzxD7GBDAE9plkRhd8QfmuwZy
S0rUlTXhvZMARuujnABxJ7FnPp81osndv/vk9ujUTZsK0UPaLJ189NuR6VwImUQK
c/xWqI9AC99VchRw6fw7smpn6lCmVkHNRJFL1Bs4iA==
-----END EC PRIVATE KEY-----
certificate_chain: |
-----BEGIN CERTIFICATE-----
MIIB4jCCAUOgAwIBAgIRAKhDsoaXc69n4uH0CB31XfswCgYIKoZIzj0EAwIwEzER
MA8GA1UEChMIQXV0aGVsaWEwHhcNMjMwNDE2MTEyMTQxWhcNMjQwNDE1MTEyMTQx
WjATMREwDwYDVQQKEwhBdXRoZWxpYTCBmzAQBgcqhkjOPQIBBgUrgQQAIwOBhgAE
ANcVvmErKZh44fwSbVUm86xz2ZSLoavQACKx67kbQeh834bPEPsYEMAT2mWRGF3x
B+a7BnJLStSVNeG9kwBG66OcAHEnsWc+nzWiyd2/++T26NRNmwrRQ9osnXz025Hp
XAiZRApz/Faoj0AL31VyFHDp/DuyamfqUKZWQc1EkUvUGziIozUwMzAOBgNVHQ8B
Af8EBAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADAKBggq
hkjOPQQDAgOBjAAwgYgCQgCBXWYXKn7aANz5JC8sXLJSOkNMF6vbd/T96KoLSBT+
+l6KKwKg5evolAKync1ksGAzNidFKqjhwG4BZj10YnowPQJCAWjb/3KeRN1RmGOc
abG/6Y0USqC3LUb/ZrtVwRYvQYZYi1R7OJx7cTJcVy1yBwfJa5IcPJwjEiQI3CXV
3AGOvhz+
-----END CERTIFICATE-----
cors:
allowed_origins:
- https://google.com
- https://example.com
clients:
- id: abc
secret: '123'
consent_mode: explicit
...

View File

@ -0,0 +1,9 @@
-----BEGIN CERTIFICATE-----
MIIBRzCB9qADAgECAhB51uvUDHkaxlSEs8cgoYBRMAoGCCqGSM49BAMCMBMxETAP
BgNVBAoTCEF1dGhlbGlhMCAXDTIzMDQxNzEzMTIwMloYDzIxMDAwMTAxMDAwMDAw
WjATMREwDwYDVQQKEwhBdXRoZWxpYTBOMBAGByqGSM49AgEGBSuBBAAhAzoABJa4
oEFZqEbmsnKWXEfNWTiqyEq6YiWbVFIH/PGijaRmsYpKC2UBGsscN4DziAUHBlqX
KLA/lsRjozUwMzAOBgNVHQ8BAf8EBAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUHAwEw
DAYDVR0TAQH/BAIwADAKBggqhkjOPQQDAgNAADA9Ah0Aq03epx31NN1fTorB/rrz
Muu9Taw8YxaZxvjLaQIcbNHGY5bFYxi04ahvN1rYi2sJEn66SaWut+lBIw==
-----END CERTIFICATE-----

View File

@ -0,0 +1,5 @@
-----BEGIN EC PRIVATE KEY-----
MGgCAQEEHM0126u3fW5scirH39HU9FgPTZOHPxg2NgbSQQ+gBwYFK4EEACGhPAM6
AASWuKBBWahG5rJyllxHzVk4qshKumIlm1RSB/zxoo2kZrGKSgtlARrLHDeA84gF
BwZalyiwP5bEYw==
-----END EC PRIVATE KEY-----

View File

@ -0,0 +1,10 @@
-----BEGIN CERTIFICATE-----
MIIBWzCCAQKgAwIBAgIRANqK3vKflYMr/2HGVd3aOR0wCgYIKoZIzj0EAwIwEzER
MA8GA1UEChMIQXV0aGVsaWEwIBcNMjMwNDE3MTMxNjI5WhgPMjEwMDAxMDEwMDAw
MDBaMBMxETAPBgNVBAoTCEF1dGhlbGlhMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcD
QgAEnnBdDSXbTgHtrc5vcJ2xz6qyGXM8PJgENjgQgn5WFVQCSZnKp08+mzeDiHrM
67KmISfxSAjoeCJV+dP6JfxIVqM1MDMwDgYDVR0PAQH/BAQDAgWgMBMGA1UdJQQM
MAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwCgYIKoZIzj0EAwIDRwAwRAIgOo+m
1yQsTmqOaKak9MY2q7CdBI9Di8vPK/sE/x5JIPYCIA/lyI/sG1EEdLT8g3M4Joc3
VK7cBHjmftnZL6kiS+Dn
-----END CERTIFICATE-----

View File

@ -0,0 +1,5 @@
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIHL87FDsqijXFhRJ5VgYiOz2ko6xxP7aP7i4v3Eowf4KoAoGCCqGSM49
AwEHoUQDQgAEnnBdDSXbTgHtrc5vcJ2xz6qyGXM8PJgENjgQgn5WFVQCSZnKp08+
mzeDiHrM67KmISfxSAjoeCJV+dP6JfxIVg==
-----END EC PRIVATE KEY-----

View File

@ -0,0 +1,10 @@
-----BEGIN CERTIFICATE-----
MIIBXTCCAQKgAwIBAgIRAPtITpvhty9gwPvrPO1J8GYwCgYIKoZIzj0EAwIwEzER
MA8GA1UEChMIQXV0aGVsaWEwIBcNMjMwNDE4MDU0NTM5WhgPMjEwMDAxMDEwMDAw
MDBaMBMxETAPBgNVBAoTCEF1dGhlbGlhMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcD
QgAEW3aPMQmzoU84DWr4UbbH8tWPMCuzLC44450JvNa8ChDto0ST+koT1Xtq75cu
JSAlxn3QeMWZ0pjlvt/woj4Y/qM1MDMwDgYDVR0PAQH/BAQDAgWgMBMGA1UdJQQM
MAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwCgYIKoZIzj0EAwIDSQAwRgIhAOI6
pgq2zjf5wr1bW21HXsUmwrbyfiCz5vSlAk76QgkRAiEA7Txu5kjdEhnFUw3ORQIF
enG1sLX3iZOljfTsHTG1kug=
-----END CERTIFICATE-----

View File

@ -0,0 +1,5 @@
-----BEGIN PRIVATE KEY-----
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgKwZ0IZaf/E54bRh6
b9AEwZJ368O9uoJaJ4tloCjWuDOhRANCAARbdo8xCbOhTzgNavhRtsfy1Y8wK7Ms
LjjjnQm81rwKEO2jRJP6ShPVe2rvly4lICXGfdB4xZnSmOW+3/CiPhj+
-----END PRIVATE KEY-----

View File

@ -0,0 +1,11 @@
-----BEGIN CERTIFICATE-----
MIIBmDCCAR6gAwIBAgIQWFgOoTSBNa4F1A+Uk5fBhTAKBggqhkjOPQQDAjATMREw
DwYDVQQKEwhBdXRoZWxpYTAgFw0yMzA0MTcxMzE1MDBaGA8yMTAwMDEwMTAwMDAw
MFowEzERMA8GA1UEChMIQXV0aGVsaWEwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAARq
Fk2dSauZd2mW0ZXuxZ0k2a5PInZOs3wbzjJr67RPzmPMNGt5dVHtbOTLr9MAcm21
E6/4CLQZ+wMq4Zxuhoa02VN4lQBFOzWFPwVTa0lcOUCkJ7E7JWXiZjX80ROyqDOj
NTAzMA4GA1UdDwEB/wQEAwIFoDATBgNVHSUEDDAKBggrBgEFBQcDATAMBgNVHRMB
Af8EAjAAMAoGCCqGSM49BAMCA2gAMGUCMQCJHEN22ouKJr0usue9/bUsJltPrgSW
v7NjjQ9hY96JAwBQpTxX6EksQdnl44Q/LLACMHBZn3weWvq8frMOAmAvOomMsnMp
H7tweTJNXh4V8XdtR2GGxAAYbq/ShyxrpQ6LVA==
-----END CERTIFICATE-----

View File

@ -0,0 +1,6 @@
-----BEGIN EC PRIVATE KEY-----
MIGkAgEBBDBd2neGG9Ax14sDR0V0TYSXIBxNWZwYr7OAFd57MRUZ/+BkHvQEMOoV
umd/tOgGjEagBwYFK4EEACKhZANiAARqFk2dSauZd2mW0ZXuxZ0k2a5PInZOs3wb
zjJr67RPzmPMNGt5dVHtbOTLr9MAcm21E6/4CLQZ+wMq4Zxuhoa02VN4lQBFOzWF
PwVTa0lcOUCkJ7E7JWXiZjX80ROyqDM=
-----END EC PRIVATE KEY-----

View File

@ -0,0 +1,11 @@
-----BEGIN CERTIFICATE-----
MIIBmDCCAR6gAwIBAgIQfhi2ThPJEF21jbDOB9z24TAKBggqhkjOPQQDAjATMREw
DwYDVQQKEwhBdXRoZWxpYTAgFw0yMzA0MTgwNTQ0NTRaGA8yMTAwMDEwMTAwMDAw
MFowEzERMA8GA1UEChMIQXV0aGVsaWEwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAATV
Q1Vm2hMPPv0zUA9+jphIUa4pxviHz0wrk2GlpwBvzP/tbF1aRY7MRH+8d/JIKF7p
9wuVfOYB0mE7/fpzI33baVb8Js35IRax8EIRxsDvVevE5kcheddIGIyJ0FC3yNyj
NTAzMA4GA1UdDwEB/wQEAwIFoDATBgNVHSUEDDAKBggrBgEFBQcDATAMBgNVHRMB
Af8EAjAAMAoGCCqGSM49BAMCA2gAMGUCMQCT6hC9cNxVkvJb1ddNOg1E4TUg3veD
pnLvWm9iifH5dbep1Vk8idK8XvKyKfyM5F8CMF7MTdv21GjKF3Fl01zORgE9cxlo
zLNWQbB/rLpgGRkB2Emd/dCZUQwgD9iDd2eifQ==
-----END CERTIFICATE-----

View File

@ -0,0 +1,6 @@
-----BEGIN PRIVATE KEY-----
MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDDQSUy5MggN3tt+A4RV
lCcLGFICUBO27VexEuZuY75e+xYRUeDISXSlnqwQVa42Qk+hZANiAATVQ1Vm2hMP
Pv0zUA9+jphIUa4pxviHz0wrk2GlpwBvzP/tbF1aRY7MRH+8d/JIKF7p9wuVfOYB
0mE7/fpzI33baVb8Js35IRax8EIRxsDvVevE5kcheddIGIyJ0FC3yNw=
-----END PRIVATE KEY-----

View File

@ -0,0 +1,13 @@
-----BEGIN CERTIFICATE-----
MIIB5DCCAUWgAwIBAgIRAIpQUsZLrSAJ7+PY4U0MIaYwCgYIKoZIzj0EAwIwEzER
MA8GA1UEChMIQXV0aGVsaWEwIBcNMjMwNDE3MTMxNTM2WhgPMjEwMDAxMDEwMDAw
MDBaMBMxETAPBgNVBAoTCEF1dGhlbGlhMIGbMBAGByqGSM49AgEGBSuBBAAjA4GG
AAQAO8GuJvWACDYuO1ZhMdbrINK8AM8B2xFn5nSvAHAgYolyXz8yxLjmFT1/ifQZ
QjnocX4j/zOGIt1f1OXQvPSRaiQAzWlFIejCKChBK0hiDqfTyzDgrJGiCobL1bgr
yxO3oDg70YeN3mr0OkMvdrIBjpGpGkt5AX6XyaIau9ogZJz6gyOjNTAzMA4GA1Ud
DwEB/wQEAwIFoDATBgNVHSUEDDAKBggrBgEFBQcDATAMBgNVHRMBAf8EAjAAMAoG
CCqGSM49BAMCA4GMADCBiAJCAKaUek+/zGw7Tt3L5rqQhXFzWI4mfci+jD99/JHY
UT/FX2Co/tjEcyty46mMWsw6E7q6XCJ6gx38SCWe7wRdMXQMAkIA6rlkntqo6r+j
PoTtPVmbkFFc5ficw+xlmhuKblKrq+u8sbnm+J62C8pXuzSc8dtKEe0+oORD5HH9
YGuoIKNL2vg=
-----END CERTIFICATE-----

View File

@ -0,0 +1,7 @@
-----BEGIN EC PRIVATE KEY-----
MIHcAgEBBEIAe0mKO82UiFUDM3M3CgyEKkXuXnt0m2DAnW3Yf2nadim00n/XsGw7
+ID6Zz5Xhazpx7WFNNhtrjbNQOKbsQNndPugBwYFK4EEACOhgYkDgYYABAA7wa4m
9YAINi47VmEx1usg0rwAzwHbEWfmdK8AcCBiiXJfPzLEuOYVPX+J9BlCOehxfiP/
M4Yi3V/U5dC89JFqJADNaUUh6MIoKEErSGIOp9PLMOCskaIKhsvVuCvLE7egODvR
h43eavQ6Qy92sgGOkakaS3kBfpfJohq72iBknPqDIw==
-----END EC PRIVATE KEY-----

View File

@ -0,0 +1,13 @@
-----BEGIN CERTIFICATE-----
MIIB4zCCAUSgAwIBAgIQLyw4qjgh0+UtQ4W4Apd+7zAKBggqhkjOPQQDAjATMREw
DwYDVQQKEwhBdXRoZWxpYTAgFw0yMzA0MTgwNTM4NTdaGA8yMTAwMDEwMTAwMDAw
MFowEzERMA8GA1UEChMIQXV0aGVsaWEwgZswEAYHKoZIzj0CAQYFK4EEACMDgYYA
BABUNIHCT8zGTz/gYHp5vA5DvYr1CbUuncCF2uP+Yoy56p06hD4oQKgo9K9gK03q
DCZTs0rAamxgO+PgZW3D1VZlDQAehJniclY/0Fpsc+qMrfZ5O269fTvwutUR7L/S
LXifAaW8lHcwYjgpEsaPdZUyxzKZW//usTrCHhkwSy7LXZzQ56M1MDMwDgYDVR0P
AQH/BAQDAgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwCgYI
KoZIzj0EAwIDgYwAMIGIAkIBznnm//FcYd+WRw19hRvyC9XCjmHW+M11B3kqpApv
iHueaoBIPvSZueS9fjjsNZG8qpLRVgWfVVPV8+L01z9DWrUCQgD/0sIlL4Cf5W4L
JasSbXARkm+jBj+QH5ZKA0hgpoL32I98TIEUATWSBvygXigHaD7ZEZEbxLyCL32a
uL3TmO+2wA==
-----END CERTIFICATE-----

View File

@ -0,0 +1,8 @@
-----BEGIN PRIVATE KEY-----
MIHuAgEAMBAGByqGSM49AgEGBSuBBAAjBIHWMIHTAgEBBEIB1bhgd0P7DBnlzyL3
S2YcOXhRxq4KkcTcw3zEX+jn6XG2cyh16u9gLv9PJ6fHLVdFHQxrVGC6/fxH9p9S
B6B57/OhgYkDgYYABABUNIHCT8zGTz/gYHp5vA5DvYr1CbUuncCF2uP+Yoy56p06
hD4oQKgo9K9gK03qDCZTs0rAamxgO+PgZW3D1VZlDQAehJniclY/0Fpsc+qMrfZ5
O269fTvwutUR7L/SLXifAaW8lHcwYjgpEsaPdZUyxzKZW//usTrCHhkwSy7LXZzQ
5w==
-----END PRIVATE KEY-----

View File

@ -0,0 +1,8 @@
-----BEGIN CERTIFICATE-----
MIIBGjCBzaADAgECAhAiL/zLZb4EevlhMjiuV5DZMAUGAytlcDATMREwDwYDVQQK
EwhBdXRoZWxpYTAgFw0yMzA0MTgwNTQ4NDNaGA8yMTAwMDEwMTAwMDAwMFowEzER
MA8GA1UEChMIQXV0aGVsaWEwKjAFBgMrZXADIQBjrmjS0+DbAzaJWN+8USL8V1qU
smG9mWH96wuA6NPA4aM1MDMwDgYDVR0PAQH/BAQDAgWgMBMGA1UdJQQMMAoGCCsG
AQUFBwMBMAwGA1UdEwEB/wQCMAAwBQYDK2VwA0EAMuUxDvbVjJwCYhal6H1pZxOh
lJd16Aj4C7j7qHLwmWWwREkCoLK/Su1b3982OuCWrOMYMxEx4yNdwsnrKpNTBg==
-----END CERTIFICATE-----

View File

@ -0,0 +1,3 @@
-----BEGIN PRIVATE KEY-----
MC4CAQAwBQYDK2VwBCIEIG1eby8XtRD2+eZMPFi2jvztZbBSr3wcYEeEl6Sj8k6N
-----END PRIVATE KEY-----

View File

@ -0,0 +1,13 @@
-----BEGIN CERTIFICATE-----
MIIB4zCCAUygAwIBAgIRANSysyC3vJlv86Ttmi8M97owDQYJKoZIhvcNAQELBQAw
EzERMA8GA1UEChMIQXV0aGVsaWEwIBcNMjMwNDE3MTM0MTM3WhgPMjEwMDAxMDEw
MDAwMDBaMBMxETAPBgNVBAoTCEF1dGhlbGlhMIGfMA0GCSqGSIb3DQEBAQUAA4GN
ADCBiQKBgQC4ntJ/qcs9yBQihZkrF5v2Pdp6Rr8uNc4GDjuOsVGUohpwcjVobAuj
AuvCG646cnekbkJOm1bY+38F+nfWJ7ny9RYMp1ng6xWR6vpzZiPyJI89FQU3gd8f
WDI5Xn2ZvrSqfgEJhXMAWn7EPgUajlbLoPzYFCKSChIpR9umk5DBnQIDAQABozUw
MzAOBgNVHQ8BAf8EBAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDAYDVR0TAQH/
BAIwADANBgkqhkiG9w0BAQsFAAOBgQBjpYkj+iE9XoA0q8Iq8+CYwlRwQ76jHKgy
z+0JCJE10ysuDPqRJEGJR3vfOs6VyNTGcvdCemPkTEYYAikaT4ydRNqIwefuHlx0
7Abr/GUkZpRdTNfitAZbN4HpHpxZhx/A4yNutwGLiZSzqsn1r1VxTymSkNLa680X
84rsVRZppA==
-----END CERTIFICATE-----

View File

@ -0,0 +1,15 @@
-----BEGIN RSA PRIVATE KEY-----
MIICWwIBAAKBgQC4ntJ/qcs9yBQihZkrF5v2Pdp6Rr8uNc4GDjuOsVGUohpwcjVo
bAujAuvCG646cnekbkJOm1bY+38F+nfWJ7ny9RYMp1ng6xWR6vpzZiPyJI89FQU3
gd8fWDI5Xn2ZvrSqfgEJhXMAWn7EPgUajlbLoPzYFCKSChIpR9umk5DBnQIDAQAB
AoGAHNF93jus5An1SqY8EIPw7nEdR3T/psDzVfKmzVFUgLUFF4RcXd5vupRcJMKZ
Ybo4fsxPQWHyHpCzdUVxq1YsKkK5qaAGjUfyHKP9yS/ZTKzA4BQJ+mOxagdZ79PB
dxrxtRWz58x++537TNGAUNziG7zaLOmdwlqul4bYjHt5FCkCQQDhPwFMbrpjT4oM
fDuy1bWS4t3X4VVBZfEQT3WeLu4qHzCnBbEszL/q3bXtKlbiqcWRwhrOvCqkmY8v
XBAb21yTAkEA0dPVHCcgXKWIytz7DOGEcBoO8ANw/918VoA9LW560pL5B1qzYAl+
7Ecl6zoZLJPVY1BQ+HVE5tLih84hmlp3DwJAZnHQdmHaFfcEE3+ha0n1plPWkCwl
KXRi+ocZOJOhsLi02RImrfiFxR2Hc9GQ6NBMUmnU5XgBcRGCZQjbLsBLTwJAEct7
SVXwIqtPPJUdHWyKxM8Q8T35eVmZT+S0S4QRGoaoY/1HNR/ZCcTG7HoS5HrtH+0R
0OBxJXpBB+9tXh/J9QJAJDVE0lcJWKeUl/W3P/pzCXZHdUtqoYUFuuuJyFnAAFFU
CKi6wGKnfsc0v01tVpooyThJ+4Z9eTotNGp6ke1tTA==
-----END RSA PRIVATE KEY-----

View File

@ -0,0 +1,18 @@
-----BEGIN CERTIFICATE-----
MIIC6DCCAdCgAwIBAgIRAKtA7n5MZorcd0TquNdXF74wDQYJKoZIhvcNAQELBQAw
EzERMA8GA1UEChMIQXV0aGVsaWEwIBcNMjMwNDE3MTM0NDI4WhgPMjEwMDAxMDEw
MDAwMDBaMBMxETAPBgNVBAoTCEF1dGhlbGlhMIIBIjANBgkqhkiG9w0BAQEFAAOC
AQ8AMIIBCgKCAQEAugGNhIscNEpZtxdrTuRAyeGjSERuue56uF0lMDvh8YDi0alQ
y4q80FmIwfP9lWmji9IkMvf1iD0M2I55WBPrL9ddqtpdkIfLgz6eX791LKtF9n8u
PwuX2jUDtWdJrMlOIJ8wCcTjyCFzjTFujAtXGffjWt4tlKCWZZUkJqmyfBiQIag6
ZFb1S6VXFXFpWTOIc41X2VBmzpSLnfqEDqgp/KMDja1tDYAhFh3IAFSzBpXqUGT4
cH2wJUcrngdaiHR/2ToJRu66jK8akB35gjmKiF/Tp9pUI7/rBgPeFWDCZuuZa3k3
brfjJkynQAoCNajfxy8cglCxAuG+jWubFDPGewIDAQABozUwMzAOBgNVHQ8BAf8E
BAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADANBgkqhkiG
9w0BAQsFAAOCAQEAoIXTZcKC13KW1GNhzx9ECFTs/gatfjkYONRz+M2wjpVGHzsn
JUPXhoT1SL9WdWYXVVCUXrQge9n9n6IccDjwddnoWL2JMXnNd2PAJLgwE2Xfd40o
U1CLTwvsVNCXjoQLyjkg+SbWGqApVS3oj+A8RTtSBbztP+CoOqbyD3Roo1sFHeE8
PXYboT5bIIaU7DaxhItGHwVDLLOSD72FP/5i+ZmFse2EzUUdyi6d4FSjk7pZCX1T
/2w/bqk3zRemBqDwTnH+sMhPUPvcOg6AIR5YdjWYSDz45sDdgBpUZYYTPfSz2nUL
PJwsB/gk0asMwSYprat6sJ0X3xrtg1ak3a7LkQ==
-----END CERTIFICATE-----

View File

@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAugGNhIscNEpZtxdrTuRAyeGjSERuue56uF0lMDvh8YDi0alQ
y4q80FmIwfP9lWmji9IkMvf1iD0M2I55WBPrL9ddqtpdkIfLgz6eX791LKtF9n8u
PwuX2jUDtWdJrMlOIJ8wCcTjyCFzjTFujAtXGffjWt4tlKCWZZUkJqmyfBiQIag6
ZFb1S6VXFXFpWTOIc41X2VBmzpSLnfqEDqgp/KMDja1tDYAhFh3IAFSzBpXqUGT4
cH2wJUcrngdaiHR/2ToJRu66jK8akB35gjmKiF/Tp9pUI7/rBgPeFWDCZuuZa3k3
brfjJkynQAoCNajfxy8cglCxAuG+jWubFDPGewIDAQABAoIBAD7IkWUAs4du5TNo
wz7AyqGZ+MxG1P0LYv7h6dCLFeu3blgIh438iVjmL8QPwDNzkdF7H97YVVckDDb4
eDrjlknyrtohlN1ZCLeHJlv5OurV8OqP6SM8nYf4xwSvFW4uEKHwOX3CqIP/zooE
+mRo24CXbHVacxYs0jb9jVNDikxaRh57aL9wqvg8/SUhe2T2VXzTO9z0ZYNuIyF/
EkOLkRM1FY7o3zHuYeRN2Y/QbS7yOT4aIodHBtwvObWs8g6Sq8cSbss72x22fY8J
uPwDASy93EOfn7phx9rGq6ioy5XpZtiGHPs+dThftBoOUCq3x2PxdVjGRwnZ1LRd
kEhWm3kCgYEA8LelRt6go8tqUGj82Y+RkKlbviO7+P8Hol1B1fV+wIlLK6CRm6Mb
3PB7fRdBsxcuBFf8WfVdfIkSHEi2uG+Ehb54AySTdsgRYHSQtSYMvKyKxHcsEJ6x
5uNK425N6wVS86MGoIK5li6jpVwdnA8bE6cvEMBRMXlTTIzvwM1+AV8CgYEAxdCw
qqaS6aYBH5Ol0hUi3wW6HaLUOVGksrn3ZTfAGsgoX/mgF8qinz1NmuSzKoevvVK9
q9TC+GDrVNQcD7s2kQQD3Ni8Jb3ok360HcqRN+n8O5+v/t7OjHSsqSRmnQX/lvWO
+65r442ziHXrhFT2CwmY3zBHXe2+MBrGHmrgRGUCgYEAkRoSbdrrOHD44Am5SSfq
1inQnJgLyjdpEa1nbyLxyfu4rU64FvpGZHMt7SSkvODfI00qV8u5E8XIffYy9pB6
cOh0jWhx36sQFnWNeTS7fsv/RhiUHlya3pPqY5ftLhtiemyuJPlIB8iLarVRP+43
IyynCVD0YH9DACUArNbx+r8CgYBpd9EZy2I9DPNAYLpifj5vZmBK+MvqG6uSVzCe
WNEl9l4AfdlrlfCKsma0FQepv1pluL3D5dZmE1aljcnAYXLAcsGUeEIoZU6hhUaH
M7+lbi27pHJzk1vQ60w7ilrjkZUqaZZofiCr3JtCQIznq1zbmaxWIymJ3P4wK7ZB
9X3JOQKBgQDHPBrRVP5Od7dd/C+yi7p2CujuLbV4vKczpNf7Kj+OwQZtnZxjzi95
ObquHDkrz5+OhkvZwKNKO89UQzvhT+7gpQpZJ/gdVl1oNuZ5gAsJ4aDWX+NZ00Z0
Nkb2HpYR8xlnt6rFV465SYun1CIz5h1sUD1T75LDK340SrMbQq2Ilw==
-----END RSA PRIVATE KEY-----

View File

@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEogIBAAKCAQEAwwT+q3AlxKeidlD706fdK9G5d+R0vK8D+eThGULFyzQfC4A3
zmG92yWtrLcux1jsFBunjHCU7CnJTMazgMSl9K0oDIv5vZoIau+aq80Y+jP3CfyR
guqzQRwVdLZ3Z2rgFISsdhBTFXAyPpaZUfPjqlCXb5DMLoWwva8feA9PD9YOsuhV
A2aWbBYdYL0XlFP+dOCGbhB6Y+Kr3OXhXIYnFbX1bf8Jr1y+bvyf4B3zasl7awRx
lRn1kI2zpNIWFL1Srpj9NlwkcsW2ne03+JwgJtfaCRBTkZMOh1sgGC4Xjby7KrBY
21YLkpHhDJadg9NEdhD+St4MfdAQKkG3e/UWcQIDAQABAoIBABfi1bpzyvxyN9Dc
DGwZJFrInjnUDoRJv2ftI7DvX8CKyr6i3rL1f8aGr+X2rdEW0BuKY5Qs+eCPIau4
rqW38EeuqbgXsOgLJLrMTBp8zXFfygM8Hyp0yq3P3cTk0G0nRvjcYy82wqZejpjh
4zeJcroakuHET23nTAV/nJAc9+cNbwwE41wIJ3t+xd/gECWc8LRWiwGjQJSaBVSK
D6/fKKqBMdk9QYeqM1Z/KGZW4PUw1IDgxbUXFWGAU5Vl1qYckDRVmVfrZOzyl1yM
nZ3cdTMACJ+i+T0SGUWlsZ3l8cmNryGnxE65tnQsRs6+r48/bwFh/2MgIDwy5Jle
IG36wMECgYEA12rREd1qAEDBVe9Vfz3RG//SedDhGtBcZiyZu6ndfioAW9glxRCD
j+ICiTVH6Uipz8FWIn8pHkr0uosgE1fDEW2LIH6/FAHalNhGSQc7omSfOrxgUf7H
fH/YS9sAErRDMhmPPdWj6pYO9MWG8FNiEsTspAEBLh8JFJnOzVkGv3kCgYEA58Ju
mnjj8wTkib4Tbi/CX0ZrnEiI0xAsLbAPzLhnhLvPvYvMnThSsRBbjg51fe6n/P9m
FgC1Bxd9kyY3MRRS7/7iPmMTQyOYTC83brBAFjCHiTVrQAyU1QsVhofCEsVFl8Iq
xSb9pO3A/HJCeKokxZ1WNTGbs3bylgK6XCX0eLkCgYAn1NJvsTcmcNLO5wAyFOYT
fUwXxi25XYmYQuryLkiMSYvjb6YcOB97fVjmsfloA02S4rbgjg62UTnLPGpj6Thi
gpTVH1qJgoY+O3dTjYjTUDO5Epfk0W1lceY2sHnk+3vpSZyY3GYAvFprnBKFYYWi
3tK6yIzFUGvHaWE5yxpaUQKBgAJ2/egkqv/1qFySqfA9D8slm5Vg7Buai2289p4N
xAQUX0Q7zWRKqg56Bw8+th8tv5mgOby4KzS1Gj/LY0DhX3Rv+IYDVbwcD82XnvZN
Z5EU2QwrhkV7HMYbRRJWNUkv0eqoRP4tyPrNEIVezbgszxFO+BC+w1IoTLItuz+p
o6z5AoGAeFRJrSzapMU9RSXdIxkoT6Hsalunbqw1yTedlCpqGggFujfX/GCToWB7
PZeWUOWijK818+IR5YJbvFHx1q/4zf5CCfOHHp1TmmA9bR/622oQF6/jCrTnMTqK
0BpcWXXzh241EzE1rXpLHxnywCygxEGELRFsCR5YVFxdwPf1ZgM=
-----END RSA PRIVATE KEY-----

View File

@ -0,0 +1,8 @@
-----BEGIN RSA PUBLIC KEY-----
MIIBCgKCAQEAwwT+q3AlxKeidlD706fdK9G5d+R0vK8D+eThGULFyzQfC4A3zmG9
2yWtrLcux1jsFBunjHCU7CnJTMazgMSl9K0oDIv5vZoIau+aq80Y+jP3CfyRguqz
QRwVdLZ3Z2rgFISsdhBTFXAyPpaZUfPjqlCXb5DMLoWwva8feA9PD9YOsuhVA2aW
bBYdYL0XlFP+dOCGbhB6Y+Kr3OXhXIYnFbX1bf8Jr1y+bvyf4B3zasl7awRxlRn1
kI2zpNIWFL1Srpj9NlwkcsW2ne03+JwgJtfaCRBTkZMOh1sgGC4Xjby7KrBY21YL
kpHhDJadg9NEdhD+St4MfdAQKkG3e/UWcQIDAQAB
-----END RSA PUBLIC KEY-----

View File

@ -0,0 +1,18 @@
-----BEGIN CERTIFICATE-----
MIIC6DCCAdCgAwIBAgIRAIs/ORkJgOjf/C7gnnKyjicwDQYJKoZIhvcNAQELBQAw
EzERMA8GA1UEChMIQXV0aGVsaWEwIBcNMjMwNDE4MDU0NjUwWhgPMjEwMDAxMDEw
MDAwMDBaMBMxETAPBgNVBAoTCEF1dGhlbGlhMIIBIjANBgkqhkiG9w0BAQEFAAOC
AQ8AMIIBCgKCAQEA6ZPuDj+TItlSNL+rx4h2L/oxIVZzPVf/fD92b1zIILlMCAMh
MB1PbaUtxnFfkdT5OGnIQaF9aZtEk3Hf80YSVvOHSEhKKAtmN+Zd+kTvasJBZzHM
r82+ZhDKCAVkIj8+vlSSQ7plV8/m9EC4aUQE0qRCuTthqluLfLwJ+ceNnuTIuQnD
Qg10qG7+bycjPNFQuXDnU+voJ4PCQ5OVo1ZfN/u2KthtfenbqIx5Xi9JVFyA9Xik
ZDTm87OJahSi9bfbhi+3aJJOGpgOC3hEA+vNh0b6kbkbHXMaCCM26/3R/inECywd
r9FQqVyOm3eQIdPKNxwbqdMyWrgoQYSOvVliZwIDAQABozUwMzAOBgNVHQ8BAf8E
BAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADANBgkqhkiG
9w0BAQsFAAOCAQEAaDeYs35r0+bSjWQl4TlBdpcOBbceffQ4DY1OqXT7aogyGcr7
O8wzvf4bEHAkzGo0GdvaBOp/rbJEQOmpXQaGiTg1V/5rFfi9RG/7NhOCmto8RSee
66pYYJPfUR7nv/EnfJExGCz+vhroaHlqRJJfXJjT8XCzG5KnbUm8PL5QG4GounzZ
u0+seG+lTMaxHNdLxZ9Zp5ZTbXZjQ9F5/bdvuKJnM9wD1EW6JJw05Dl1hjl2Ak0E
X34dUe0XNWirfpjntjd5mt+QpcLYJi5tqjiskrMzhoGFKh4g9lXQA3qLX2aaPAt4
28ew+YkL7/qvP3MBE91bJ/IWtYX+3UCMke2+dw==
-----END CERTIFICATE-----

View File

@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDpk+4OP5Mi2VI0
v6vHiHYv+jEhVnM9V/98P3ZvXMgguUwIAyEwHU9tpS3GcV+R1Pk4achBoX1pm0ST
cd/zRhJW84dISEooC2Y35l36RO9qwkFnMcyvzb5mEMoIBWQiPz6+VJJDumVXz+b0
QLhpRATSpEK5O2GqW4t8vAn5x42e5Mi5CcNCDXSobv5vJyM80VC5cOdT6+gng8JD
k5WjVl83+7Yq2G196duojHleL0lUXID1eKRkNObzs4lqFKL1t9uGL7dokk4amA4L
eEQD682HRvqRuRsdcxoIIzbr/dH+KcQLLB2v0VCpXI6bd5Ah08o3HBup0zJauChB
hI69WWJnAgMBAAECggEASCEkbEX5m9NcbWmbFFzxklNChLb7kz/vZ2D5o94U9vYB
op/EyeTjOEq/3f34s0H/TApuisXhwpDuFlIeBDPpSeyeJBYewEr03+JFtxk+jcs7
AzD/snJoj4Azw1JW37SEHaZkHIIc9YcAHQE3cVpN2vZanHTX7hGi+3vd4MJc22nX
8XD/KRrosulsaFbWho2H9K1g7p3BkRQASuNm6caGVM6uvon+GqrylFgGHYAgzFXW
5ZEbybLN3yjSHag3F6LKw1LLxZ1/amy2Zz8nt5DzPajJEATFyeMGVnGXV2gv0BT+
2Xj6U46cvHIjQtfmWCm+vWTK9cexaAjgRNZhwMwOAQKBgQDtL9kJ3Pp41g90idiO
p2mNCKh5000b3W+7HPhi2MkuPEIwzotd4Yzxm6DYxTy1UlQ3v1TnCcxGxXHSqXRL
Iq07eK+KQjxfJmTJIc5BQgiGDczj4kxrPOjIj5uGwhgpMJRyquAN+opJjOMV+FIh
rQx5XQ7ZljtGvUvEMqrHPpdD5wKBgQD8GsyVV7CsqeSyEs3SubvggOAJXoivNdg0
Mmy6DL2ctvMH9qp038V1gdgxIuZN7Iul1qLTDKhVeS7zg7PJ2T2/06XIM6bz8FPx
6E21QfFPVbQw73VQ/2uGzsZoLPBe5sVSz/GLVlgM3bJfTPadB92RRhWNO4HEz08x
KQBnPjkdgQKBgEu2UlnLqEiaTCSvO+mNlyvl76GzyZFzhg78mG01NkMECTz7MZGu
Rgd53kZT76URusBw2vFFN2f/7u2IGg9B6nppc992dT8KSnHJ0MUUBxSDozu7KRmy
P7yF2ueRXZUIZNqVoR/VMf94caS5t734N1smUW8zfYh/NIhUhB9F45NRAoGAT7Nt
QD2T1lJzwbReK6OaePRlX7DqR3IfYwkaBIuomlXgaYEbDI3+EBM3tPkSlEoXMBu2
KEDVKwh/xm65tTOf6PhRbgSeYHp3H4BQqOArGOjAacQac7v8U4clhKPIbkhI09B3
zZRDi/W+wZBEWwq0iov8nkTU1tKvd5w9y2YJioECgYEA4xyF1+QV4stitX59NsES
IG/PuqxjmGtvzOLgMhfDCm/yewtAWLqXNoIoL3ogbilC/n/J+70kKOjXRwAwXBae
7JBKm3hJQm9uDYG3VSVkR4+SWpZbvSNSBdndObE9Uj0kc9+FQh7DMggSgkCP6UUl
/oyZBYhR3oBq6TyHZSS03/g=
-----END PRIVATE KEY-----

View File

@ -0,0 +1,29 @@
-----BEGIN CERTIFICATE-----
MIIE5zCCAs+gAwIBAgIQFaK8pCAGUonNI4J2+aHgGzANBgkqhkiG9w0BAQsFADAT
MREwDwYDVQQKEwhBdXRoZWxpYTAgFw0yMzA0MTcxMzQ1MTBaGA8yMTAwMDEwMTAw
MDAwMFowEzERMA8GA1UEChMIQXV0aGVsaWEwggIiMA0GCSqGSIb3DQEBAQUAA4IC
DwAwggIKAoICAQCjdNqfv8DzXBfR4XXsskYFcUYSLx17BrQ1hQqyLCTaAluH80OE
7HyZbbklKyvw4ig5Rk8Slq1pv6JBK3dEsOWWth2BNZEQyyGXa/aa77Zj6FxdDRHb
H/retzJzxTaaiu+F50OU0PkE5clj/V5JsgKwm70GwOy6zLkGnlv0k0I8HzTYJd1u
9qfKLZpmkJ0oiR4TogEgZA+9atCxzAFbcrynEnStIrepxvad9oOlzFLxArFe5Ai8
IsT3hgIo1SHFSVMhPdfsXVt1nIo5h2Ol2Ry932sIIDypNc70KsYgzQ4jC/6iRni4
saKoUp9IIDCRl+zjlLM1csiufhzC8U0g+UvWFkzigTW4J+CneQk9nnb5BtfCAiir
6WjOicQJff9EuvQFYASljQypH8hunKcH9YWtT/DGRThpWRgDMMalHnEprC4uSrYy
1QajLCi0ncJIArW3SdyePc7tRebNIxY3/Phj5kMwfV+ypIso5nJyu41AQVaRT7U5
+YHydvg3FGOa7JDUm1a27BQgpocz5yU5aazbffmPz/eRPqMa/YRsmzBLnuhkR2Fo
6aRoU0a3zQe9LcrP8gdxr2ZQqZYzdVJ2feywaeH6RN6jl3S2IlH/j71G0dyi3nSN
nC4pe7CHH/wtE6NYCzoPcpZIcDqWt1aCFKYCK1MacmWSycxzv0dzJo+CnwIDAQAB
ozUwMzAOBgNVHQ8BAf8EBAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDAYDVR0T
AQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAgEAL65NXfJDjTF/GeoB6s8V3fvuwviu
RVQZsbKO4i7nCgs7lFJbXDkW+ybXhq1fmOeCnD1BaF5wwBNB4rgb70PT2eGpKbC7
7U4sgghU7CYSS5sc3dP2xAS1NaCrTAa3m8tlzPqhkR1VkZ9X16fG7kTp/CPXMKMT
zoBj+v3Iv3Gf+gSJu6a2PmvAPuV4w78PH9Lz2sZ7FkZYOYhzNgUS++vL2k53DIW9
6mIqMhm6y9yzsxJx9FXKBWqNuqpL3Gp5KL0XWFy4JIbnpX1b0J4C8yAvF5QS8viO
3VFFcgGB5VWS6vMDAp/c6O+9Rzg0ZbfnLYAHxeSMGZ/Zkf/TnHNjKASFmEu912iH
c6ulT8hVxwTxi/P8eereFdsMib8Z0z871e/2KGZN9bwycVIsqZIllTM3vqcwc9wi
uu6eoScqx25qut2G7K2aQxtfPHmPfyh7/Ft3jZra3apzEuRE3KRBVWaSbDI2SjoP
LESJpBtFPnKOt2p9p/70iODv2lrfoMpj4eXXztJAJFUi4KkczomrU1WtJDc8J5Pp
9tBiNFR1bBKE4+9kwY+6x8LMJs94XjlbG7stoPki41qGR/8Th+n33GcIF4n9Up9l
2XR5/Iqewj2FJAkiYcalFasScU/hLTyjJzpYMOtAvVbBgvYm8IQ4Q5VBkQPPe6a4
P+3smf8j9ywptqk=
-----END CERTIFICATE-----

View File

@ -0,0 +1,51 @@
-----BEGIN RSA PRIVATE KEY-----
MIIJJwIBAAKCAgEAo3Tan7/A81wX0eF17LJGBXFGEi8dewa0NYUKsiwk2gJbh/ND
hOx8mW25JSsr8OIoOUZPEpatab+iQSt3RLDllrYdgTWREMshl2v2mu+2Y+hcXQ0R
2x/63rcyc8U2morvhedDlND5BOXJY/1eSbICsJu9BsDsusy5Bp5b9JNCPB802CXd
bvanyi2aZpCdKIkeE6IBIGQPvWrQscwBW3K8pxJ0rSK3qcb2nfaDpcxS8QKxXuQI
vCLE94YCKNUhxUlTIT3X7F1bdZyKOYdjpdkcvd9rCCA8qTXO9CrGIM0OIwv+okZ4
uLGiqFKfSCAwkZfs45SzNXLIrn4cwvFNIPlL1hZM4oE1uCfgp3kJPZ52+QbXwgIo
q+lozonECX3/RLr0BWAEpY0MqR/IbpynB/WFrU/wxkU4aVkYAzDGpR5xKawuLkq2
MtUGoywotJ3CSAK1t0ncnj3O7UXmzSMWN/z4Y+ZDMH1fsqSLKOZycruNQEFWkU+1
OfmB8nb4NxRjmuyQ1JtWtuwUIKaHM+clOWms2335j8/3kT6jGv2EbJswS57oZEdh
aOmkaFNGt80HvS3Kz/IHca9mUKmWM3VSdn3ssGnh+kTeo5d0tiJR/4+9RtHcot50
jZwuKXuwhx/8LROjWAs6D3KWSHA6lrdWghSmAitTGnJlksnMc79HcyaPgp8CAwEA
AQKCAgAO31QBEwZwXiHAs/3x0mqylhLlFqpdBkghUoCdo4ya1XoUjZrIHmhb4XLm
Id52pW05gN8y9sjChXAy88x/UIUjSGC43/HaEFF3IJiokkULJBo7UTQdtvQxjYOm
qvwD5b5Tda5dfQIbYvkHAwewNuUtwo3ZbnZbrMLtCj2drERrif9Z52AVd5XevHV+
/Yt/I7K74JKvqssP1gc1FjXNZ0wo+3HoSu9hIDxSNRrXXBbz3OXcl20ACT3Ys7XA
l1viQoCw1pqt4/StZ9ff0iTL80w9LnXjoGNEliPFbZrnYyD1KWM6yqSzUV5WaGYb
vuoMZUFll6MSquX9knX1etUkueofZE631DEtXagjdgoPwShxHzJM6dPqrelm79u0
FWrptMCKActwGnFMOow8jDBnJ/ns7g95fKWthCZgyHhJHhs5EpNFEc1WTRuV+/Yb
564oJlgwTrSsNdBtdtmNLVFX2YqMXjeFkJuiJDFHEFpV8fMmHgVko0ihpFrohSza
ftfwCQKkd/L4huN4hMfZYwVd9rdeUqsJGXASnDu0f4Q1Bq6H9zQ//A+wBkh70Sq+
2cYsW/GryI4h/Q5677xtKUA9IDMIz2opBBewjCJH+BRPUobcuyq/xWod217mo8gn
mHN5/8ysFPWJH42wL3aO20L0XUrps4n9ni5qW7sbiGg/VZCQyQKCAQEA2WpR4Csw
HOkhfnvay7NOEAiud/MLPLNPVzQioR+SdOqWuQ81Dx1iZf6ZkMJzKi7l/I9txG39
QAh3aEUXStpjcmQygn+LS9zmZsaACnii/kBqNEJ7EVDkP/kL9qecgAUwSPB9xD8P
1SpcUVqcXfDpN9jQduLTHHcXgs5vUIUwFcC3OJKTMwWbSxMW2WmBsWrI/QqjprI3
sFKHKOPs+RRALUF44L8jMnODzwbMEK1Bui+eFiNOI5QbU2cd/uVkKe+PZTMexziX
cBUa7gzrE2dDccLkVup8dRsXjZJlu2ET8VZYncLidjBzAiTy7E8hZwuHzzovvppr
c2PHvlX0JtrEvQKCAQEAwHcPEOCQP/cswnYgRSsI3uDCZN3PpiMQd1qKYiIQItKT
hBRPvYqzvDcsprSnqvcWqd6jlbvyhgZyi8xQt+Iyduib3scoOZ2w8P94B5YR3rT7
u+BVd/A4iUxU0f8FfZ6sbwE5Q76XZdWwvFuy+lloM8y4BFe//VicA0JO7Rt4lFN2
31WSGwa7nJuPbbIPCeY9hPQVkdgsVgSksW+TjuUxiRVUvcF60rAuErcDBnO4XcC6
f/qGlLAUAzmQQqI0WpfldAFlYCrZi5UuJJaA7/w7h0a9MQmPx4UCcg4D1IZiyt0U
psueRzFBAndsgVLd+/v5t8Tkrz3M3/Dsw9yDg8RwiwKCAQB3gD7cjiB145YrZXxP
dpCzs3HiME6+4Hf9oIRgN3BSnxaVRUyOsEIDebuCm76dMwXqmhNlYmdOqNipEUDK
PdtnZrd0jxJLcnGZkAWUu9YrFdDKRLhMPkAXAZaXzmzw2Ok/TiBym47iRdRUSw+j
euVVcvCyR95tyO+9UCZTBcH2UuTiTX5nDu/ahfWLLrjAgcdTfmORHmgJnHL6AL2h
8oWL2m7MaYK5GlEam8vSZsi3w7CKzoEGgUO7xfPwxLkXa7tPjpeePPbP/mm86pDT
K3EguFS1iVE7NNbvU8ZjBermPeWbYSEEgYDVbuWvCZd8ghP1zS+s/keNNwz1C12V
da2pAoIBAFsBsSkM1oi4ivykuJucPsSMyL7DN6XaXLXjJR5D9xdQNRq2NAJvLI/q
Ev383G92CMxoDzgFOCdxswYxpVVd6vjZAqMzzux3iSxb0Fjd+DMzpvjumdttxn39
jvoBOYpt1iFjFb3XyGUJx1k5jwbb8e7UdYrwJ0NXe+X6m7F4VOrmEIaIQt7urxXd
ZNO852mJ6jsM44okCsrdxTZ1iPN/oo2sfXaAn2AymIaW7SJG473JHSbYwnxaSgxA
Utt/MXxI6OGSq2nuuRFMiBYa6HsR7OAJbfpbCBaS6VYfFGaQ6PP91/8Ktxv4yUGu
UKtSEM9PFYR04KGQemjF1l7CzZkn8QMCggEAetZUlucrPeejwSaIJvxcyO59Vp73
I5Yp6wnIQ3SZ3SIyx3GRJNU6uB8S6GR9Zu6CzdHSjuU9oBdmd4WdRle9H+8hooq0
xWbtpZE90cXvx36Z1IqILSu1ZTJrdsCxTiU9vQmg23jRl5z8K2YnsN4ury7qiZBt
SPD051WfyTeyLG3A8gx9ugw3vyJwXgvE6d82BJvJoWS9IzuK7xGULD80zO/l18P+
9ixzCh3rTi46FWESKu58HYNtWdTb0NolrqKexAx+IXhPBc8Zj7+Ip5u5s6XDV0Ek
tk96Zvf7GMFfPqRP6gidXeouyTu3pdcR7bCT9bVC0AcT/4n/L2D99ftFYQ==
-----END RSA PRIVATE KEY-----

View File

@ -144,7 +144,7 @@ const (
const (
errFmtOIDCNoClientsConfigured = "identity_providers: oidc: option 'clients' must have one or " +
"more clients configured"
errFmtOIDCNoPrivateKey = "identity_providers: oidc: option 'issuer_private_key' is required"
errFmtOIDCNoPrivateKey = "identity_providers: oidc: option 'issuer_private_key' or `issuer_jwks` is required"
errFmtOIDCInvalidPrivateKeyBitSize = "identity_providers: oidc: option 'issuer_private_key' must be an RSA private key with %d bits or more but it only has %d bits"
errFmtOIDCInvalidPrivateKeyMalformedMissingPublicKey = "identity_providers: oidc: option 'issuer_private_key' must be a valid RSA private key but the provided data is missing the public key bits"
errFmtOIDCCertificateMismatch = "identity_providers: oidc: option 'issuer_private_key' does not appear to be the private key the certificate provided by option 'issuer_certificate_chain'"
@ -409,6 +409,7 @@ const (
attrOIDCRedirectURIs = "redirect_uris"
attrOIDCTokenAuthMethod = "token_endpoint_auth_method"
attrOIDCUsrSigAlg = "userinfo_signing_algorithm"
attrOIDCIDTokenSigAlg = "id_token_signing_alg"
attrOIDCPKCEChallengeMethod = "pkce_challenge_method"
)
@ -416,7 +417,6 @@ var (
validOIDCCORSEndpoints = []string{oidc.EndpointAuthorization, oidc.EndpointPushedAuthorizationRequest, oidc.EndpointToken, oidc.EndpointIntrospection, oidc.EndpointRevocation, oidc.EndpointUserinfo}
validOIDCClientScopes = []string{oidc.ScopeOpenID, oidc.ScopeEmail, oidc.ScopeProfile, oidc.ScopeGroups, oidc.ScopeOfflineAccess}
validOIDCClientUserinfoAlgorithms = []string{oidc.SigningAlgNone, oidc.SigningAlgRSAUsingSHA256}
validOIDCClientConsentModes = []string{auto, oidc.ClientConsentModeImplicit.String(), oidc.ClientConsentModeExplicit.String(), oidc.ClientConsentModePreConfigured.String()}
validOIDCClientResponseModes = []string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery, oidc.ResponseModeFragment}
validOIDCClientResponseTypes = []string{oidc.ResponseTypeAuthorizationCodeFlow, oidc.ResponseTypeImplicitFlowIDToken, oidc.ResponseTypeImplicitFlowToken, oidc.ResponseTypeImplicitFlowBoth, oidc.ResponseTypeHybridFlowIDToken, oidc.ResponseTypeHybridFlowToken, oidc.ResponseTypeHybridFlowBoth}
@ -428,6 +428,7 @@ var (
validOIDCClientTokenEndpointAuthMethods = []string{oidc.ClientAuthMethodNone, oidc.ClientAuthMethodClientSecretPost, oidc.ClientAuthMethodClientSecretBasic, oidc.ClientAuthMethodClientSecretJWT}
validOIDCClientTokenEndpointAuthMethodsConfidential = []string{oidc.ClientAuthMethodClientSecretPost, oidc.ClientAuthMethodClientSecretBasic}
validOIDCClientTokenEndpointAuthSigAlgs = []string{oidc.SigningAlgHMACUsingSHA256, oidc.SigningAlgHMACUsingSHA384, oidc.SigningAlgHMACUsingSHA512}
validOIDCIssuerJWKSigningAlgs = []string{oidc.SigningAlgRSAUsingSHA256, oidc.SigningAlgRSAPSSUsingSHA256, oidc.SigningAlgECDSAUsingP256AndSHA256, oidc.SigningAlgRSAUsingSHA384, oidc.SigningAlgRSAPSSUsingSHA384, oidc.SigningAlgECDSAUsingP384AndSHA384, oidc.SigningAlgRSAUsingSHA512, oidc.SigningAlgRSAPSSUsingSHA512, oidc.SigningAlgECDSAUsingP521AndSHA512}
)
var (

View File

@ -1,12 +1,19 @@
package validator
import (
"crypto"
"crypto/ecdsa"
"crypto/ed25519"
"crypto/rsa"
"fmt"
"net/url"
"sort"
"strconv"
"strings"
"time"
"gopkg.in/square/go-jose.v2"
"github.com/authelia/authelia/v4/internal/configuration/schema"
"github.com/authelia/authelia/v4/internal/oidc"
"github.com/authelia/authelia/v4/internal/utils"
@ -24,26 +31,9 @@ func validateOIDC(config *schema.OpenIDConnectConfiguration, val *schema.StructV
setOIDCDefaults(config)
switch {
case config.IssuerPrivateKey == nil:
val.Push(fmt.Errorf(errFmtOIDCNoPrivateKey))
default:
if config.IssuerCertificateChain.HasCertificates() {
if !config.IssuerCertificateChain.EqualKey(config.IssuerPrivateKey) {
val.Push(fmt.Errorf(errFmtOIDCCertificateMismatch))
}
validateOIDCIssuer(config, val)
if err := config.IssuerCertificateChain.Validate(); err != nil {
val.Push(fmt.Errorf(errFmtOIDCCertificateChain, err))
}
}
if config.IssuerPrivateKey.PublicKey.N == nil {
val.Push(fmt.Errorf(errFmtOIDCInvalidPrivateKeyMalformedMissingPublicKey))
} else if config.IssuerPrivateKey.Size()*8 < 2048 {
val.Push(fmt.Errorf(errFmtOIDCInvalidPrivateKeyBitSize, 2048, config.IssuerPrivateKey.Size()*8))
}
}
sort.Sort(oidc.SortedSigningAlgs(config.Discovery.RegisteredJWKSigningAlgs))
if config.MinimumParameterEntropy != 0 && config.MinimumParameterEntropy < 8 {
val.PushWarning(fmt.Errorf(errFmtOIDCServerInsecureParameterEntropy, config.MinimumParameterEntropy))
@ -62,6 +52,163 @@ func validateOIDC(config *schema.OpenIDConnectConfiguration, val *schema.StructV
}
}
func validateOIDCIssuer(config *schema.OpenIDConnectConfiguration, val *schema.StructValidator) {
switch {
case config.IssuerPrivateKey != nil:
validateOIDCIssuerLegacy(config, val)
fallthrough
case len(config.IssuerJWKS) != 0:
validateOIDCIssuerModern(config, val)
default:
val.Push(fmt.Errorf(errFmtOIDCNoPrivateKey))
}
}
func validateOIDCIssuerLegacy(config *schema.OpenIDConnectConfiguration, val *schema.StructValidator) {
j := &jose.JSONWebKey{Key: &config.IssuerPrivateKey.PublicKey}
thumbprint, err := j.Thumbprint(crypto.SHA1)
if err != nil {
val.Push(fmt.Errorf("identity_providers: oidc: option 'issuer_private_key' failed to calculate thumbprint to configure key id value: %w", err))
return
}
config.IssuerJWKS = append(config.IssuerJWKS, schema.JWK{
KeyID: fmt.Sprintf("%x", thumbprint)[:6],
Algorithm: oidc.SigningAlgRSAUsingSHA256,
Use: oidc.KeyUseSignature,
Key: config.IssuerPrivateKey,
CertificateChain: config.IssuerCertificateChain,
})
}
//nolint:gocyclo // Refactor time permitting.
func validateOIDCIssuerModern(config *schema.OpenIDConnectConfiguration, val *schema.StructValidator) {
var (
props *JWKProperties
err error
)
kids := make([]string, len(config.IssuerJWKS))
for i := 0; i < len(config.IssuerJWKS); i++ {
if key, ok := config.IssuerJWKS[i].Key.(*rsa.PrivateKey); ok && key.PublicKey.N == nil {
val.Push(fmt.Errorf("identity_providers: oidc: issuer_jwks: key #%d: option 'key' must be a valid RSA private key but the provided data is malformed as it's missing the public key bits", i+1))
continue
}
switch n := len(config.IssuerJWKS[i].KeyID); {
case n == 0:
j := jose.JSONWebKey{}
switch key := config.IssuerJWKS[i].Key.(type) {
case schema.CryptographicPrivateKey:
j.Key = key.Public()
case *rsa.PublicKey, *ecdsa.PublicKey, ed25519.PublicKey:
j.Key = key
default:
break
}
if j.Key == nil {
break
}
var thumbprint []byte
if thumbprint, err = j.Thumbprint(crypto.SHA1); err != nil {
val.Push(fmt.Errorf("identity_providers: oidc: issuer_jwks: key #%d: option 'key' failed to calculate thumbprint to configure key id value: %w", i+1, err))
continue
}
config.IssuerJWKS[i].KeyID = fmt.Sprintf("%x", thumbprint)[:6]
case n > 7:
val.Push(fmt.Errorf("identity_providers: oidc: issuer_jwks: key #%d with key id '%s': option `key_id`` must be 7 characters or less", i+1, config.IssuerJWKS[i].KeyID))
}
if config.IssuerJWKS[i].KeyID != "" && utils.IsStringInSlice(config.IssuerJWKS[i].KeyID, kids) {
val.Push(fmt.Errorf("identity_providers: oidc: issuer_jwks: key #%d with key id '%s': option 'key_id' must be unique", i+1, config.IssuerJWKS[i].KeyID))
}
kids[i] = config.IssuerJWKS[i].KeyID
if !utils.IsStringAlphaNumeric(config.IssuerJWKS[i].KeyID) {
val.Push(fmt.Errorf("identity_providers: oidc: issuer_jwks: key #%d with key id '%s': option 'key_id' must only have alphanumeric characters", i+1, config.IssuerJWKS[i].KeyID))
}
if props, err = schemaJWKGetProperties(config.IssuerJWKS[i]); err != nil {
val.Push(fmt.Errorf("identity_providers: oidc: issuer_jwks: key #%d with key id '%s': option 'key' failed to get key properties: %w", i+1, config.IssuerJWKS[i].KeyID, err))
continue
}
switch config.IssuerJWKS[i].Use {
case "":
config.IssuerJWKS[i].Use = props.Use
case oidc.KeyUseSignature:
break
default:
val.Push(fmt.Errorf("identity_providers: oidc: issuer_jwks: key #%d with key id '%s': option '%s' must be one of %s but it's configured as '%s'", i+1, config.IssuerJWKS[i].KeyID, "use", strJoinOr([]string{oidc.KeyUseSignature}), config.IssuerJWKS[i].Use))
}
switch {
case config.IssuerJWKS[i].Algorithm == "":
config.IssuerJWKS[i].Algorithm = props.Algorithm
case utils.IsStringInSlice(config.IssuerJWKS[i].Algorithm, validOIDCIssuerJWKSigningAlgs):
break
default:
val.Push(fmt.Errorf("identity_providers: oidc: issuer_jwks: key #%d with key id '%s': option '%s' must be one of %s but it's configured as '%s'", i+1, config.IssuerJWKS[i].KeyID, "algorithm", strJoinOr(validOIDCIssuerJWKSigningAlgs), config.IssuerJWKS[i].Algorithm))
}
if config.IssuerJWKS[i].Algorithm != "" {
if utils.IsStringInSlice(config.IssuerJWKS[i].Algorithm, config.Discovery.RegisteredJWKSigningAlgs) {
val.Push(fmt.Errorf("identity_providers: oidc: issuer_jwks: key #%d with key id '%s': option 'algorithm' must be unique but another key is using it", i+1, config.IssuerJWKS[i].KeyID))
} else {
config.Discovery.RegisteredJWKSigningAlgs = append(config.Discovery.RegisteredJWKSigningAlgs, config.IssuerJWKS[i].Algorithm)
}
}
if config.IssuerJWKS[i].Algorithm == oidc.SigningAlgRSAUsingSHA256 && config.Discovery.DefaultKeyID == "" {
config.Discovery.DefaultKeyID = config.IssuerJWKS[i].KeyID
}
var checkEqualKey bool
switch key := config.IssuerJWKS[i].Key.(type) {
case *rsa.PrivateKey:
checkEqualKey = true
if key.Size() < 256 {
checkEqualKey = false
val.Push(fmt.Errorf("identity_providers: oidc: issuer_jwks: key #%d with key id '%s': option 'key' is an RSA %d bit private key but it must be a RSA 2048 bit private key", i+1, config.IssuerJWKS[i].KeyID, key.Size()*8))
}
case *ecdsa.PrivateKey:
checkEqualKey = true
default:
val.Push(fmt.Errorf("identity_providers: oidc: issuer_jwks: key #%d with key id '%s': option 'key' must be a *rsa.PrivateKey or *ecdsa.PrivateKey but it's a %T", i+1, config.IssuerJWKS[i].KeyID, key))
}
if config.IssuerJWKS[i].CertificateChain.HasCertificates() {
if checkEqualKey && !config.IssuerJWKS[i].CertificateChain.EqualKey(config.IssuerJWKS[i].Key) {
val.Push(fmt.Errorf("identity_providers: oidc: issuer_jwks: key #%d with key id '%s': option 'key' does not appear to be the private key the certificate provided by option 'certificate_chain'", i+1, config.IssuerJWKS[i].KeyID))
}
if err = config.IssuerJWKS[i].CertificateChain.Validate(); err != nil {
val.Push(fmt.Errorf("identity_providers: oidc: issuer_jwks: key #%d with key id '%s': option 'certificate_chain' produced an error during validation of the chain: %w", i+1, config.IssuerJWKS[i].KeyID, err))
}
}
}
if len(config.Discovery.RegisteredJWKSigningAlgs) != 0 && !utils.IsStringInSlice(oidc.SigningAlgRSAUsingSHA256, config.Discovery.RegisteredJWKSigningAlgs) {
val.Push(fmt.Errorf("identity_providers: oidc: issuer_jwks: keys: must at least have one key supporting the '%s' algorithm but only has %s", oidc.SigningAlgRSAUsingSHA256, strJoinAnd(config.Discovery.RegisteredJWKSigningAlgs)))
}
}
func setOIDCDefaults(config *schema.OpenIDConnectConfiguration) {
if config.AccessTokenLifespan == time.Duration(0) {
config.AccessTokenLifespan = schema.DefaultOpenIDConnectConfiguration.AccessTokenLifespan
@ -228,7 +375,7 @@ func validateOIDCClient(c int, config *schema.OpenIDConnectConfiguration, val *s
validateOIDCClientRedirectURIs(c, config, val, errDeprecatedFunc)
validateOIDCClientTokenEndpointAuth(c, config, val)
validateOIDDClientUserinfoAlgorithm(c, config, val)
validateOIDDClientSigningAlgs(c, config, val)
validateOIDCClientSectorIdentifier(c, config, val)
}
@ -516,13 +663,18 @@ func validateOIDCClientTokenEndpointAuth(c int, config *schema.OpenIDConnectConf
}
}
func validateOIDDClientUserinfoAlgorithm(c int, config *schema.OpenIDConnectConfiguration, val *schema.StructValidator) {
if config.Clients[c].UserinfoSigningAlgorithm == "" {
config.Clients[c].UserinfoSigningAlgorithm = schema.DefaultOpenIDConnectClientConfiguration.UserinfoSigningAlgorithm
func validateOIDDClientSigningAlgs(c int, config *schema.OpenIDConnectConfiguration, val *schema.StructValidator) {
if config.Clients[c].UserinfoSigningAlg == "" {
config.Clients[c].UserinfoSigningAlg = schema.DefaultOpenIDConnectClientConfiguration.UserinfoSigningAlg
} else if config.Clients[c].UserinfoSigningAlg != oidc.SigningAlgNone && !utils.IsStringInSlice(config.Clients[c].UserinfoSigningAlg, config.Discovery.RegisteredJWKSigningAlgs) {
val.Push(fmt.Errorf(errFmtOIDCClientInvalidValue,
config.Clients[c].ID, attrOIDCUsrSigAlg, strJoinOr(append(config.Discovery.RegisteredJWKSigningAlgs, oidc.SigningAlgNone)), config.Clients[c].UserinfoSigningAlg))
}
if !utils.IsStringInSlice(config.Clients[c].UserinfoSigningAlgorithm, validOIDCClientUserinfoAlgorithms) {
if config.Clients[c].IDTokenSigningAlg == "" {
config.Clients[c].IDTokenSigningAlg = schema.DefaultOpenIDConnectClientConfiguration.IDTokenSigningAlg
} else if !utils.IsStringInSlice(config.Clients[c].IDTokenSigningAlg, config.Discovery.RegisteredJWKSigningAlgs) {
val.Push(fmt.Errorf(errFmtOIDCClientInvalidValue,
config.Clients[c].ID, attrOIDCUsrSigAlg, strJoinOr(validOIDCClientUserinfoAlgorithms), config.Clients[c].UserinfoSigningAlgorithm))
config.Clients[c].ID, attrOIDCIDTokenSigAlg, strJoinOr(config.Discovery.RegisteredJWKSigningAlgs), config.Clients[c].IDTokenSigningAlg))
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,10 +1,17 @@
package validator
import (
"crypto/ecdsa"
"crypto/ed25519"
"crypto/elliptic"
"crypto/rsa"
"fmt"
"strings"
"golang.org/x/net/publicsuffix"
"github.com/authelia/authelia/v4/internal/configuration/schema"
"github.com/authelia/authelia/v4/internal/oidc"
"github.com/authelia/authelia/v4/internal/utils"
)
@ -107,3 +114,47 @@ func validateList(values, valid []string, chkDuplicate bool) (invalid, duplicate
return
}
type JWKProperties struct {
Use string
Algorithm string
Bits int
Curve elliptic.Curve
}
func schemaJWKGetProperties(jwk schema.JWK) (properties *JWKProperties, err error) {
switch key := jwk.Key.(type) {
case nil:
return nil, fmt.Errorf("private key is nil")
case ed25519.PrivateKey, ed25519.PublicKey:
return &JWKProperties{}, nil
case *rsa.PrivateKey:
return &JWKProperties{oidc.KeyUseSignature, oidc.SigningAlgRSAUsingSHA256, key.Size(), nil}, nil
case *rsa.PublicKey:
return &JWKProperties{oidc.KeyUseSignature, oidc.SigningAlgRSAUsingSHA256, key.Size(), nil}, nil
case *ecdsa.PublicKey:
switch key.Curve {
case elliptic.P256():
return &JWKProperties{oidc.KeyUseSignature, oidc.SigningAlgECDSAUsingP256AndSHA256, -1, key.Curve}, nil
case elliptic.P384():
return &JWKProperties{oidc.KeyUseSignature, oidc.SigningAlgECDSAUsingP384AndSHA384, -1, key.Curve}, nil
case elliptic.P521():
return &JWKProperties{oidc.KeyUseSignature, oidc.SigningAlgECDSAUsingP521AndSHA512, -1, key.Curve}, nil
default:
return &JWKProperties{oidc.KeyUseSignature, "", -1, key.Curve}, nil
}
case *ecdsa.PrivateKey:
switch key.Curve {
case elliptic.P256():
return &JWKProperties{oidc.KeyUseSignature, oidc.SigningAlgECDSAUsingP256AndSHA256, -1, key.Curve}, nil
case elliptic.P384():
return &JWKProperties{oidc.KeyUseSignature, oidc.SigningAlgECDSAUsingP384AndSHA384, -1, key.Curve}, nil
case elliptic.P521():
return &JWKProperties{oidc.KeyUseSignature, oidc.SigningAlgECDSAUsingP521AndSHA512, -1, key.Curve}, nil
default:
return &JWKProperties{oidc.KeyUseSignature, "", -1, key.Curve}, nil
}
default:
return nil, fmt.Errorf("the key type '%T' is unknown or not valid for the configuration", key)
}
}

View File

@ -8,9 +8,9 @@ import (
// JSONWebKeySetGET returns the JSON Web Key Set. Used in OAuth 2.0 and OpenID Connect 1.0.
func JSONWebKeySetGET(ctx *middlewares.AutheliaCtx) {
ctx.SetContentType("application/json")
ctx.SetContentTypeApplicationJSON()
if err := json.NewEncoder(ctx).Encode(ctx.Providers.OpenIDConnect.KeyManager.GetKeySet()); err != nil {
if err := json.NewEncoder(ctx).Encode(ctx.Providers.OpenIDConnect.KeyManager.Set(ctx)); err != nil {
ctx.Error(err, "failed to serve json web key set")
}
}

View File

@ -127,7 +127,7 @@ func OpenIDConnectAuthorization(ctx *middlewares.AutheliaCtx, rw http.ResponseWr
ctx.Logger.Debugf("Authorization Request with id '%s' on client with id '%s' was successfully processed, proceeding to build Authorization Response", requester.GetID(), clientID)
session := oidc.NewSessionWithAuthorizeRequest(issuer, ctx.Providers.OpenIDConnect.KeyManager.GetActiveKeyID(),
session := oidc.NewSessionWithAuthorizeRequest(issuer, ctx.Providers.OpenIDConnect.KeyManager.GetKIDFromAlg(ctx, client.GetIDTokenSigningAlg()),
userSession.Username, userSession.AuthenticationMethodRefs.MarshalRFC8176(), extraClaims, authTime, consent, requester)
ctx.Logger.Tracef("Authorization Request with id '%s' on client with id '%s' creating session for Authorization Response for subject '%s' with username '%s' with claims: %+v",

View File

@ -145,11 +145,10 @@ func (s *ClientAuthenticationStrategySuite) SetupTest() {
s.ctrl = gomock.NewController(s.T())
s.store = mocks.NewMockStorage(s.ctrl)
var err error
secret := MustDecodeSecret("$plaintext$client-secret")
s.provider, err = oidc.NewOpenIDConnectProvider(&schema.OpenIDConnectConfiguration{
s.provider = oidc.NewOpenIDConnectProvider(&schema.OpenIDConnectConfiguration{
IssuerJWKS: []schema.JWK{},
IssuerCertificateChain: schema.X509CertificateChain{},
IssuerPrivateKey: MustParseRSAPrivateKey(exampleRSAPrivateKey),
HMACSecret: "abc123",
@ -370,8 +369,6 @@ func (s *ClientAuthenticationStrategySuite) SetupTest() {
},
},
}, s.store, nil)
s.Require().NoError(err)
}
func (s *ClientAuthenticationStrategySuite) TestShouldValidateAssertionHS256() {

View File

@ -99,8 +99,18 @@ func OpenIDConnectUserinfo(ctx *middlewares.AutheliaCtx, rw http.ResponseWriter,
ctx.Logger.Tracef("UserInfo Response with id '%s' on client with id '%s' is being sent with the following claims: %+v", requester.GetID(), clientID, claims)
switch client.GetUserinfoSigningAlgorithm() {
case oidc.SigningAlgRSAUsingSHA256:
switch alg := client.GetUserinfoSigningAlg(); alg {
case oidc.SigningAlgNone, "":
ctx.Providers.OpenIDConnect.Write(rw, req, claims)
default:
var jwk *oidc.JWK
if jwk = ctx.Providers.OpenIDConnect.KeyManager.GetByAlg(ctx, alg); jwk == nil {
ctx.Providers.OpenIDConnect.WriteError(rw, req, errors.WithStack(fosite.ErrServerError.WithHintf("Unsupported UserInfo signing algorithm '%s'.", alg)))
return
}
var jti uuid.UUID
if jti, err = uuid.NewRandom(); err != nil {
@ -114,11 +124,11 @@ func OpenIDConnectUserinfo(ctx *middlewares.AutheliaCtx, rw http.ResponseWriter,
headers := &jwt.Headers{
Extra: map[string]any{
oidc.JWTHeaderKeyIdentifier: ctx.Providers.OpenIDConnect.KeyManager.GetActiveKeyID(),
oidc.JWTHeaderKeyIdentifier: jwk.KeyID(),
},
}
if token, _, err = ctx.Providers.OpenIDConnect.KeyManager.Strategy().Generate(req.Context(), claims, headers); err != nil {
if token, _, err = jwk.Strategy().Generate(req.Context(), claims, headers); err != nil {
ctx.Providers.OpenIDConnect.WriteError(rw, req, err)
return
@ -126,9 +136,5 @@ func OpenIDConnectUserinfo(ctx *middlewares.AutheliaCtx, rw http.ResponseWriter,
rw.Header().Set(fasthttp.HeaderContentType, "application/jwt")
_, _ = rw.Write([]byte(token))
case oidc.SigningAlgNone, "":
ctx.Providers.OpenIDConnect.Write(rw, req, claims)
default:
ctx.Providers.OpenIDConnect.WriteError(rw, req, errors.WithStack(fosite.ErrServerError.WithHintf("Unsupported UserInfo signing algorithm '%s'.", client.GetUserinfoSigningAlgorithm())))
}
}

View File

@ -34,7 +34,8 @@ func NewClient(config schema.OpenIDConnectClientConfiguration) (client Client) {
EnforcePAR: config.EnforcePAR,
UserinfoSigningAlgorithm: config.UserinfoSigningAlgorithm,
IDTokenSigningAlg: config.IDTokenSigningAlg,
UserinfoSigningAlg: config.UserinfoSigningAlg,
Policy: authorization.NewLevel(config.Policy),
@ -131,13 +132,22 @@ func (c *BaseClient) GetResponseModes() []fosite.ResponseModeType {
return c.ResponseModes
}
// GetUserinfoSigningAlgorithm returns the UserinfoSigningAlgorithm.
func (c *BaseClient) GetUserinfoSigningAlgorithm() string {
if c.UserinfoSigningAlgorithm == "" {
c.UserinfoSigningAlgorithm = SigningAlgNone
// GetIDTokenSigningAlg returns the IDTokenSigningAlg.
func (c *BaseClient) GetIDTokenSigningAlg() (alg string) {
if c.IDTokenSigningAlg == "" {
c.IDTokenSigningAlg = SigningAlgRSAUsingSHA256
}
return c.UserinfoSigningAlgorithm
return c.IDTokenSigningAlg
}
// GetUserinfoSigningAlg returns the UserinfoSigningAlg.
func (c *BaseClient) GetUserinfoSigningAlg() string {
if c.UserinfoSigningAlg == "" {
c.UserinfoSigningAlg = SigningAlgNone
}
return c.UserinfoSigningAlg
}
// GetPAREnforcement returns EnforcePAR.
@ -295,7 +305,7 @@ func (c *FullClient) GetTokenEndpointAuthMethod() string {
if c.Public {
c.TokenEndpointAuthMethod = ClientAuthMethodNone
} else {
c.TokenEndpointAuthMethod = ClientAuthMethodClientSecretPost
c.TokenEndpointAuthMethod = ClientAuthMethodClientSecretBasic
}
}

View File

@ -74,8 +74,8 @@ func (p *OpenIDConnectProvider) DefaultClientAuthenticationStrategy(ctx context.
return nil, errorsx.WithStack(fosite.ErrInvalidClient.WithHintf("This requested OAuth 2.0 client only supports client authentication method '%s', however that method is not supported by this server.", oidcClient.GetTokenEndpointAuthMethod()))
}
if oidcClient.GetTokenEndpointAuthSigningAlgorithm() != fmt.Sprintf("%s", t.Header[HeaderParameterAlgorithm]) {
return nil, errorsx.WithStack(fosite.ErrInvalidClient.WithHintf("The 'client_assertion' uses signing algorithm '%s' but the requested OAuth 2.0 Client enforces signing algorithm '%s'.", t.Header[HeaderParameterAlgorithm], oidcClient.GetTokenEndpointAuthSigningAlgorithm()))
if oidcClient.GetTokenEndpointAuthSigningAlgorithm() != fmt.Sprintf("%s", t.Header[JWTHeaderKeyAlgorithm]) {
return nil, errorsx.WithStack(fosite.ErrInvalidClient.WithHintf("The 'client_assertion' uses signing algorithm '%s' but the requested OAuth 2.0 Client enforces signing algorithm '%s'.", t.Header[JWTHeaderKeyAlgorithm], oidcClient.GetTokenEndpointAuthSigningAlgorithm()))
}
switch t.Method {
@ -94,7 +94,7 @@ func (p *OpenIDConnectProvider) DefaultClientAuthenticationStrategy(ctx context.
return nil, errorsx.WithStack(fosite.ErrInvalidClient.WithHint("This client does not support authentication method 'client_secret_jwt' as the client secret is not in plaintext."))
default:
return nil, errorsx.WithStack(fosite.ErrInvalidClient.WithHintf("The 'client_assertion' request parameter uses unsupported signing algorithm '%s'.", t.Header[HeaderParameterAlgorithm]))
return nil, errorsx.WithStack(fosite.ErrInvalidClient.WithHintf("The 'client_assertion' request parameter uses unsupported signing algorithm '%s'.", t.Header[JWTHeaderKeyAlgorithm]))
}
})

View File

@ -3,6 +3,7 @@ package oidc
import (
"fmt"
"testing"
"time"
"github.com/ory/fosite"
"github.com/stretchr/testify/assert"
@ -26,8 +27,8 @@ func TestNewClient(t *testing.T) {
bclient, ok := client.(*BaseClient)
require.True(t, ok)
assert.Equal(t, "", bclient.UserinfoSigningAlgorithm)
assert.Equal(t, SigningAlgNone, client.GetUserinfoSigningAlgorithm())
assert.Equal(t, "", bclient.UserinfoSigningAlg)
assert.Equal(t, SigningAlgNone, client.GetUserinfoSigningAlg())
_, ok = client.(*FullClient)
assert.False(t, ok)
@ -51,7 +52,7 @@ func TestNewClient(t *testing.T) {
assert.Equal(t, authorization.TwoFactor, client.GetAuthorizationPolicy())
config = schema.OpenIDConnectClientConfiguration{
TokenEndpointAuthMethod: ClientAuthMethodClientSecretBasic,
TokenEndpointAuthMethod: ClientAuthMethodClientSecretPost,
}
client = NewClient(config)
@ -61,18 +62,51 @@ func TestNewClient(t *testing.T) {
var niljwks *jose.JSONWebKeySet
require.True(t, ok)
assert.Equal(t, "", fclient.UserinfoSigningAlgorithm)
assert.Equal(t, ClientAuthMethodClientSecretBasic, fclient.TokenEndpointAuthMethod)
assert.Equal(t, ClientAuthMethodClientSecretBasic, fclient.GetTokenEndpointAuthMethod())
assert.Equal(t, SigningAlgNone, client.GetUserinfoSigningAlgorithm())
assert.Equal(t, "", fclient.UserinfoSigningAlg)
assert.Equal(t, SigningAlgNone, client.GetUserinfoSigningAlg())
assert.Equal(t, SigningAlgNone, fclient.UserinfoSigningAlg)
assert.Equal(t, "", fclient.IDTokenSigningAlg)
assert.Equal(t, SigningAlgRSAUsingSHA256, client.GetIDTokenSigningAlg())
assert.Equal(t, SigningAlgRSAUsingSHA256, fclient.IDTokenSigningAlg)
assert.Equal(t, ClientAuthMethodClientSecretPost, fclient.TokenEndpointAuthMethod)
assert.Equal(t, ClientAuthMethodClientSecretPost, fclient.GetTokenEndpointAuthMethod())
assert.Equal(t, "", fclient.TokenEndpointAuthSigningAlgorithm)
assert.Equal(t, SigningAlgRSAUsingSHA256, fclient.GetTokenEndpointAuthSigningAlgorithm())
assert.Equal(t, SigningAlgRSAUsingSHA256, fclient.TokenEndpointAuthSigningAlgorithm)
assert.Equal(t, "", fclient.RequestObjectSigningAlgorithm)
assert.Equal(t, "", fclient.GetRequestObjectSigningAlgorithm())
fclient.RequestObjectSigningAlgorithm = SigningAlgRSAUsingSHA256
assert.Equal(t, SigningAlgRSAUsingSHA256, fclient.GetRequestObjectSigningAlgorithm())
assert.Equal(t, "", fclient.JSONWebKeysURI)
assert.Equal(t, "", fclient.GetJSONWebKeysURI())
fclient.JSONWebKeysURI = "https://example.com"
assert.Equal(t, "https://example.com", fclient.GetJSONWebKeysURI())
assert.Equal(t, niljwks, fclient.JSONWebKeys)
assert.Equal(t, niljwks, fclient.GetJSONWebKeys())
assert.Equal(t, ClientConsentMode(0), fclient.Consent.Mode)
assert.Equal(t, time.Second*0, fclient.Consent.Duration)
assert.Equal(t, ClientConsent{Mode: ClientConsentModeExplicit}, fclient.GetConsentPolicy())
fclient.TokenEndpointAuthMethod = ""
fclient.Public = false
assert.Equal(t, ClientAuthMethodClientSecretBasic, fclient.GetTokenEndpointAuthMethod())
assert.Equal(t, ClientAuthMethodClientSecretBasic, fclient.TokenEndpointAuthMethod)
fclient.TokenEndpointAuthMethod = ""
fclient.Public = true
assert.Equal(t, ClientAuthMethodNone, fclient.GetTokenEndpointAuthMethod())
assert.Equal(t, ClientAuthMethodNone, fclient.TokenEndpointAuthMethod)
assert.Equal(t, []string(nil), fclient.RequestURIs)
assert.Equal(t, []string(nil), fclient.GetRequestURIs())
}

View File

@ -134,10 +134,6 @@ const (
PKCEChallengeMethodSHA256 = "S256"
)
const (
HeaderParameterAlgorithm = "alg"
)
const (
FormParameterClientID = "client_id"
FormParameterClientSecret = "client_secret"
@ -170,6 +166,9 @@ const (
const (
// JWTHeaderKeyIdentifier is the JWT Header referencing the JWS Key Identifier used to sign a token.
JWTHeaderKeyIdentifier = "kid"
// JWTHeaderKeyAlgorithm is the JWT Header referencing the JWS Key algorithm used to sign a token.
JWTHeaderKeyAlgorithm = "alg"
)
const (

View File

@ -1,15 +1,21 @@
package oidc
import (
"crypto/ecdsa"
"crypto/ed25519"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"fmt"
"net/url"
"os"
"strings"
"github.com/authelia/authelia/v4/internal/configuration/schema"
"github.com/authelia/authelia/v4/internal/utils"
)
const (
pathCrypto = "../configuration/test_resources/crypto/%s.%s"
myclient = "myclient"
myclientdesc = "My Client"
onefactor = "one_factor"
@ -18,7 +24,6 @@ const (
examplecomsid = "example.com"
badsecret = "$plaintext$a_bad_secret"
badhmac = "asbdhaaskmdlkamdklasmdlkams"
exampleIssuerPrivateKey = "-----BEGIN RSA PRIVATE KEY-----\nMIIEpAIBAAKCAQEAvcMVMB2vEbqI6PlSNJ4HmUyMxBDJ5iY7FS+zDDAHOZBg9S3S\nKcAn1CZcnyL0VvJ7wcdhR6oTnOwR94eKvzUyJZ+GL2hTMm27dubEYsNdhoCl6N3X\nyEEohNfoxiiCYraVauX8X3M9jFzbEz9+pacaDbHB2syaJ1qFmMNR+HSu2jPzOo7M\nlqKIOgUzA0741MaYNt47AEVg4XU5ORLdolbAkItmYg1QbyFndg9H5IvwKkYaXTGE\nlgDBcPUC0yVjAC15Mguquq+jZeQay+6PSbHTD8PQMOkLjyChI2xEhVNbdCXe676R\ncMW2R/gjrcK23zmtmTWRfdC1iZLSlHO+bJj9vQIDAQABAoIBAEZvkP/JJOCJwqPn\nV3IcbmmilmV4bdi1vByDFgyiDyx4wOSA24+PubjvfFW9XcCgRPuKjDtTj/AhWBHv\nB7stfa2lZuNV7/u562mZArA+IAr62Zp0LdIxDV8x3T8gbjVB3HhPYbv0RJZDKTYd\nzV6jhfIrVu9mHpoY6ZnodhapCPYIyk/d49KBIHZuAc25CUjMXgTeaVtf0c996036\nUxW6ef33wAOJAvW0RCvbXAJfmBeEq2qQlkjTIlpYx71fhZWexHifi8Ouv3Zonc+1\n/P2Adq5uzYVBT92f9RKHg9QxxNzVrLjSMaxyvUtWQCAQfW0tFIRdqBGsHYsQrFtI\nF4yzv8ECgYEA7ntpyN9HD9Z9lYQzPCR73sFCLM+ID99aVij0wHuxK97bkSyyvkLd\n7MyTaym3lg1UEqWNWBCLvFULZx7F0Ah6qCzD4ymm3Bj/ADpWWPgljBI0AFml+HHs\nhcATmXUrj5QbLyhiP2gmJjajp1o/rgATx6ED66seSynD6JOH8wUhhZUCgYEAy7OA\n06PF8GfseNsTqlDjNF0K7lOqd21S0prdwrsJLiVzUlfMM25MLE0XLDUutCnRheeh\nIlcuDoBsVTxz6rkvFGD74N+pgXlN4CicsBq5ofK060PbqCQhSII3fmHobrZ9Cr75\nHmBjAxHx998SKaAAGbBbcYGUAp521i1pH5CEPYkCgYEAkUd1Zf0+2RMdZhwm6hh/\nrW+l1I6IoMK70YkZsLipccRNld7Y9LbfYwYtODcts6di9AkOVfueZJiaXbONZfIE\nZrb+jkAteh9wGL9xIrnohbABJcV3Kiaco84jInUSmGDtPokncOENfHIEuEpuSJ2b\nbx1TuhmAVuGWivR0+ULC7RECgYEAgS0cDRpWc9Xzh9Cl7+PLsXEvdWNpPsL9OsEq\n0Ep7z9+/+f/jZtoTRCS/BTHUpDvAuwHglT5j3p5iFMt5VuiIiovWLwynGYwrbnNS\nqfrIrYKUaH1n1oDS+oBZYLQGCe9/7EifAjxtjYzbvSyg//SPG7tSwfBCREbpZXj2\nqSWkNsECgYA/mCDzCTlrrWPuiepo6kTmN+4TnFA+hJI6NccDVQ+jvbqEdoJ4SW4L\nzqfZSZRFJMNpSgIqkQNRPJqMP0jQ5KRtJrjMWBnYxktwKz9fDg2R2MxdFgMF2LH2\nHEMMhFHlv8NDjVOXh1KwRoltNGVWYsSrD9wKU9GhRCEfmNCGrvBcEg==\n-----END RSA PRIVATE KEY-----"
)
func MustDecodeSecret(value string) *schema.PasswordDigest {
@ -37,20 +42,126 @@ func MustParseRequestURI(input string) *url.URL {
}
}
func MustParseRSAPrivateKey(data string) *rsa.PrivateKey {
block, _ := pem.Decode([]byte(data))
if block == nil || block.Bytes == nil || len(block.Bytes) == 0 {
panic("not pem encoded")
func MustLoadCrypto(alg, mod, ext string, extra ...string) any {
fparts := []string{alg, mod}
if len(extra) != 0 {
fparts = append(fparts, extra...)
}
if block.Type != "RSA PRIVATE KEY" {
panic("not private key")
}
var (
data []byte
decoded any
err error
)
key, err := x509.ParsePKCS1PrivateKey(block.Bytes)
if err != nil {
if data, err = os.ReadFile(fmt.Sprintf(pathCrypto, strings.Join(fparts, "_"), ext)); err != nil {
panic(err)
}
if decoded, err = utils.ParseX509FromPEMRecursive(data); err != nil {
panic(err)
}
return decoded
}
func MustLoadCertificateChain(alg, op string) schema.X509CertificateChain {
decoded := MustLoadCrypto(alg, op, "crt")
switch cert := decoded.(type) {
case *x509.Certificate:
return schema.NewX509CertificateChainFromCerts([]*x509.Certificate{cert})
case []*x509.Certificate:
return schema.NewX509CertificateChainFromCerts(cert)
default:
panic(fmt.Errorf("the key was not a *x509.Certificate or []*x509.Certificate, it's a %T", cert))
}
}
func MustLoadCertificate(alg, op string) *x509.Certificate {
decoded := MustLoadCrypto(alg, op, "crt")
cert, ok := decoded.(*x509.Certificate)
if !ok {
panic(fmt.Errorf("the key was not a *x509.Certificate, it's a %T", cert))
}
return cert
}
func MustLoadEd15519PrivateKey(curve string, extra ...string) ed25519.PrivateKey {
decoded := MustLoadCrypto("ED25519", curve, "pem", extra...)
key, ok := decoded.(ed25519.PrivateKey)
if !ok {
panic(fmt.Errorf("the key was not a ed25519.PrivateKey, it's a %T", key))
}
return key
}
func MustLoadECDSAPrivateKey(curve string, extra ...string) *ecdsa.PrivateKey {
decoded := MustLoadCrypto("ECDSA", curve, "pem", extra...)
key, ok := decoded.(*ecdsa.PrivateKey)
if !ok {
panic(fmt.Errorf("the key was not a *ecdsa.PrivateKey, it's a %T", key))
}
return key
}
func MustLoadRSAPublicKey(bits string, extra ...string) *rsa.PublicKey {
decoded := MustLoadCrypto("RSA", bits, "pem", extra...)
key, ok := decoded.(*rsa.PublicKey)
if !ok {
panic(fmt.Errorf("the key was not a *rsa.PublicKey, it's a %T", key))
}
return key
}
func MustLoadRSAPrivateKey(bits string, extra ...string) *rsa.PrivateKey {
decoded := MustLoadCrypto("RSA", bits, "pem", extra...)
key, ok := decoded.(*rsa.PrivateKey)
if !ok {
panic(fmt.Errorf("the key was not a *rsa.PrivateKey, it's a %T", key))
}
return key
}
var (
tOpenIDConnectPBKDF2ClientSecret, tOpenIDConnectPlainTextClientSecret *schema.PasswordDigest
// Standard RSA key / certificate pairs.
keyRSA1024, keyRSA2048, keyRSA4096 *rsa.PrivateKey
certRSA1024, certRSA2048, certRSA4096 schema.X509CertificateChain
// Standard ECDSA key / certificate pairs.
keyECDSAP224, keyECDSAP256, keyECDSAP384, keyECDSAP521 *ecdsa.PrivateKey
certECDSAP224, certECDSAP256, certECDSAP384, certECDSAP521 schema.X509CertificateChain
)
func init() {
tOpenIDConnectPBKDF2ClientSecret = MustDecodeSecret("$pbkdf2-sha512$310000$c8p78n7pUMln0jzvd4aK4Q$JNRBzwAo0ek5qKn50cFzzvE9RXV88h1wJn5KGiHrD0YKtZaR/nCb2CJPOsKaPK0hjf.9yHxzQGZziziccp6Yng")
tOpenIDConnectPlainTextClientSecret = MustDecodeSecret("$plaintext$example")
keyRSA1024 = MustLoadRSAPrivateKey("1024")
keyRSA2048 = MustLoadRSAPrivateKey("2048")
keyRSA4096 = MustLoadRSAPrivateKey("4096")
keyECDSAP224 = MustLoadECDSAPrivateKey("P224")
keyECDSAP256 = MustLoadECDSAPrivateKey("P256")
keyECDSAP384 = MustLoadECDSAPrivateKey("P384")
keyECDSAP521 = MustLoadECDSAPrivateKey("P521")
certRSA1024 = MustLoadCertificateChain("RSA", "1024")
certRSA2048 = MustLoadCertificateChain("RSA", "2048")
certRSA4096 = MustLoadCertificateChain("RSA", "4096")
certECDSAP224 = MustLoadCertificateChain("ECDSA", "P224")
certECDSAP256 = MustLoadCertificateChain("ECDSA", "P256")
certECDSAP384 = MustLoadCertificateChain("ECDSA", "P384")
certECDSAP521 = MustLoadCertificateChain("ECDSA", "P521")
}

View File

@ -1,13 +1,93 @@
package oidc
import (
"context"
"fmt"
"regexp"
"strings"
"testing"
"time"
"github.com/ory/fosite"
"github.com/ory/fosite/token/hmac"
"github.com/stretchr/testify/assert"
)
func TestHMACStrategy(t *testing.T) {
goodsecret := []byte("R7VCSUfnKc7Y5zE84q6GstYqfMGjL4wM")
secreta := []byte("a")
config := &Config{
TokenEntropy: 10,
GlobalSecret: secreta,
Lifespans: LifespanConfig{
AccessToken: time.Hour,
RefreshToken: time.Hour,
AuthorizeCode: time.Minute,
},
}
strategy := &HMACCoreStrategy{
Enigma: &hmac.HMACStrategy{Config: config},
Config: config,
}
var (
token, signature string
err error
)
ctx := context.Background()
token, signature, err = strategy.GenerateAuthorizeCode(ctx, &fosite.Request{})
assert.EqualError(t, err, "secret for signing HMAC-SHA512/256 is expected to be 32 byte long, got 1 byte")
assert.Empty(t, token)
assert.Empty(t, signature)
config.GlobalSecret = goodsecret
token, signature, err = strategy.GenerateAuthorizeCode(ctx, &fosite.Request{})
assert.NoError(t, err)
assert.NotEmpty(t, token)
assert.NotEmpty(t, signature)
assert.Equal(t, signature, strategy.AuthorizeCodeSignature(ctx, token))
assert.Regexp(t, regexp.MustCompile(`^authelia_ac_`), token)
assert.NoError(t, strategy.ValidateAuthorizeCode(ctx, &fosite.Request{RequestedAt: time.Now(), Session: &fosite.DefaultSession{}}, token))
assert.NoError(t, strategy.ValidateAuthorizeCode(ctx, &fosite.Request{RequestedAt: time.Now(), Session: &fosite.DefaultSession{}}, token))
assert.EqualError(t, strategy.ValidateAuthorizeCode(ctx, &fosite.Request{RequestedAt: time.Now().Add(time.Hour * -2400), Session: &fosite.DefaultSession{}}, token), "invalid_token")
assert.NoError(t, strategy.ValidateAuthorizeCode(ctx, &fosite.Request{RequestedAt: time.Now().Add(time.Hour * -2400), Session: &fosite.DefaultSession{ExpiresAt: map[fosite.TokenType]time.Time{fosite.AuthorizeCode: time.Now().Add(100 * time.Hour)}}}, token))
assert.EqualError(t, strategy.ValidateAuthorizeCode(ctx, &fosite.Request{RequestedAt: time.Now(), Session: &fosite.DefaultSession{ExpiresAt: map[fosite.TokenType]time.Time{fosite.AuthorizeCode: time.Now().Add(-100 * time.Second)}}}, token), "invalid_token")
token, signature, err = strategy.GenerateRefreshToken(ctx, &fosite.Request{})
assert.NoError(t, err)
assert.NotEmpty(t, token)
assert.NotEmpty(t, signature)
assert.Equal(t, signature, strategy.RefreshTokenSignature(ctx, token))
assert.Regexp(t, regexp.MustCompile(`^authelia_rt_`), token)
assert.NoError(t, strategy.ValidateRefreshToken(ctx, &fosite.Request{RequestedAt: time.Now(), Session: &fosite.DefaultSession{}}, token))
assert.NoError(t, strategy.ValidateRefreshToken(ctx, &fosite.Request{RequestedAt: time.Now(), Session: &fosite.DefaultSession{}}, token))
assert.NoError(t, strategy.ValidateRefreshToken(ctx, &fosite.Request{RequestedAt: time.Now().Add(time.Hour * -2400), Session: &fosite.DefaultSession{ExpiresAt: map[fosite.TokenType]time.Time{fosite.RefreshToken: time.Now().Add(100 * time.Hour)}}}, token))
assert.EqualError(t, strategy.ValidateRefreshToken(ctx, &fosite.Request{RequestedAt: time.Now(), Session: &fosite.DefaultSession{ExpiresAt: map[fosite.TokenType]time.Time{fosite.RefreshToken: time.Now().Add(-100 * time.Second)}}}, token), "invalid_token")
token, signature, err = strategy.GenerateAccessToken(ctx, &fosite.Request{})
assert.NoError(t, err)
assert.NotEmpty(t, token)
assert.NotEmpty(t, signature)
assert.Equal(t, signature, strategy.AccessTokenSignature(ctx, token))
assert.Regexp(t, regexp.MustCompile(`^authelia_at_`), token)
assert.NoError(t, strategy.ValidateAccessToken(ctx, &fosite.Request{RequestedAt: time.Now(), Session: &fosite.DefaultSession{}}, token))
assert.NoError(t, strategy.ValidateAccessToken(ctx, &fosite.Request{RequestedAt: time.Now(), Session: &fosite.DefaultSession{}}, token))
assert.EqualError(t, strategy.ValidateAccessToken(ctx, &fosite.Request{RequestedAt: time.Now().Add(time.Hour * -2400), Session: &fosite.DefaultSession{}}, token), "invalid_token")
assert.NoError(t, strategy.ValidateAccessToken(ctx, &fosite.Request{RequestedAt: time.Now().Add(time.Hour * -2400), Session: &fosite.DefaultSession{ExpiresAt: map[fosite.TokenType]time.Time{fosite.AccessToken: time.Now().Add(100 * time.Hour)}}}, token))
assert.EqualError(t, strategy.ValidateAccessToken(ctx, &fosite.Request{RequestedAt: time.Now(), Session: &fosite.DefaultSession{ExpiresAt: map[fosite.TokenType]time.Time{fosite.AccessToken: time.Now().Add(-100 * time.Second)}}}, token), "invalid_token")
}
func TestHMACCoreStrategy_TrimPrefix(t *testing.T) {
testCases := []struct {
name string

View File

@ -1,7 +1,10 @@
package oidc
import (
"sort"
"github.com/authelia/authelia/v4/internal/configuration/schema"
"github.com/authelia/authelia/v4/internal/utils"
)
// NewOpenIDConnectWellKnownConfiguration generates a new OpenIDConnectWellKnownConfiguration.
@ -75,6 +78,21 @@ func NewOpenIDConnectWellKnownConfiguration(c *schema.OpenIDConnectConfiguration
CodeChallengeMethodsSupported: []string{
PKCEChallengeMethodSHA256,
},
RevocationEndpointAuthMethodsSupported: []string{
ClientAuthMethodClientSecretBasic,
ClientAuthMethodClientSecretPost,
ClientAuthMethodClientSecretJWT,
ClientAuthMethodNone,
},
RevocationEndpointAuthSigningAlgValuesSupported: []string{
SigningAlgHMACUsingSHA256,
SigningAlgHMACUsingSHA384,
SigningAlgHMACUsingSHA512,
},
IntrospectionEndpointAuthMethodsSupported: []string{
ClientAuthMethodClientSecretBasic,
ClientAuthMethodNone,
},
},
OAuth2PushedAuthorizationDiscoveryOptions: &OAuth2PushedAuthorizationDiscoveryOptions{
RequirePushedAuthorizationRequests: c.PAR.Enforce,
@ -89,6 +107,10 @@ func NewOpenIDConnectWellKnownConfiguration(c *schema.OpenIDConnectConfiguration
SigningAlgNone,
SigningAlgRSAUsingSHA256,
},
RequestObjectSigningAlgValuesSupported: []string{
SigningAlgNone,
SigningAlgRSAUsingSHA256,
},
},
OpenIDConnectFrontChannelLogoutDiscoveryOptions: &OpenIDConnectFrontChannelLogoutDiscoveryOptions{},
OpenIDConnectBackChannelLogoutDiscoveryOptions: &OpenIDConnectBackChannelLogoutDiscoveryOptions{},
@ -100,6 +122,26 @@ func NewOpenIDConnectWellKnownConfiguration(c *schema.OpenIDConnectConfiguration
},
}
algs := make([]string, len(c.Discovery.RegisteredJWKSigningAlgs))
copy(algs, c.Discovery.RegisteredJWKSigningAlgs)
for _, alg := range algs {
if !utils.IsStringInSlice(alg, config.IDTokenSigningAlgValuesSupported) {
config.IDTokenSigningAlgValuesSupported = append(config.IDTokenSigningAlgValuesSupported, alg)
}
if !utils.IsStringInSlice(alg, config.UserinfoSigningAlgValuesSupported) {
config.UserinfoSigningAlgValuesSupported = append(config.UserinfoSigningAlgValuesSupported, alg)
}
}
sort.Sort(SortedSigningAlgs(config.IDTokenSigningAlgValuesSupported))
sort.Sort(SortedSigningAlgs(config.UserinfoSigningAlgValuesSupported))
sort.Sort(SortedSigningAlgs(config.RequestObjectSigningAlgValuesSupported))
sort.Sort(SortedSigningAlgs(config.RevocationEndpointAuthSigningAlgValuesSupported))
sort.Sort(SortedSigningAlgs(config.TokenEndpointAuthSigningAlgValuesSupported))
if c.EnablePKCEPlainChallenge {
config.CodeChallengeMethodsSupported = append(config.CodeChallengeMethodsSupported, PKCEChallengeMethodPlain)
}

View File

@ -14,8 +14,9 @@ func TestNewOpenIDConnectWellKnownConfiguration(t *testing.T) {
pkcePlainChallenge bool
enforcePAR bool
clients map[string]Client
discovery schema.OpenIDConnectDiscovery
expectCodeChallengeMethodsSupported, expectSubjectTypesSupported []string
expectCodeChallengeMethodsSupported, expectSubjectTypesSupported, expectedIDTokenSigAlgsSupported, expectedUserInfoSigAlgsSupported []string
}{
{
desc: "ShouldHaveChallengeMethodsS256ANDSubjectTypesSupportedPublic",
@ -23,6 +24,20 @@ func TestNewOpenIDConnectWellKnownConfiguration(t *testing.T) {
clients: map[string]Client{"a": &BaseClient{}},
expectCodeChallengeMethodsSupported: []string{PKCEChallengeMethodSHA256},
expectSubjectTypesSupported: []string{SubjectTypePublic, SubjectTypePairwise},
expectedIDTokenSigAlgsSupported: []string{SigningAlgRSAUsingSHA256},
expectedUserInfoSigAlgsSupported: []string{SigningAlgRSAUsingSHA256, SigningAlgNone},
},
{
desc: "ShouldIncludDiscoveryInfo",
pkcePlainChallenge: false,
clients: map[string]Client{"a": &BaseClient{}},
discovery: schema.OpenIDConnectDiscovery{
RegisteredJWKSigningAlgs: []string{SigningAlgECDSAUsingP521AndSHA512},
},
expectCodeChallengeMethodsSupported: []string{PKCEChallengeMethodSHA256},
expectSubjectTypesSupported: []string{SubjectTypePublic, SubjectTypePairwise},
expectedIDTokenSigAlgsSupported: []string{SigningAlgRSAUsingSHA256, SigningAlgECDSAUsingP521AndSHA512},
expectedUserInfoSigAlgsSupported: []string{SigningAlgRSAUsingSHA256, SigningAlgECDSAUsingP521AndSHA512, SigningAlgNone},
},
{
desc: "ShouldHaveChallengeMethodsS256PlainANDSubjectTypesSupportedPublic",
@ -30,6 +45,8 @@ func TestNewOpenIDConnectWellKnownConfiguration(t *testing.T) {
clients: map[string]Client{"a": &BaseClient{}},
expectCodeChallengeMethodsSupported: []string{PKCEChallengeMethodSHA256, PKCEChallengeMethodPlain},
expectSubjectTypesSupported: []string{SubjectTypePublic, SubjectTypePairwise},
expectedIDTokenSigAlgsSupported: []string{SigningAlgRSAUsingSHA256},
expectedUserInfoSigAlgsSupported: []string{SigningAlgRSAUsingSHA256, SigningAlgNone},
},
{
desc: "ShouldHaveChallengeMethodsS256ANDSubjectTypesSupportedPublicPairwise",
@ -37,6 +54,8 @@ func TestNewOpenIDConnectWellKnownConfiguration(t *testing.T) {
clients: map[string]Client{"a": &BaseClient{SectorIdentifier: "yes"}},
expectCodeChallengeMethodsSupported: []string{PKCEChallengeMethodSHA256},
expectSubjectTypesSupported: []string{SubjectTypePublic, SubjectTypePairwise},
expectedIDTokenSigAlgsSupported: []string{SigningAlgRSAUsingSHA256},
expectedUserInfoSigAlgsSupported: []string{SigningAlgRSAUsingSHA256, SigningAlgNone},
},
{
desc: "ShouldHaveChallengeMethodsS256PlainANDSubjectTypesSupportedPublicPairwise",
@ -44,6 +63,8 @@ func TestNewOpenIDConnectWellKnownConfiguration(t *testing.T) {
clients: map[string]Client{"a": &BaseClient{SectorIdentifier: "yes"}},
expectCodeChallengeMethodsSupported: []string{PKCEChallengeMethodSHA256, PKCEChallengeMethodPlain},
expectSubjectTypesSupported: []string{SubjectTypePublic, SubjectTypePairwise},
expectedIDTokenSigAlgsSupported: []string{SigningAlgRSAUsingSHA256},
expectedUserInfoSigAlgsSupported: []string{SigningAlgRSAUsingSHA256, SigningAlgNone},
},
{
desc: "ShouldHaveTokenAuthMethodsNone",
@ -51,6 +72,8 @@ func TestNewOpenIDConnectWellKnownConfiguration(t *testing.T) {
clients: map[string]Client{"a": &BaseClient{SectorIdentifier: "yes"}},
expectCodeChallengeMethodsSupported: []string{PKCEChallengeMethodSHA256, PKCEChallengeMethodPlain},
expectSubjectTypesSupported: []string{SubjectTypePublic, SubjectTypePairwise},
expectedIDTokenSigAlgsSupported: []string{SigningAlgRSAUsingSHA256},
expectedUserInfoSigAlgsSupported: []string{SigningAlgRSAUsingSHA256, SigningAlgNone},
},
{
desc: "ShouldHaveTokenAuthMethodsNone",
@ -61,6 +84,20 @@ func TestNewOpenIDConnectWellKnownConfiguration(t *testing.T) {
},
expectCodeChallengeMethodsSupported: []string{PKCEChallengeMethodSHA256, PKCEChallengeMethodPlain},
expectSubjectTypesSupported: []string{SubjectTypePublic, SubjectTypePairwise},
expectedIDTokenSigAlgsSupported: []string{SigningAlgRSAUsingSHA256},
expectedUserInfoSigAlgsSupported: []string{SigningAlgRSAUsingSHA256, SigningAlgNone},
},
{
desc: "ShouldHaveTokenAuthMethodsNone",
pkcePlainChallenge: true,
clients: map[string]Client{
"a": &BaseClient{SectorIdentifier: "yes"},
"b": &BaseClient{SectorIdentifier: "yes"},
},
expectCodeChallengeMethodsSupported: []string{PKCEChallengeMethodSHA256, PKCEChallengeMethodPlain},
expectSubjectTypesSupported: []string{SubjectTypePublic, SubjectTypePairwise},
expectedIDTokenSigAlgsSupported: []string{SigningAlgRSAUsingSHA256},
expectedUserInfoSigAlgsSupported: []string{SigningAlgRSAUsingSHA256, SigningAlgNone},
},
}
@ -71,6 +108,7 @@ func TestNewOpenIDConnectWellKnownConfiguration(t *testing.T) {
PAR: schema.OpenIDConnectPARConfiguration{
Enforce: tc.enforcePAR,
},
Discovery: tc.discovery,
}
actual := NewOpenIDConnectWellKnownConfiguration(&c)
@ -89,6 +127,9 @@ func TestNewOpenIDConnectWellKnownConfiguration(t *testing.T) {
for _, subjectType := range actual.SubjectTypesSupported {
assert.Contains(t, tc.expectSubjectTypesSupported, subjectType)
}
assert.Equal(t, tc.expectedUserInfoSigAlgsSupported, actual.UserinfoSigningAlgValuesSupported)
assert.Equal(t, tc.expectedIDTokenSigAlgsSupported, actual.IDTokenSigningAlgValuesSupported)
})
}
}

View File

@ -3,181 +3,398 @@ package oidc
import (
"context"
"crypto"
"crypto/ecdsa"
"crypto/rsa"
"errors"
"fmt"
"sort"
"strings"
"github.com/ory/fosite/token/jwt"
"github.com/golang-jwt/jwt/v4"
fjwt "github.com/ory/fosite/token/jwt"
"github.com/ory/x/errorsx"
"gopkg.in/square/go-jose.v2"
"github.com/authelia/authelia/v4/internal/configuration/schema"
)
// NewKeyManagerWithConfiguration when provided a schema.OpenIDConnectConfiguration creates a new KeyManager and adds an
// initial key to the manager.
func NewKeyManagerWithConfiguration(config *schema.OpenIDConnectConfiguration) (manager *KeyManager, err error) {
manager = NewKeyManager()
if _, err = manager.AddActiveJWK(config.IssuerCertificateChain, config.IssuerPrivateKey); err != nil {
return nil, err
// NewKeyManager news up a KeyManager.
func NewKeyManager(config *schema.OpenIDConnectConfiguration) (manager *KeyManager) {
manager = &KeyManager{
kids: map[string]*JWK{},
algs: map[string]*JWK{},
}
return manager, nil
}
for _, sjwk := range config.IssuerJWKS {
jwk := NewJWK(sjwk)
// NewKeyManager creates a new empty KeyManager.
func NewKeyManager() (manager *KeyManager) {
return &KeyManager{
jwks: &jose.JSONWebKeySet{},
manager.kids[sjwk.KeyID] = jwk
manager.algs[jwk.alg.Alg()] = jwk
if jwk.kid == config.Discovery.DefaultKeyID {
manager.kid = jwk.kid
}
}
return manager
}
// Strategy returns the fosite jwt.JWTStrategy.
func (m *KeyManager) Strategy() (strategy jwt.Signer) {
if m.jwk == nil {
// The KeyManager type handles JWKs and signing operations.
type KeyManager struct {
kid string
kids map[string]*JWK
algs map[string]*JWK
}
func (m *KeyManager) GetKIDFromAlgStrict(ctx context.Context, alg string) (kid string, err error) {
if jwks, ok := m.algs[alg]; ok {
return jwks.kid, nil
}
return "", fmt.Errorf("alg not found")
}
func (m *KeyManager) GetKIDFromAlg(ctx context.Context, alg string) string {
if jwks, ok := m.algs[alg]; ok {
return jwks.kid
}
return m.kid
}
func (m *KeyManager) GetByAlg(ctx context.Context, alg string) *JWK {
if jwk, ok := m.algs[alg]; ok {
return jwk
}
return nil
}
return m.jwk.Strategy()
}
// GetKeySet returns the joseJSONWebKeySet containing the rsa.PublicKey types.
func (m *KeyManager) GetKeySet() (jwks *jose.JSONWebKeySet) {
return m.jwks
func (m *KeyManager) GetByKID(ctx context.Context, kid string) *JWK {
if kid == "" {
return m.kids[m.kid]
}
if jwk, ok := m.kids[kid]; ok {
return jwk
}
return nil
}
// GetActiveJWK obtains the currently active jose.JSONWebKey.
func (m *KeyManager) GetActiveJWK() (jwk *jose.JSONWebKey, err error) {
if m.jwks == nil || m.jwk == nil {
return nil, errors.New("could not obtain the active JWK from an improperly configured key manager")
func (m *KeyManager) GetByHeader(ctx context.Context, header fjwt.Mapper) (jwk *JWK, err error) {
var (
kid string
ok bool
)
if header == nil {
return nil, fmt.Errorf("jwt header was nil")
}
jwks := m.jwks.Key(m.jwk.id)
if len(jwks) == 1 {
return &jwks[0], nil
if kid, ok = header.Get(JWTHeaderKeyIdentifier).(string); !ok {
return nil, fmt.Errorf("jwt header did not have a kid")
}
if len(jwks) == 0 {
return nil, errors.New("could not find a key with the active key id")
if jwk, ok = m.kids[kid]; !ok {
return nil, fmt.Errorf("jwt header '%s' with value '%s' does not match a managed jwk", JWTHeaderKeyIdentifier, kid)
}
return nil, errors.New("multiple keys with the same key id")
return jwk, nil
}
// GetActiveKeyID returns the key id of the currently active key.
func (m *KeyManager) GetActiveKeyID() (keyID string) {
if m.jwk == nil {
return ""
}
func (m *KeyManager) GetByTokenString(ctx context.Context, tokenString string) (jwk *JWK, err error) {
var (
token *jwt.Token
)
return m.jwk.id
}
// GetActivePrivateKey returns the rsa.PrivateKey of the currently active key.
func (m *KeyManager) GetActivePrivateKey() (key *rsa.PrivateKey, err error) {
if m.jwk == nil {
return nil, errors.New("failed to retrieve active private key")
}
return m.jwk.key, nil
}
// AddActiveJWK is used to add a cert and key pair.
func (m *KeyManager) AddActiveJWK(chain schema.X509CertificateChain, key *rsa.PrivateKey) (jwk *JWK, err error) {
// TODO: Add a mutex when implementing key rotation to be utilized here and in methods which retrieve the JWK or JWKS.
if m.jwk, err = NewJWK(chain, key); err != nil {
if token, _, err = jwt.NewParser().ParseUnverified(tokenString, jwt.MapClaims{}); err != nil {
return nil, err
}
m.jwks.Keys = append(m.jwks.Keys, *m.jwk.JSONWebKey())
return m.jwk, nil
return m.GetByHeader(ctx, &fjwt.Headers{Extra: token.Header})
}
// JWTStrategy is a decorator struct for the fosite jwt.JWTStrategy.
type JWTStrategy struct {
jwt.Signer
func (m *KeyManager) Set(ctx context.Context) *jose.JSONWebKeySet {
keys := make([]jose.JSONWebKey, 0, len(m.kids))
id string
}
// KeyID returns the key id.
func (s *JWTStrategy) KeyID() (id string) {
return s.id
}
// GetPublicKeyID is a decorator func for the underlying fosite RS256JWTStrategy.
func (s *JWTStrategy) GetPublicKeyID(_ context.Context) (string, error) {
return s.id, nil
}
// NewJWK creates a new JWK.
func NewJWK(chain schema.X509CertificateChain, key *rsa.PrivateKey) (j *JWK, err error) {
if key == nil {
return nil, fmt.Errorf("JWK is not properly initialized: missing key")
for _, jwk := range m.kids {
keys = append(keys, jwk.JWK())
}
j = &JWK{
key: key,
chain: chain,
sort.Sort(SortedJSONWebKey(keys))
return &jose.JSONWebKeySet{
Keys: keys,
}
jwk := &jose.JSONWebKey{
Algorithm: SigningAlgRSAUsingSHA256,
Use: KeyUseSignature,
Key: &key.PublicKey,
}
var thumbprint []byte
if thumbprint, err = jwk.Thumbprint(crypto.SHA1); err != nil {
return nil, fmt.Errorf("failed to calculate SHA1 thumbprint for certificate: %w", err)
}
j.id = strings.ToLower(fmt.Sprintf("%x", thumbprint))
if len(j.id) >= 7 {
j.id = j.id[:6]
}
if len(j.id) >= 7 {
j.id = j.id[:6]
}
return j, nil
}
// JWK is a utility wrapper for JSON Web Key's.
type JWK struct {
id string
key *rsa.PrivateKey
chain schema.X509CertificateChain
}
func (m *KeyManager) Generate(ctx context.Context, claims fjwt.MapClaims, header fjwt.Mapper) (tokenString string, sig string, err error) {
var jwk *JWK
// Strategy returns the relevant jwt.JWTStrategy for this JWT.
func (j *JWK) Strategy() (strategy jwt.Signer) {
return &JWTStrategy{id: j.id, Signer: &jwt.DefaultSigner{GetPrivateKey: j.GetPrivateKey}}
}
func (j *JWK) GetPrivateKey(ctx context.Context) (key any, err error) {
return j.key, nil
}
// JSONWebKey returns the relevant *jose.JSONWebKey for this JWT.
func (j *JWK) JSONWebKey() (jwk *jose.JSONWebKey) {
jwk = &jose.JSONWebKey{
Key: &j.key.PublicKey,
KeyID: j.id,
Algorithm: SigningAlgRSAUsingSHA256,
Use: KeyUseSignature,
Certificates: j.chain.Certificates(),
if jwk, err = m.GetByHeader(ctx, header); err != nil {
return "", "", fmt.Errorf("error getting jwk from header: %w", err)
}
if len(jwk.Certificates) != 0 {
jwk.CertificateThumbprintSHA1, jwk.CertificateThumbprintSHA256 = j.chain.Thumbprint(crypto.SHA1), j.chain.Thumbprint(crypto.SHA256)
return jwk.Strategy().Generate(ctx, claims, header)
}
func (m *KeyManager) Validate(ctx context.Context, tokenString string) (sig string, err error) {
var jwk *JWK
if jwk, err = m.GetByTokenString(ctx, tokenString); err != nil {
return "", fmt.Errorf("error getting jwk from token string: %w", err)
}
return jwk.Strategy().Validate(ctx, tokenString)
}
func (m *KeyManager) Hash(ctx context.Context, in []byte) (sum []byte, err error) {
return m.GetByKID(ctx, "").Strategy().Hash(ctx, in)
}
func (m *KeyManager) Decode(ctx context.Context, tokenString string) (token *fjwt.Token, err error) {
var jwk *JWK
if jwk, err = m.GetByTokenString(ctx, tokenString); err != nil {
return nil, fmt.Errorf("error getting jwk from token string: %w", err)
}
return jwk.Strategy().Decode(ctx, tokenString)
}
func (m *KeyManager) GetSignature(ctx context.Context, tokenString string) (sig string, err error) {
return getTokenSignature(tokenString)
}
func (m *KeyManager) GetSigningMethodLength(ctx context.Context) (size int) {
return m.GetByKID(ctx, "").Strategy().GetSigningMethodLength(ctx)
}
func NewJWK(s schema.JWK) (jwk *JWK) {
jwk = &JWK{
kid: s.KeyID,
use: s.Use,
alg: jwt.GetSigningMethod(s.Algorithm),
key: s.Key.(schema.CryptographicPrivateKey),
chain: s.CertificateChain,
thumbprint: s.CertificateChain.Thumbprint(crypto.SHA256),
thumbprintsha1: s.CertificateChain.Thumbprint(crypto.SHA1),
}
switch jwk.alg {
case jwt.SigningMethodRS256, jwt.SigningMethodPS256, jwt.SigningMethodES256:
jwk.hash = crypto.SHA256
case jwt.SigningMethodRS384, jwt.SigningMethodPS384, jwt.SigningMethodES384:
jwk.hash = crypto.SHA384
case jwt.SigningMethodRS512, jwt.SigningMethodPS512, jwt.SigningMethodES512:
jwk.hash = crypto.SHA512
default:
jwk.hash = crypto.SHA256
}
return jwk
}
type JWK struct {
kid string
use string
alg jwt.SigningMethod
hash crypto.Hash
key schema.CryptographicPrivateKey
chain schema.X509CertificateChain
thumbprintsha1 []byte
thumbprint []byte
}
func (j *JWK) GetPrivateKey(ctx context.Context) (any, error) {
return j.PrivateJWK(), nil
}
func (j *JWK) KeyID() string {
return j.kid
}
func (j *JWK) PrivateJWK() (jwk *jose.JSONWebKey) {
return &jose.JSONWebKey{
Key: j.key,
KeyID: j.kid,
Algorithm: j.alg.Alg(),
Use: j.use,
Certificates: j.chain.Certificates(),
CertificateThumbprintSHA1: j.thumbprintsha1,
CertificateThumbprintSHA256: j.thumbprint,
}
}
func (j *JWK) JWK() (jwk jose.JSONWebKey) {
return j.PrivateJWK().Public()
}
func (j *JWK) Strategy() (strategy fjwt.Signer) {
return &Signer{
hash: j.hash,
alg: j.alg,
GetPrivateKey: j.GetPrivateKey,
}
}
// Signer is responsible for generating and validating JWT challenges.
type Signer struct {
hash crypto.Hash
alg jwt.SigningMethod
GetPrivateKey fjwt.GetPrivateKeyFunc
}
func (j *Signer) GetPublicKey(ctx context.Context) (key crypto.PublicKey, err error) {
var k any
if k, err = j.GetPrivateKey(ctx); err != nil {
return nil, err
}
switch t := k.(type) {
case *jose.JSONWebKey:
return t.Public().Key, nil
case jose.OpaqueSigner:
return t.Public().Key, nil
case schema.CryptographicPrivateKey:
return t.Public(), nil
default:
return nil, errors.New("invalid private key type")
}
}
// Generate generates a new authorize code or returns an error. set secret.
func (j *Signer) Generate(ctx context.Context, claims fjwt.MapClaims, header fjwt.Mapper) (tokenString string, sig string, err error) {
var key any
if key, err = j.GetPrivateKey(ctx); err != nil {
return "", "", err
}
switch t := key.(type) {
case *jose.JSONWebKey:
return generateToken(claims, header, j.alg, t.Key)
case jose.JSONWebKey:
return generateToken(claims, header, j.alg, t.Key)
case *rsa.PrivateKey, *ecdsa.PrivateKey:
return generateToken(claims, header, j.alg, t)
case jose.OpaqueSigner:
switch tt := t.Public().Key.(type) {
case *rsa.PrivateKey, *ecdsa.PrivateKey:
return generateToken(claims, header, j.alg, t)
default:
return "", "", fmt.Errorf("unsupported private / public key pairs: %T, %T", t, tt)
}
default:
return "", "", fmt.Errorf("unsupported private key type: %T", t)
}
}
// Validate validates a token and returns its signature or an error if the token is not valid.
func (j *Signer) Validate(ctx context.Context, tokenString string) (sig string, err error) {
var (
key crypto.PublicKey
)
if key, err = j.GetPublicKey(ctx); err != nil {
return "", err
}
return validateToken(tokenString, key)
}
// Decode will decode a JWT token.
func (j *Signer) Decode(ctx context.Context, tokenString string) (token *fjwt.Token, err error) {
var (
key crypto.PublicKey
)
if key, err = j.GetPublicKey(ctx); err != nil {
return nil, err
}
return decodeToken(tokenString, key)
}
// GetSignature will return the signature of a token.
func (j *Signer) GetSignature(ctx context.Context, tokenString string) (sig string, err error) {
return getTokenSignature(tokenString)
}
// Hash will return a given hash based on the byte input or an error upon fail.
func (j *Signer) Hash(ctx context.Context, in []byte) (sum []byte, err error) {
hash := j.hash.New()
if _, err = hash.Write(in); err != nil {
return []byte{}, errorsx.WithStack(err)
}
return hash.Sum([]byte{}), nil
}
// GetSigningMethodLength will return the length of the signing method.
func (j *Signer) GetSigningMethodLength(ctx context.Context) (size int) {
return j.hash.Size()
}
func generateToken(claims fjwt.MapClaims, header fjwt.Mapper, signingMethod jwt.SigningMethod, key any) (rawToken string, sig string, err error) {
if header == nil || claims == nil {
return "", "", errors.New("either claims or header is nil")
}
token := jwt.NewWithClaims(signingMethod, claims)
token.Header = assign(token.Header, header.ToMap())
if rawToken, err = token.SignedString(key); err != nil {
return "", "", err
}
if sig, err = getTokenSignature(rawToken); err != nil {
return "", "", err
}
return rawToken, sig, nil
}
func decodeToken(tokenString string, key any) (token *fjwt.Token, err error) {
return fjwt.ParseWithClaims(tokenString, fjwt.MapClaims{}, func(*fjwt.Token) (any, error) {
return key, nil
})
}
func validateToken(tokenString string, key any) (sig string, err error) {
if _, err = decodeToken(tokenString, key); err != nil {
return "", err
}
return getTokenSignature(tokenString)
}
func getTokenSignature(tokenString string) (sig string, err error) {
parts := strings.Split(tokenString, ".")
if len(parts) != 3 {
return "", errors.New("header, body and signature must all be set")
}
return parts[2], nil
}
func assign(a, b map[string]any) map[string]any {
for k, w := range b {
if _, ok := a[k]; ok {
continue
}
a[k] = w
}
return a
}

View File

@ -1,48 +1,450 @@
package oidc
import (
"crypto"
"context"
"encoding/json"
"fmt"
"strings"
"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_AddActiveJWK(t *testing.T) {
manager := NewKeyManager()
assert.Nil(t, manager.jwk)
assert.Nil(t, manager.Strategy())
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,
},
},
}
j, err := manager.AddActiveJWK(schema.X509CertificateChain{}, MustParseRSAPrivateKey(exampleIssuerPrivateKey))
require.NoError(t, err)
require.NotNil(t, j)
require.NotNil(t, manager.jwk)
require.NotNil(t, manager.Strategy())
for i, key := range config.IssuerJWKS {
config.IssuerJWKS[i].KeyID = fmt.Sprintf("kid-%s-%s", key.Algorithm, key.Use)
}
thumbprint, err := j.JSONWebKey().Thumbprint(crypto.SHA1)
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)
kid := strings.ToLower(fmt.Sprintf("%x", thumbprint)[:6])
assert.Equal(t, manager.jwk.id, kid)
assert.Equal(t, kid, j.JSONWebKey().KeyID)
assert.Len(t, manager.jwks.Keys, 1)
keys := manager.jwks.Key(kid)
assert.Equal(t, keys[0].KeyID, kid)
privKey, err := manager.GetActivePrivateKey()
sigb, err = manager.GetSignature(ctx, tokenString)
assert.NoError(t, err)
assert.NotNil(t, privKey)
assert.Equal(t, sig, sigb)
webKey, err := manager.GetActiveJWK()
sigb, err = manager.Validate(ctx, tokenString)
assert.NoError(t, err)
assert.NotNil(t, webKey)
assert.Equal(t, sig, sigb)
keySet := manager.GetKeySet()
assert.NotNil(t, keySet)
assert.Equal(t, kid, manager.GetActiveKeyID())
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())
})
})
}
}

View File

@ -13,34 +13,31 @@ import (
)
// NewOpenIDConnectProvider new-ups a OpenIDConnectProvider.
func NewOpenIDConnectProvider(config *schema.OpenIDConnectConfiguration, store storage.Provider, templates *templates.Provider) (provider *OpenIDConnectProvider, err error) {
func NewOpenIDConnectProvider(config *schema.OpenIDConnectConfiguration, store storage.Provider, templates *templates.Provider) (provider *OpenIDConnectProvider) {
if config == nil {
return nil, nil
return nil
}
provider = &OpenIDConnectProvider{
JSONWriter: herodot.NewJSONWriter(nil),
Store: NewStore(config, store),
KeyManager: NewKeyManager(config),
Config: NewConfig(config, templates),
}
provider.OAuth2Provider = fosite.NewOAuth2Provider(provider.Store, provider.Config)
if provider.KeyManager, err = NewKeyManagerWithConfiguration(config); err != nil {
return nil, err
}
provider.Config.Strategy.OpenID = &openid.DefaultStrategy{
Signer: provider.KeyManager.Strategy(),
Signer: provider.KeyManager,
Config: provider.Config,
}
provider.Config.LoadHandlers(provider.Store, provider.KeyManager.Strategy())
provider.Config.LoadHandlers(provider.Store, provider.KeyManager)
provider.Config.Strategy.ClientAuthentication = provider.DefaultClientAuthenticationStrategy
provider.discovery = NewOpenIDConnectWellKnownConfiguration(config)
return provider, nil
return provider
}
// GetOAuth2WellKnownConfiguration returns the discovery document for the OAuth Configuration.

View File

@ -1,6 +1,7 @@
package oidc
import (
"encoding/json"
"net/url"
"testing"
@ -11,16 +12,15 @@ import (
)
func TestOpenIDConnectProvider_NewOpenIDConnectProvider_NotConfigured(t *testing.T) {
provider, err := NewOpenIDConnectProvider(nil, nil, nil)
provider := NewOpenIDConnectProvider(nil, nil, nil)
assert.NoError(t, err)
assert.Nil(t, provider)
}
func TestNewOpenIDConnectProvider_ShouldEnableOptionalDiscoveryValues(t *testing.T) {
provider, err := NewOpenIDConnectProvider(&schema.OpenIDConnectConfiguration{
provider := NewOpenIDConnectProvider(&schema.OpenIDConnectConfiguration{
IssuerCertificateChain: schema.X509CertificateChain{},
IssuerPrivateKey: MustParseRSAPrivateKey(exampleIssuerPrivateKey),
IssuerPrivateKey: keyRSA2048,
EnablePKCEPlainChallenge: true,
HMACSecret: badhmac,
Clients: []schema.OpenIDConnectClientConfiguration{
@ -36,7 +36,7 @@ func TestNewOpenIDConnectProvider_ShouldEnableOptionalDiscoveryValues(t *testing
},
}, nil, nil)
assert.NoError(t, err)
require.NotNil(t, provider)
disco := provider.GetOpenIDConnectWellKnownConfiguration(examplecom)
@ -50,9 +50,9 @@ func TestNewOpenIDConnectProvider_ShouldEnableOptionalDiscoveryValues(t *testing
}
func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GoodConfiguration(t *testing.T) {
provider, err := NewOpenIDConnectProvider(&schema.OpenIDConnectConfiguration{
provider := NewOpenIDConnectProvider(&schema.OpenIDConnectConfiguration{
IssuerCertificateChain: schema.X509CertificateChain{},
IssuerPrivateKey: MustParseRSAPrivateKey(exampleIssuerPrivateKey),
IssuerPrivateKey: keyRSA2048,
HMACSecret: badhmac,
Clients: []schema.OpenIDConnectClientConfiguration{
{
@ -86,13 +86,12 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GoodConfiguration(t *tes
}, nil, nil)
assert.NotNil(t, provider)
assert.NoError(t, err)
}
func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOpenIDConnectWellKnownConfiguration(t *testing.T) {
provider, err := NewOpenIDConnectProvider(&schema.OpenIDConnectConfiguration{
provider := NewOpenIDConnectProvider(&schema.OpenIDConnectConfiguration{
IssuerCertificateChain: schema.X509CertificateChain{},
IssuerPrivateKey: MustParseRSAPrivateKey(exampleIssuerPrivateKey),
IssuerPrivateKey: keyRSA2048,
HMACSecret: "asbdhaaskmdlkamdklasmdlkams",
Clients: []schema.OpenIDConnectClientConfiguration{
{
@ -106,7 +105,7 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOpenIDConnectWellKnow
},
}, nil, nil)
assert.NoError(t, err)
require.NotNil(t, provider)
disco := provider.GetOpenIDConnectWellKnownConfiguration(examplecom)
@ -153,19 +152,41 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOpenIDConnectWellKnow
assert.Contains(t, disco.TokenEndpointAuthMethodsSupported, ClientAuthMethodClientSecretJWT)
assert.Contains(t, disco.TokenEndpointAuthMethodsSupported, ClientAuthMethodNone)
assert.Len(t, disco.RevocationEndpointAuthMethodsSupported, 4)
assert.Contains(t, disco.RevocationEndpointAuthMethodsSupported, ClientAuthMethodClientSecretBasic)
assert.Contains(t, disco.RevocationEndpointAuthMethodsSupported, ClientAuthMethodClientSecretPost)
assert.Contains(t, disco.RevocationEndpointAuthMethodsSupported, ClientAuthMethodClientSecretJWT)
assert.Contains(t, disco.RevocationEndpointAuthMethodsSupported, ClientAuthMethodNone)
assert.Len(t, disco.IntrospectionEndpointAuthMethodsSupported, 2)
assert.Contains(t, disco.IntrospectionEndpointAuthMethodsSupported, ClientAuthMethodClientSecretBasic)
assert.Contains(t, disco.IntrospectionEndpointAuthMethodsSupported, ClientAuthMethodNone)
assert.Len(t, disco.GrantTypesSupported, 3)
assert.Contains(t, disco.GrantTypesSupported, GrantTypeAuthorizationCode)
assert.Contains(t, disco.GrantTypesSupported, GrantTypeRefreshToken)
assert.Contains(t, disco.GrantTypesSupported, GrantTypeImplicit)
assert.Len(t, disco.RevocationEndpointAuthSigningAlgValuesSupported, 3)
assert.Equal(t, disco.RevocationEndpointAuthSigningAlgValuesSupported[0], SigningAlgHMACUsingSHA256)
assert.Equal(t, disco.RevocationEndpointAuthSigningAlgValuesSupported[1], SigningAlgHMACUsingSHA384)
assert.Equal(t, disco.RevocationEndpointAuthSigningAlgValuesSupported[2], SigningAlgHMACUsingSHA512)
assert.Len(t, disco.TokenEndpointAuthSigningAlgValuesSupported, 3)
assert.Equal(t, disco.TokenEndpointAuthSigningAlgValuesSupported[0], SigningAlgHMACUsingSHA256)
assert.Equal(t, disco.TokenEndpointAuthSigningAlgValuesSupported[1], SigningAlgHMACUsingSHA384)
assert.Equal(t, disco.TokenEndpointAuthSigningAlgValuesSupported[2], SigningAlgHMACUsingSHA512)
assert.Len(t, disco.IDTokenSigningAlgValuesSupported, 1)
assert.Contains(t, disco.IDTokenSigningAlgValuesSupported, SigningAlgRSAUsingSHA256)
assert.Len(t, disco.UserinfoSigningAlgValuesSupported, 2)
assert.Contains(t, disco.UserinfoSigningAlgValuesSupported, SigningAlgRSAUsingSHA256)
assert.Contains(t, disco.UserinfoSigningAlgValuesSupported, SigningAlgNone)
assert.Equal(t, disco.UserinfoSigningAlgValuesSupported[0], SigningAlgRSAUsingSHA256)
assert.Equal(t, disco.UserinfoSigningAlgValuesSupported[1], SigningAlgNone)
assert.Len(t, disco.RequestObjectSigningAlgValuesSupported, 0)
require.Len(t, disco.RequestObjectSigningAlgValuesSupported, 2)
assert.Equal(t, SigningAlgRSAUsingSHA256, disco.RequestObjectSigningAlgValuesSupported[0])
assert.Equal(t, SigningAlgNone, disco.RequestObjectSigningAlgValuesSupported[1])
assert.Len(t, disco.ClaimsSupported, 18)
assert.Contains(t, disco.ClaimsSupported, ClaimAuthenticationMethodsReference)
@ -186,12 +207,16 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOpenIDConnectWellKnow
assert.Contains(t, disco.ClaimsSupported, ClaimGroups)
assert.Contains(t, disco.ClaimsSupported, ClaimPreferredUsername)
assert.Contains(t, disco.ClaimsSupported, ClaimFullName)
assert.Len(t, disco.PromptValuesSupported, 2)
assert.Contains(t, disco.PromptValuesSupported, PromptConsent)
assert.Contains(t, disco.PromptValuesSupported, PromptNone)
}
func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOAuth2WellKnownConfiguration(t *testing.T) {
provider, err := NewOpenIDConnectProvider(&schema.OpenIDConnectConfiguration{
provider := NewOpenIDConnectProvider(&schema.OpenIDConnectConfiguration{
IssuerCertificateChain: schema.X509CertificateChain{},
IssuerPrivateKey: MustParseRSAPrivateKey(exampleIssuerPrivateKey),
IssuerPrivateKey: keyRSA2048,
HMACSecret: "asbdhaaskmdlkamdklasmdlkams",
Clients: []schema.OpenIDConnectClientConfiguration{
{
@ -205,7 +230,7 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOAuth2WellKnownConfig
},
}, nil, nil)
assert.NoError(t, err)
require.NotNil(t, provider)
disco := provider.GetOAuth2WellKnownConfiguration(examplecom)
@ -278,9 +303,9 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOAuth2WellKnownConfig
}
func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOpenIDConnectWellKnownConfigurationWithPlainPKCE(t *testing.T) {
provider, err := NewOpenIDConnectProvider(&schema.OpenIDConnectConfiguration{
provider := NewOpenIDConnectProvider(&schema.OpenIDConnectConfiguration{
IssuerCertificateChain: schema.X509CertificateChain{},
IssuerPrivateKey: MustParseRSAPrivateKey(exampleIssuerPrivateKey),
IssuerPrivateKey: keyRSA2048,
HMACSecret: "asbdhaaskmdlkamdklasmdlkams",
EnablePKCEPlainChallenge: true,
Clients: []schema.OpenIDConnectClientConfiguration{
@ -295,7 +320,7 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOpenIDConnectWellKnow
},
}, nil, nil)
assert.NoError(t, err)
require.NotNil(t, provider)
disco := provider.GetOpenIDConnectWellKnownConfiguration(examplecom)
@ -303,3 +328,44 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOpenIDConnectWellKnow
assert.Equal(t, PKCEChallengeMethodSHA256, disco.CodeChallengeMethodsSupported[0])
assert.Equal(t, PKCEChallengeMethodPlain, disco.CodeChallengeMethodsSupported[1])
}
func TestNewOpenIDConnectProviderDiscovery(t *testing.T) {
provider := NewOpenIDConnectProvider(&schema.OpenIDConnectConfiguration{
IssuerCertificateChain: schema.X509CertificateChain{},
IssuerPrivateKey: keyRSA2048,
HMACSecret: "asbdhaaskmdlkamdklasmdlkams",
EnablePKCEPlainChallenge: true,
Clients: []schema.OpenIDConnectClientConfiguration{
{
ID: "a-client",
Secret: MustDecodeSecret("$plaintext$a-client-secret"),
Policy: onefactor,
RedirectURIs: []string{
"https://google.com",
},
},
},
}, nil, nil)
a := provider.GetOpenIDConnectWellKnownConfiguration("https://auth.example.com")
data, err := json.Marshal(&a)
assert.NoError(t, err)
b := OpenIDConnectWellKnownConfiguration{}
assert.NoError(t, json.Unmarshal(data, &b))
assert.Equal(t, a, b)
y := provider.GetOAuth2WellKnownConfiguration("https://auth.example.com")
data, err = json.Marshal(&y)
assert.NoError(t, err)
z := OAuth2WellKnownConfiguration{}
assert.NoError(t, json.Unmarshal(data, &z))
assert.Equal(t, y, z)
}

View File

@ -15,7 +15,7 @@ import (
func TestOpenIDConnectStore_GetClientPolicy(t *testing.T) {
s := NewStore(&schema.OpenIDConnectConfiguration{
IssuerCertificateChain: schema.X509CertificateChain{},
IssuerPrivateKey: MustParseRSAPrivateKey(exampleIssuerPrivateKey),
IssuerPrivateKey: keyRSA2048,
Clients: []schema.OpenIDConnectClientConfiguration{
{
ID: myclient,
@ -47,7 +47,7 @@ func TestOpenIDConnectStore_GetClientPolicy(t *testing.T) {
func TestOpenIDConnectStore_GetInternalClient(t *testing.T) {
s := NewStore(&schema.OpenIDConnectConfiguration{
IssuerCertificateChain: schema.X509CertificateChain{},
IssuerPrivateKey: MustParseRSAPrivateKey(exampleIssuerPrivateKey),
IssuerPrivateKey: keyRSA2048,
Clients: []schema.OpenIDConnectClientConfiguration{
{
ID: myclient,
@ -82,7 +82,7 @@ func TestOpenIDConnectStore_GetInternalClient_ValidClient(t *testing.T) {
s := NewStore(&schema.OpenIDConnectConfiguration{
IssuerCertificateChain: schema.X509CertificateChain{},
IssuerPrivateKey: MustParseRSAPrivateKey(exampleIssuerPrivateKey),
IssuerPrivateKey: keyRSA2048,
Clients: []schema.OpenIDConnectClientConfiguration{c1},
}, nil)
@ -110,7 +110,7 @@ func TestOpenIDConnectStore_GetInternalClient_InvalidClient(t *testing.T) {
s := NewStore(&schema.OpenIDConnectConfiguration{
IssuerCertificateChain: schema.X509CertificateChain{},
IssuerPrivateKey: MustParseRSAPrivateKey(exampleIssuerPrivateKey),
IssuerPrivateKey: keyRSA2048,
Clients: []schema.OpenIDConnectClientConfiguration{c1},
}, nil)
@ -122,7 +122,7 @@ func TestOpenIDConnectStore_GetInternalClient_InvalidClient(t *testing.T) {
func TestOpenIDConnectStore_IsValidClientID(t *testing.T) {
s := NewStore(&schema.OpenIDConnectConfiguration{
IssuerCertificateChain: schema.X509CertificateChain{},
IssuerPrivateKey: MustParseRSAPrivateKey(exampleIssuerPrivateKey),
IssuerPrivateKey: keyRSA2048,
Clients: []schema.OpenIDConnectClientConfiguration{
{
ID: myclient,

View File

@ -122,7 +122,8 @@ type BaseClient struct {
ResponseTypes []string
ResponseModes []fosite.ResponseModeType
UserinfoSigningAlgorithm string
IDTokenSigningAlg string
UserinfoSigningAlg string
Policy authorization.Level
@ -150,7 +151,9 @@ type Client interface {
GetSecret() algorithm.Digest
GetSectorIdentifier() string
GetConsentResponseBody(consent *model.OAuth2ConsentSession) ConsentGetResponseBody
GetUserinfoSigningAlgorithm() string
GetUserinfoSigningAlg() string
GetIDTokenSigningAlg() string
GetPAREnforcement() bool
GetPKCEEnforcement() bool
@ -222,13 +225,6 @@ func (c ClientConsentMode) String() string {
}
}
// KeyManager keeps track of all of the active/inactive rsa keys and provides them to services requiring them.
// It additionally allows us to add keys for the purpose of key rotation in the future.
type KeyManager struct {
jwk *JWK
jwks *jose.JSONWebKeySet
}
// ConsentGetResponseBody schema of the response body of the consent GET endpoint.
type ConsentGetResponseBody struct {
ClientID string `json:"client_id"`

View File

@ -4,9 +4,101 @@ import (
"strings"
"github.com/ory/fosite"
"gopkg.in/square/go-jose.v2"
)
// IsPushedAuthorizedRequest returns true if the requester has a PushedAuthorizationRequest redirect_uri value.
func IsPushedAuthorizedRequest(r fosite.Requester, prefix string) bool {
return strings.HasPrefix(r.GetRequestForm().Get(FormParameterRequestURI), prefix)
}
// SortedSigningAlgs is a sorting type which allows the use of sort.Sort to order a list of OAuth 2.0 Signing Algs.
// Sorting occurs in the order of from within the RFC's.
type SortedSigningAlgs []string
func (algs SortedSigningAlgs) Len() int {
return len(algs)
}
func (algs SortedSigningAlgs) Less(i, j int) bool {
return isSigningAlgLess(algs[i], algs[j])
}
func (algs SortedSigningAlgs) Swap(i, j int) {
algs[i], algs[j] = algs[j], algs[i]
}
type SortedJSONWebKey []jose.JSONWebKey
func (jwks SortedJSONWebKey) Len() int {
return len(jwks)
}
func (jwks SortedJSONWebKey) Less(i, j int) bool {
if jwks[i].Algorithm == jwks[j].Algorithm {
return jwks[i].KeyID < jwks[j].KeyID
}
return isSigningAlgLess(jwks[i].Algorithm, jwks[j].Algorithm)
}
func (jwks SortedJSONWebKey) Swap(i, j int) {
jwks[i], jwks[j] = jwks[j], jwks[i]
}
//nolint:gocyclo // Low importance func.
func isSigningAlgLess(i, j string) bool {
switch {
case i == j:
return false
case i == SigningAlgNone:
return false
case j == SigningAlgNone:
return true
default:
var (
ip, jp string
it, jt bool
)
if len(i) > 2 {
it = true
ip = i[:2]
}
if len(j) > 2 {
jt = true
jp = j[:2]
}
switch {
case it && jt && ip == jp:
return i < j
case ip == SigningAlgPrefixHMAC:
return true
case jp == SigningAlgPrefixHMAC:
return false
case ip == SigningAlgPrefixRSAPSS:
return false
case jp == SigningAlgPrefixRSAPSS:
return true
case ip == SigningAlgPrefixRSA:
return true
case jp == SigningAlgPrefixRSA:
return false
case ip == SigningAlgPrefixECDSA:
return true
case jp == SigningAlgPrefixECDSA:
return false
default:
return false
}
}
}
const (
SigningAlgPrefixRSA = "RS"
SigningAlgPrefixHMAC = "HS"
SigningAlgPrefixRSAPSS = "PS"
SigningAlgPrefixECDSA = "ES"
)

View File

@ -31,6 +31,7 @@ const (
BlockTypePKIXPublicKey = "PUBLIC KEY"
BlockTypeCertificate = "CERTIFICATE"
BlockTypeCertificateRequest = "CERTIFICATE REQUEST"
BlockTypeX509CRL = "X509 CRL"
KeyAlgorithmRSA = "RSA"
KeyAlgorithmECDSA = "ECDSA"

View File

@ -13,6 +13,7 @@ import (
"encoding/pem"
"errors"
"fmt"
"io"
"math/big"
"net"
"os"
@ -188,28 +189,95 @@ func ParseX509FromPEM(data []byte) (key any, err error) {
return nil, errors.New("failed to parse PEM block containing the key")
}
return ParsePEMBlock(block)
}
// ParseX509FromPEMRecursive allows returning the appropriate key type given some PEM encoded input.
// For Keys this is a single value of one of *rsa.PrivateKey, *rsa.PublicKey, *ecdsa.PrivateKey, *ecdsa.PublicKey,
// ed25519.PrivateKey, or ed25519.PublicKey. For certificates this is
// either a *X509.Certificate, or a []*X509.Certificate.
func ParseX509FromPEMRecursive(data []byte) (decoded any, err error) {
var (
block *pem.Block
multi bool
certificates []*x509.Certificate
)
for i := 0; true; i++ {
block, data = pem.Decode(data)
n := len(data)
switch {
case block == nil:
return nil, fmt.Errorf("failed to parse PEM blocks: data does not appear to be PEM encoded")
case multi || n != 0:
switch block.Type {
case BlockTypeCertificate:
var certificate *x509.Certificate
if certificate, err = x509.ParseCertificate(block.Bytes); err != nil {
return nil, fmt.Errorf("failed to parse PEM blocks: data contains multiple blocks but #%d had an error during parsing: %w", i, err)
}
certificates = append(certificates, certificate)
default:
return nil, fmt.Errorf("failed to parse PEM blocks: data contains multiple blocks but #%d has a '%s' block type and should have a '%s' block type", i, block.Type, BlockTypeCertificate)
}
multi = true
default:
if decoded, err = ParsePEMBlock(block); err != nil {
return nil, err
}
}
if n == 0 {
break
}
}
switch {
case multi:
return certificates, nil
default:
return decoded, nil
}
}
// ParsePEMBlock parses a single PEM block into the relevant X509 data struct.
func ParsePEMBlock(block *pem.Block) (key any, err error) {
if block == nil {
return nil, errors.New("failed to parse PEM block as it was empty")
}
switch block.Type {
case BlockTypeRSAPrivateKey:
key, err = x509.ParsePKCS1PrivateKey(block.Bytes)
return x509.ParsePKCS1PrivateKey(block.Bytes)
case BlockTypeECDSAPrivateKey:
key, err = x509.ParseECPrivateKey(block.Bytes)
return x509.ParseECPrivateKey(block.Bytes)
case BlockTypePKCS8PrivateKey:
key, err = x509.ParsePKCS8PrivateKey(block.Bytes)
return x509.ParsePKCS8PrivateKey(block.Bytes)
case BlockTypeRSAPublicKey:
key, err = x509.ParsePKCS1PublicKey(block.Bytes)
return x509.ParsePKCS1PublicKey(block.Bytes)
case BlockTypePKIXPublicKey:
key, err = x509.ParsePKIXPublicKey(block.Bytes)
return x509.ParsePKIXPublicKey(block.Bytes)
case BlockTypeCertificate:
key, err = x509.ParseCertificate(block.Bytes)
return x509.ParseCertificate(block.Bytes)
case BlockTypeCertificateRequest:
return x509.ParseCertificateRequest(block.Bytes)
case BlockTypeX509CRL:
return x509.ParseRevocationList(block.Bytes)
default:
switch {
case strings.Contains(block.Type, "PRIVATE KEY"):
return x509.ParsePKCS8PrivateKey(block.Bytes)
case strings.Contains(block.Type, "PUBLIC KEY"):
return x509.ParsePKIXPublicKey(block.Bytes)
default:
return nil, fmt.Errorf("unknown block type: %s", block.Type)
}
if err != nil {
return nil, err
}
return key, nil
}
// CastX509AsCertificate converts an interface to an *x509.Certificate.
@ -306,8 +374,25 @@ func NewX509CertPool(directory string) (certPool *x509.CertPool, warnings []erro
return certPool, warnings, errors
}
// WriteCertificateBytesToPEM writes a certificate/csr to a file in the PEM format.
func WriteCertificateBytesToPEM(path string, csr bool, certs ...[]byte) (err error) {
// WriteCertificateBytesAsPEMToPath writes a certificate/csr to a file in the PEM format.
func WriteCertificateBytesAsPEMToPath(path string, csr bool, certs ...[]byte) (err error) {
var out *os.File
if out, err = os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600); err != nil {
return err
}
if err = WriteCertificateBytesAsPEMToWriter(out, csr, certs...); err != nil {
_ = out.Close()
return err
}
return nil
}
// WriteCertificateBytesAsPEMToWriter writes a certificate/csr to a io.Writer in the PEM format.
func WriteCertificateBytesAsPEMToWriter(wr io.Writer, csr bool, certs ...[]byte) (err error) {
blockType := BlockTypeCertificate
if csr {
blockType = BlockTypeCertificateRequest
@ -319,28 +404,36 @@ func WriteCertificateBytesToPEM(path string, csr bool, certs ...[]byte) (err err
blocks[i] = &pem.Block{Type: blockType, Bytes: cert}
}
return WritePEM(path, blocks...)
return WritePEMBlocksToWriter(wr, blocks...)
}
// WritePEM writes a set of *pem.Blocks to a file.
func WritePEM(path string, blocks ...*pem.Block) (err error) {
// WritePEMBlocksToPath writes a set of *pem.Blocks to a file.
func WritePEMBlocksToPath(path string, blocks ...*pem.Block) (err error) {
var out *os.File
if out, err = os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600); err != nil {
return err
}
for _, block := range blocks {
if err = pem.Encode(out, block); err != nil {
if err = WritePEMBlocksToWriter(out, blocks...); err != nil {
_ = out.Close()
return err
}
}
return out.Close()
}
func WritePEMBlocksToWriter(wr io.Writer, blocks ...*pem.Block) (err error) {
for _, block := range blocks {
if err = pem.Encode(wr, block); err != nil {
return err
}
}
return nil
}
// WriteKeyToPEM writes a key that can be encoded as a PEM to a file in the PEM format.
func WriteKeyToPEM(key any, path string, pkcs8 bool) (err error) {
block, err := PEMBlockFromX509Key(key, pkcs8)
@ -348,7 +441,7 @@ func WriteKeyToPEM(key any, path string, pkcs8 bool) (err error) {
return err
}
return WritePEM(path, block)
return WritePEMBlocksToPath(path, block)
}
// PEMBlockFromX509Key turns a PublicKey or PrivateKey into a pem.Block.