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
parent
435d8e35fd
commit
602041d37d
|
@ -28,12 +28,12 @@ More information about the beta can be found in the [roadmap](../../roadmap/acti
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
{{< config-alert-example >}}
|
The following snippet provides a sample-configuration for the OIDC identity provider explaining each field in detail.
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
identity_providers:
|
identity_providers:
|
||||||
oidc:
|
oidc:
|
||||||
hmac_secret: 'this_is_a_secret_abc123abc123abc'
|
hmac_secret: this_is_a_secret_abc123abc123abc
|
||||||
issuer_certificate_chain: |
|
issuer_certificate_chain: |
|
||||||
-----BEGIN CERTIFICATE-----
|
-----BEGIN CERTIFICATE-----
|
||||||
MIIC5jCCAc6gAwIBAgIRAK4Sj7FiN6PXo/urPfO4E7owDQYJKoZIhvcNAQELBQAw
|
MIIC5jCCAc6gAwIBAgIRAK4Sj7FiN6PXo/urPfO4E7owDQYJKoZIhvcNAQELBQAw
|
||||||
|
@ -101,54 +101,59 @@ identity_providers:
|
||||||
27GoE2i5mh6Yez6VAYbUuns3FcwIsMyWLq043Tu2DNkx9ijOOAuQzw^invalid..
|
27GoE2i5mh6Yez6VAYbUuns3FcwIsMyWLq043Tu2DNkx9ijOOAuQzw^invalid..
|
||||||
DO NOT USE==
|
DO NOT USE==
|
||||||
-----END RSA PRIVATE KEY-----
|
-----END RSA PRIVATE KEY-----
|
||||||
access_token_lifespan: '1h'
|
issuer_jwks:
|
||||||
authorize_code_lifespan: '1m'
|
- key_id: ''
|
||||||
id_token_lifespan: '1h'
|
algorithm: 'RS256'
|
||||||
refresh_token_lifespan: '90m'
|
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
|
enable_client_debug_messages: false
|
||||||
enforce_pkce: 'public_clients_only'
|
enforce_pkce: public_clients_only
|
||||||
cors:
|
cors:
|
||||||
endpoints:
|
endpoints:
|
||||||
- 'authorization'
|
- authorization
|
||||||
- 'token'
|
- token
|
||||||
- 'revocation'
|
- revocation
|
||||||
- 'introspection'
|
- introspection
|
||||||
allowed_origins:
|
allowed_origins:
|
||||||
- 'https://example.com'
|
- https://example.com
|
||||||
allowed_origins_from_client_redirect_uris: false
|
allowed_origins_from_client_redirect_uris: false
|
||||||
clients:
|
clients:
|
||||||
- id: 'myapp'
|
- id: myapp
|
||||||
description: 'My Application'
|
description: My Application
|
||||||
secret: '$pbkdf2-sha512$310000$c8p78n7pUMln0jzvd4aK4Q$JNRBzwAo0ek5qKn50cFzzvE9RXV88h1wJn5KGiHrD0YKtZaR/nCb2CJPOsKaPK0hjf.9yHxzQGZziziccp6Yng' # The digest of 'insecure_secret'.
|
secret: '$pbkdf2-sha512$310000$c8p78n7pUMln0jzvd4aK4Q$JNRBzwAo0ek5qKn50cFzzvE9RXV88h1wJn5KGiHrD0YKtZaR/nCb2CJPOsKaPK0hjf.9yHxzQGZziziccp6Yng' # The digest of 'insecure_secret'.
|
||||||
sector_identifier: ''
|
sector_identifier: ''
|
||||||
public: false
|
public: false
|
||||||
authorization_policy: 'two_factor'
|
authorization_policy: two_factor
|
||||||
consent_mode: 'explicit'
|
consent_mode: explicit
|
||||||
pre_configured_consent_duration: '1w'
|
pre_configured_consent_duration: 1w
|
||||||
audience: []
|
audience: []
|
||||||
scopes:
|
scopes:
|
||||||
- 'openid'
|
- openid
|
||||||
- 'groups'
|
- groups
|
||||||
- 'email'
|
- email
|
||||||
- 'profile'
|
- profile
|
||||||
redirect_uris:
|
redirect_uris:
|
||||||
- 'https://oidc.example.com:8080/oauth2/callback'
|
- https://oidc.example.com:8080/oauth2/callback
|
||||||
grant_types:
|
grant_types:
|
||||||
- 'refresh_token'
|
- refresh_token
|
||||||
- 'authorization_code'
|
- authorization_code
|
||||||
response_types:
|
response_types:
|
||||||
- 'code'
|
- code
|
||||||
response_modes:
|
response_modes:
|
||||||
- 'form_post'
|
- form_post
|
||||||
- 'query'
|
- query
|
||||||
- 'fragment'
|
- fragment
|
||||||
userinfo_signing_algorithm: 'none'
|
userinfo_signing_algorithm: none
|
||||||
```
|
```
|
||||||
|
|
||||||
## Options
|
## Options
|
||||||
|
|
||||||
This section describes the individual configuration options.
|
|
||||||
|
|
||||||
### hmac_secret
|
### hmac_secret
|
||||||
|
|
||||||
{{< confkey type="string" required="yes" >}}
|
{{< 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)
|
[Random Alphanumeric String](../../reference/guides/generating-secure-values.md#generating-a-random-alphanumeric-string)
|
||||||
with 64 or more characters.
|
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
|
### issuer_private_key
|
||||||
|
|
||||||
{{< confkey type="string" required="yes" >}}
|
{{< 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
|
If the [issuer_certificate_chain](#issuercertificatechain) is provided the private key must include matching public
|
||||||
key data for the first certificate in the chain.
|
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
|
### access_token_lifespan
|
||||||
|
|
||||||
{{< confkey type="duration" default="1h" required="no" >}}
|
{{< 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.
|
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].
|
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" >}}
|
{{< 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
|
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].
|
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" >}}
|
{{< 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].
|
The maximum lifetime of an ID token. For more information read these docs about [token lifespan].
|
||||||
|
|
||||||
### refresh_token_lifespan
|
### refresh_token_lifespan
|
||||||
|
|
||||||
{{< confkey type="string" default="90m" required="no" >}}
|
{{< 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
|
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
|
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].
|
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" >}}
|
{{< 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
|
The maximum amount of time between the [Pushed Authorization Requests] flow being initiated and the generated
|
||||||
`request_uri` being utilized by a client.
|
`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" >}}
|
{{< confkey type="duration" default="1w" required="no" >}}
|
||||||
|
|
||||||
*__Reference Note:__ This configuration option uses the [duration common syntax](../prologue/common.md#duration).
|
*__Note:__ This setting uses the [duration notation format](../prologue/common.md#duration-notation-format). Please see
|
||||||
Please see the [documentation](../prologue/common.md#duration) on this format for more information.*
|
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
|
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`.
|
specified as well as the [consent_mode] then it only has an effect if the [consent_mode] is `pre-configured` or `auto`.
|
||||||
|
|
|
@ -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 |
|
| preferred_username | string | username | The username the user used to login with |
|
||||||
| name | string | display_name | The users display name |
|
| 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
|
## Parameters
|
||||||
|
|
||||||
The following section describes advanced parameters which can be used in various endpoints as well as their related
|
The following section describes advanced parameters which can be used in various endpoints as well as their related
|
||||||
|
|
|
@ -162,9 +162,7 @@ func (ctx *CmdCtx) LoadProviders() (warns, errs []error) {
|
||||||
ctx.providers.Notifier = notification.NewFileNotifier(*ctx.config.Notifier.FileSystem)
|
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 {
|
ctx.providers.OpenIDConnect = oidc.NewOpenIDConnectProvider(ctx.config.IdentityProviders.OIDC, ctx.providers.StorageProvider, ctx.providers.Templates)
|
||||||
errs = append(errs, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if ctx.config.Telemetry.Metrics.Enabled {
|
if ctx.config.Telemetry.Metrics.Enabled {
|
||||||
ctx.providers.Metrics = metrics.NewPrometheus()
|
ctx.providers.Metrics = metrics.NewPrometheus()
|
||||||
|
|
|
@ -335,7 +335,7 @@ func (ctx *CmdCtx) CryptoCertificateRequestRunE(cmd *cobra.Command, _ []string)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = utils.WriteCertificateBytesToPEM(csrPath, true, csr); err != nil {
|
if err = utils.WriteCertificateBytesAsPEMToPath(csrPath, true, csr); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -430,7 +430,7 @@ func (ctx *CmdCtx) CryptoCertificateGenerateRunE(cmd *cobra.Command, _ []string,
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = utils.WriteCertificateBytesToPEM(certificatePath, false, certificate); err != nil {
|
if err = utils.WriteCertificateBytesAsPEMToPath(certificatePath, false, certificate); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -198,7 +198,7 @@ func cryptoGenerateCertificateBundlesFromCmd(cmd *cobra.Command, b *strings.Buil
|
||||||
|
|
||||||
b.WriteString(fmt.Sprintf("\tCertificate (chain): %s\n", pathPEM))
|
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
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -220,7 +220,7 @@ func cryptoGenerateCertificateBundlesFromCmd(cmd *cobra.Command, b *strings.Buil
|
||||||
|
|
||||||
b.WriteString(fmt.Sprintf("\tCertificate (priv-chain): %s\n", pathPEM))
|
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
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.
|
// StringToPrivateKeyHookFunc decodes strings to rsa.PrivateKey's.
|
||||||
func StringToPrivateKeyHookFunc() mapstructure.DecodeHookFuncType {
|
func StringToPrivateKeyHookFunc() mapstructure.DecodeHookFuncType {
|
||||||
return func(f reflect.Type, t reflect.Type, data any) (value any, err error) {
|
return func(f reflect.Type, t reflect.Type, data any) (value any, err error) {
|
||||||
|
|
|
@ -21,6 +21,7 @@ func TestKoanfEnvironmentCallback(t *testing.T) {
|
||||||
keyMap := map[string]string{
|
keyMap := map[string]string{
|
||||||
DefaultEnvPrefix + "KEY_EXAMPLE_UNDERSCORE": "key.example_underscore",
|
DefaultEnvPrefix + "KEY_EXAMPLE_UNDERSCORE": "key.example_underscore",
|
||||||
}
|
}
|
||||||
|
|
||||||
ignoredKeys := []string{DefaultEnvPrefix + "SOME_SECRET"}
|
ignoredKeys := []string{DefaultEnvPrefix + "SOME_SECRET"}
|
||||||
|
|
||||||
callback := koanfEnvironmentCallback(keyMap, ignoredKeys, DefaultEnvPrefix, DefaultEnvDelimiter)
|
callback := koanfEnvironmentCallback(keyMap, ignoredKeys, DefaultEnvPrefix, DefaultEnvDelimiter)
|
||||||
|
|
|
@ -65,6 +65,7 @@ func unmarshal(ko *koanf.Koanf, val *schema.StructValidator, path string, o any)
|
||||||
StringToX509CertificateChainHookFunc(),
|
StringToX509CertificateChainHookFunc(),
|
||||||
StringToPrivateKeyHookFunc(),
|
StringToPrivateKeyHookFunc(),
|
||||||
StringToCryptoPrivateKeyHookFunc(),
|
StringToCryptoPrivateKeyHookFunc(),
|
||||||
|
StringToCryptographicKeyHookFunc(),
|
||||||
StringToTLSVersionHookFunc(),
|
StringToTLSVersionHookFunc(),
|
||||||
StringToPasswordDigestHookFunc(),
|
StringToPasswordDigestHookFunc(),
|
||||||
ToTimeDurationHookFunc(),
|
ToTimeDurationHookFunc(),
|
||||||
|
|
|
@ -245,6 +245,37 @@ func TestShouldLoadURLList(t *testing.T) {
|
||||||
assert.Equal(t, "https://example.com", config.IdentityProviders.OIDC.CORS.AllowedOrigins[1].String())
|
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) {
|
func TestShouldConfigureConsent(t *testing.T) {
|
||||||
val := schema.NewStructValidator()
|
val := schema.NewStructValidator()
|
||||||
keys, config, err := Load(val, NewDefaultSources([]string{"./test_resources/config_oidc.yml"}, DefaultEnvPrefix, DefaultEnvDelimiter)...)
|
keys, config, err := Load(val, NewDefaultSources([]string{"./test_resources/config_oidc.yml"}, DefaultEnvPrefix, DefaultEnvDelimiter)...)
|
||||||
|
|
|
@ -17,6 +17,8 @@ type OpenIDConnectConfiguration struct {
|
||||||
IssuerCertificateChain X509CertificateChain `koanf:"issuer_certificate_chain"`
|
IssuerCertificateChain X509CertificateChain `koanf:"issuer_certificate_chain"`
|
||||||
IssuerPrivateKey *rsa.PrivateKey `koanf:"issuer_private_key"`
|
IssuerPrivateKey *rsa.PrivateKey `koanf:"issuer_private_key"`
|
||||||
|
|
||||||
|
IssuerJWKS []JWK `koanf:"issuer_jwks"`
|
||||||
|
|
||||||
AccessTokenLifespan time.Duration `koanf:"access_token_lifespan"`
|
AccessTokenLifespan time.Duration `koanf:"access_token_lifespan"`
|
||||||
AuthorizeCodeLifespan time.Duration `koanf:"authorize_code_lifespan"`
|
AuthorizeCodeLifespan time.Duration `koanf:"authorize_code_lifespan"`
|
||||||
IDTokenLifespan time.Duration `koanf:"id_token_lifespan"`
|
IDTokenLifespan time.Duration `koanf:"id_token_lifespan"`
|
||||||
|
@ -32,6 +34,13 @@ type OpenIDConnectConfiguration struct {
|
||||||
PAR OpenIDConnectPARConfiguration `koanf:"pushed_authorizations"`
|
PAR OpenIDConnectPARConfiguration `koanf:"pushed_authorizations"`
|
||||||
|
|
||||||
Clients []OpenIDConnectClientConfiguration `koanf:"clients"`
|
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.
|
// OpenIDConnectPARConfiguration represents an OpenID Connect PAR config.
|
||||||
|
@ -67,13 +76,15 @@ type OpenIDConnectClientConfiguration struct {
|
||||||
TokenEndpointAuthMethod string `koanf:"token_endpoint_auth_method"`
|
TokenEndpointAuthMethod string `koanf:"token_endpoint_auth_method"`
|
||||||
TokenEndpointAuthSigningAlg string `koanf:"token_endpoint_auth_signing_alg"`
|
TokenEndpointAuthSigningAlg string `koanf:"token_endpoint_auth_signing_alg"`
|
||||||
|
|
||||||
|
IDTokenSigningAlg string `koanf:"id_token_signing_alg"`
|
||||||
|
|
||||||
Policy string `koanf:"authorization_policy"`
|
Policy string `koanf:"authorization_policy"`
|
||||||
|
|
||||||
EnforcePAR bool `koanf:"enforce_par"`
|
EnforcePAR bool `koanf:"enforce_par"`
|
||||||
EnforcePKCE bool `koanf:"enforce_pkce"`
|
EnforcePKCE bool `koanf:"enforce_pkce"`
|
||||||
|
|
||||||
PKCEChallengeMethod string `koanf:"pkce_challenge_method"`
|
PKCEChallengeMethod string `koanf:"pkce_challenge_method"`
|
||||||
UserinfoSigningAlgorithm string `koanf:"userinfo_signing_algorithm"`
|
UserinfoSigningAlg string `koanf:"userinfo_signing_algorithm"`
|
||||||
|
|
||||||
ConsentMode string `koanf:"consent_mode"`
|
ConsentMode string `koanf:"consent_mode"`
|
||||||
ConsentPreConfiguredDuration *time.Duration `koanf:"pre_configured_consent_duration"`
|
ConsentPreConfiguredDuration *time.Duration `koanf:"pre_configured_consent_duration"`
|
||||||
|
@ -97,7 +108,8 @@ var DefaultOpenIDConnectClientConfiguration = OpenIDConnectClientConfiguration{
|
||||||
ResponseTypes: []string{"code"},
|
ResponseTypes: []string{"code"},
|
||||||
ResponseModes: []string{"form_post"},
|
ResponseModes: []string{"form_post"},
|
||||||
|
|
||||||
UserinfoSigningAlgorithm: "none",
|
IDTokenSigningAlg: "RS256",
|
||||||
|
UserinfoSigningAlg: "none",
|
||||||
ConsentMode: "auto",
|
ConsentMode: "auto",
|
||||||
ConsentPreConfiguredDuration: &defaultOIDCClientConsentPreConfiguredDuration,
|
ConsentPreConfiguredDuration: &defaultOIDCClientConsentPreConfiguredDuration,
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,12 @@ var Keys = []string{
|
||||||
"identity_providers.oidc.hmac_secret",
|
"identity_providers.oidc.hmac_secret",
|
||||||
"identity_providers.oidc.issuer_certificate_chain",
|
"identity_providers.oidc.issuer_certificate_chain",
|
||||||
"identity_providers.oidc.issuer_private_key",
|
"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.access_token_lifespan",
|
||||||
"identity_providers.oidc.authorize_code_lifespan",
|
"identity_providers.oidc.authorize_code_lifespan",
|
||||||
"identity_providers.oidc.id_token_lifespan",
|
"identity_providers.oidc.id_token_lifespan",
|
||||||
|
@ -47,6 +53,7 @@ var Keys = []string{
|
||||||
"identity_providers.oidc.clients[].response_modes",
|
"identity_providers.oidc.clients[].response_modes",
|
||||||
"identity_providers.oidc.clients[].token_endpoint_auth_method",
|
"identity_providers.oidc.clients[].token_endpoint_auth_method",
|
||||||
"identity_providers.oidc.clients[].token_endpoint_auth_signing_alg",
|
"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[].authorization_policy",
|
||||||
"identity_providers.oidc.clients[].enforce_par",
|
"identity_providers.oidc.clients[].enforce_par",
|
||||||
"identity_providers.oidc.clients[].enforce_pkce",
|
"identity_providers.oidc.clients[].enforce_pkce",
|
||||||
|
@ -54,6 +61,7 @@ var Keys = []string{
|
||||||
"identity_providers.oidc.clients[].userinfo_signing_algorithm",
|
"identity_providers.oidc.clients[].userinfo_signing_algorithm",
|
||||||
"identity_providers.oidc.clients[].consent_mode",
|
"identity_providers.oidc.clients[].consent_mode",
|
||||||
"identity_providers.oidc.clients[].pre_configured_consent_duration",
|
"identity_providers.oidc.clients[].pre_configured_consent_duration",
|
||||||
|
"identity_providers.oidc",
|
||||||
"authentication_backend.password_reset.disable",
|
"authentication_backend.password_reset.disable",
|
||||||
"authentication_backend.password_reset.custom_url",
|
"authentication_backend.password_reset.custom_url",
|
||||||
"authentication_backend.refresh_interval",
|
"authentication_backend.refresh_interval",
|
||||||
|
|
|
@ -34,3 +34,12 @@ type ServerBuffers struct {
|
||||||
Read int `koanf:"read"`
|
Read int `koanf:"read"`
|
||||||
Write int `koanf:"write"`
|
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"`
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package schema
|
package schema
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"crypto"
|
"crypto"
|
||||||
"crypto/ecdsa"
|
"crypto/ecdsa"
|
||||||
"crypto/ed25519"
|
"crypto/ed25519"
|
||||||
|
@ -101,6 +102,10 @@ func NewX509CertificateChain(in string) (chain *X509CertificateChain, err error)
|
||||||
return chain, nil
|
return chain, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NewX509CertificateChainFromCerts(in []*x509.Certificate) (chain X509CertificateChain) {
|
||||||
|
return X509CertificateChain{certs: in}
|
||||||
|
}
|
||||||
|
|
||||||
// NewTLSVersion returns a new TLSVersion given a string.
|
// NewTLSVersion returns a new TLSVersion given a string.
|
||||||
func NewTLSVersion(input string) (version *TLSVersion, err error) {
|
func NewTLSVersion(input string) (version *TLSVersion, err error) {
|
||||||
switch strings.ReplaceAll(strings.ToUpper(input), " ", "") {
|
switch strings.ReplaceAll(strings.ToUpper(input), " ", "") {
|
||||||
|
@ -166,6 +171,9 @@ type CryptographicPrivateKey interface {
|
||||||
Equal(x crypto.PrivateKey) bool
|
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.
|
// X509CertificateChain is a helper struct that holds a list of *x509.Certificate's.
|
||||||
type X509CertificateChain struct {
|
type X509CertificateChain struct {
|
||||||
certs []*x509.Certificate
|
certs []*x509.Certificate
|
||||||
|
@ -277,6 +285,24 @@ func (c *X509CertificateChain) Leaf() (leaf *x509.Certificate) {
|
||||||
return c.certs[0]
|
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
|
// 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.
|
// (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) {
|
func (c *X509CertificateChain) Validate() (err error) {
|
||||||
|
|
|
@ -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
|
||||||
|
...
|
|
@ -0,0 +1,9 @@
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIBRzCB9qADAgECAhB51uvUDHkaxlSEs8cgoYBRMAoGCCqGSM49BAMCMBMxETAP
|
||||||
|
BgNVBAoTCEF1dGhlbGlhMCAXDTIzMDQxNzEzMTIwMloYDzIxMDAwMTAxMDAwMDAw
|
||||||
|
WjATMREwDwYDVQQKEwhBdXRoZWxpYTBOMBAGByqGSM49AgEGBSuBBAAhAzoABJa4
|
||||||
|
oEFZqEbmsnKWXEfNWTiqyEq6YiWbVFIH/PGijaRmsYpKC2UBGsscN4DziAUHBlqX
|
||||||
|
KLA/lsRjozUwMzAOBgNVHQ8BAf8EBAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUHAwEw
|
||||||
|
DAYDVR0TAQH/BAIwADAKBggqhkjOPQQDAgNAADA9Ah0Aq03epx31NN1fTorB/rrz
|
||||||
|
Muu9Taw8YxaZxvjLaQIcbNHGY5bFYxi04ahvN1rYi2sJEn66SaWut+lBIw==
|
||||||
|
-----END CERTIFICATE-----
|
|
@ -0,0 +1,5 @@
|
||||||
|
-----BEGIN EC PRIVATE KEY-----
|
||||||
|
MGgCAQEEHM0126u3fW5scirH39HU9FgPTZOHPxg2NgbSQQ+gBwYFK4EEACGhPAM6
|
||||||
|
AASWuKBBWahG5rJyllxHzVk4qshKumIlm1RSB/zxoo2kZrGKSgtlARrLHDeA84gF
|
||||||
|
BwZalyiwP5bEYw==
|
||||||
|
-----END EC PRIVATE KEY-----
|
|
@ -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-----
|
|
@ -0,0 +1,5 @@
|
||||||
|
-----BEGIN EC PRIVATE KEY-----
|
||||||
|
MHcCAQEEIHL87FDsqijXFhRJ5VgYiOz2ko6xxP7aP7i4v3Eowf4KoAoGCCqGSM49
|
||||||
|
AwEHoUQDQgAEnnBdDSXbTgHtrc5vcJ2xz6qyGXM8PJgENjgQgn5WFVQCSZnKp08+
|
||||||
|
mzeDiHrM67KmISfxSAjoeCJV+dP6JfxIVg==
|
||||||
|
-----END EC PRIVATE KEY-----
|
|
@ -0,0 +1,10 @@
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIBXTCCAQKgAwIBAgIRAPtITpvhty9gwPvrPO1J8GYwCgYIKoZIzj0EAwIwEzER
|
||||||
|
MA8GA1UEChMIQXV0aGVsaWEwIBcNMjMwNDE4MDU0NTM5WhgPMjEwMDAxMDEwMDAw
|
||||||
|
MDBaMBMxETAPBgNVBAoTCEF1dGhlbGlhMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcD
|
||||||
|
QgAEW3aPMQmzoU84DWr4UbbH8tWPMCuzLC44450JvNa8ChDto0ST+koT1Xtq75cu
|
||||||
|
JSAlxn3QeMWZ0pjlvt/woj4Y/qM1MDMwDgYDVR0PAQH/BAQDAgWgMBMGA1UdJQQM
|
||||||
|
MAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwCgYIKoZIzj0EAwIDSQAwRgIhAOI6
|
||||||
|
pgq2zjf5wr1bW21HXsUmwrbyfiCz5vSlAk76QgkRAiEA7Txu5kjdEhnFUw3ORQIF
|
||||||
|
enG1sLX3iZOljfTsHTG1kug=
|
||||||
|
-----END CERTIFICATE-----
|
|
@ -0,0 +1,5 @@
|
||||||
|
-----BEGIN PRIVATE KEY-----
|
||||||
|
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgKwZ0IZaf/E54bRh6
|
||||||
|
b9AEwZJ368O9uoJaJ4tloCjWuDOhRANCAARbdo8xCbOhTzgNavhRtsfy1Y8wK7Ms
|
||||||
|
LjjjnQm81rwKEO2jRJP6ShPVe2rvly4lICXGfdB4xZnSmOW+3/CiPhj+
|
||||||
|
-----END PRIVATE KEY-----
|
|
@ -0,0 +1,11 @@
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIBmDCCAR6gAwIBAgIQWFgOoTSBNa4F1A+Uk5fBhTAKBggqhkjOPQQDAjATMREw
|
||||||
|
DwYDVQQKEwhBdXRoZWxpYTAgFw0yMzA0MTcxMzE1MDBaGA8yMTAwMDEwMTAwMDAw
|
||||||
|
MFowEzERMA8GA1UEChMIQXV0aGVsaWEwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAARq
|
||||||
|
Fk2dSauZd2mW0ZXuxZ0k2a5PInZOs3wbzjJr67RPzmPMNGt5dVHtbOTLr9MAcm21
|
||||||
|
E6/4CLQZ+wMq4Zxuhoa02VN4lQBFOzWFPwVTa0lcOUCkJ7E7JWXiZjX80ROyqDOj
|
||||||
|
NTAzMA4GA1UdDwEB/wQEAwIFoDATBgNVHSUEDDAKBggrBgEFBQcDATAMBgNVHRMB
|
||||||
|
Af8EAjAAMAoGCCqGSM49BAMCA2gAMGUCMQCJHEN22ouKJr0usue9/bUsJltPrgSW
|
||||||
|
v7NjjQ9hY96JAwBQpTxX6EksQdnl44Q/LLACMHBZn3weWvq8frMOAmAvOomMsnMp
|
||||||
|
H7tweTJNXh4V8XdtR2GGxAAYbq/ShyxrpQ6LVA==
|
||||||
|
-----END CERTIFICATE-----
|
|
@ -0,0 +1,6 @@
|
||||||
|
-----BEGIN EC PRIVATE KEY-----
|
||||||
|
MIGkAgEBBDBd2neGG9Ax14sDR0V0TYSXIBxNWZwYr7OAFd57MRUZ/+BkHvQEMOoV
|
||||||
|
umd/tOgGjEagBwYFK4EEACKhZANiAARqFk2dSauZd2mW0ZXuxZ0k2a5PInZOs3wb
|
||||||
|
zjJr67RPzmPMNGt5dVHtbOTLr9MAcm21E6/4CLQZ+wMq4Zxuhoa02VN4lQBFOzWF
|
||||||
|
PwVTa0lcOUCkJ7E7JWXiZjX80ROyqDM=
|
||||||
|
-----END EC PRIVATE KEY-----
|
|
@ -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-----
|
|
@ -0,0 +1,6 @@
|
||||||
|
-----BEGIN PRIVATE KEY-----
|
||||||
|
MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDDQSUy5MggN3tt+A4RV
|
||||||
|
lCcLGFICUBO27VexEuZuY75e+xYRUeDISXSlnqwQVa42Qk+hZANiAATVQ1Vm2hMP
|
||||||
|
Pv0zUA9+jphIUa4pxviHz0wrk2GlpwBvzP/tbF1aRY7MRH+8d/JIKF7p9wuVfOYB
|
||||||
|
0mE7/fpzI33baVb8Js35IRax8EIRxsDvVevE5kcheddIGIyJ0FC3yNw=
|
||||||
|
-----END PRIVATE KEY-----
|
|
@ -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-----
|
|
@ -0,0 +1,7 @@
|
||||||
|
-----BEGIN EC PRIVATE KEY-----
|
||||||
|
MIHcAgEBBEIAe0mKO82UiFUDM3M3CgyEKkXuXnt0m2DAnW3Yf2nadim00n/XsGw7
|
||||||
|
+ID6Zz5Xhazpx7WFNNhtrjbNQOKbsQNndPugBwYFK4EEACOhgYkDgYYABAA7wa4m
|
||||||
|
9YAINi47VmEx1usg0rwAzwHbEWfmdK8AcCBiiXJfPzLEuOYVPX+J9BlCOehxfiP/
|
||||||
|
M4Yi3V/U5dC89JFqJADNaUUh6MIoKEErSGIOp9PLMOCskaIKhsvVuCvLE7egODvR
|
||||||
|
h43eavQ6Qy92sgGOkakaS3kBfpfJohq72iBknPqDIw==
|
||||||
|
-----END EC PRIVATE KEY-----
|
|
@ -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-----
|
|
@ -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-----
|
|
@ -0,0 +1,8 @@
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIBGjCBzaADAgECAhAiL/zLZb4EevlhMjiuV5DZMAUGAytlcDATMREwDwYDVQQK
|
||||||
|
EwhBdXRoZWxpYTAgFw0yMzA0MTgwNTQ4NDNaGA8yMTAwMDEwMTAwMDAwMFowEzER
|
||||||
|
MA8GA1UEChMIQXV0aGVsaWEwKjAFBgMrZXADIQBjrmjS0+DbAzaJWN+8USL8V1qU
|
||||||
|
smG9mWH96wuA6NPA4aM1MDMwDgYDVR0PAQH/BAQDAgWgMBMGA1UdJQQMMAoGCCsG
|
||||||
|
AQUFBwMBMAwGA1UdEwEB/wQCMAAwBQYDK2VwA0EAMuUxDvbVjJwCYhal6H1pZxOh
|
||||||
|
lJd16Aj4C7j7qHLwmWWwREkCoLK/Su1b3982OuCWrOMYMxEx4yNdwsnrKpNTBg==
|
||||||
|
-----END CERTIFICATE-----
|
|
@ -0,0 +1,3 @@
|
||||||
|
-----BEGIN PRIVATE KEY-----
|
||||||
|
MC4CAQAwBQYDK2VwBCIEIG1eby8XtRD2+eZMPFi2jvztZbBSr3wcYEeEl6Sj8k6N
|
||||||
|
-----END PRIVATE KEY-----
|
|
@ -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-----
|
|
@ -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-----
|
|
@ -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-----
|
|
@ -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-----
|
|
@ -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-----
|
|
@ -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-----
|
|
@ -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-----
|
|
@ -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-----
|
|
@ -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-----
|
|
@ -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-----
|
|
@ -144,7 +144,7 @@ const (
|
||||||
const (
|
const (
|
||||||
errFmtOIDCNoClientsConfigured = "identity_providers: oidc: option 'clients' must have one or " +
|
errFmtOIDCNoClientsConfigured = "identity_providers: oidc: option 'clients' must have one or " +
|
||||||
"more clients configured"
|
"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"
|
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"
|
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'"
|
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"
|
attrOIDCRedirectURIs = "redirect_uris"
|
||||||
attrOIDCTokenAuthMethod = "token_endpoint_auth_method"
|
attrOIDCTokenAuthMethod = "token_endpoint_auth_method"
|
||||||
attrOIDCUsrSigAlg = "userinfo_signing_algorithm"
|
attrOIDCUsrSigAlg = "userinfo_signing_algorithm"
|
||||||
|
attrOIDCIDTokenSigAlg = "id_token_signing_alg"
|
||||||
attrOIDCPKCEChallengeMethod = "pkce_challenge_method"
|
attrOIDCPKCEChallengeMethod = "pkce_challenge_method"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -416,7 +417,6 @@ var (
|
||||||
validOIDCCORSEndpoints = []string{oidc.EndpointAuthorization, oidc.EndpointPushedAuthorizationRequest, oidc.EndpointToken, oidc.EndpointIntrospection, oidc.EndpointRevocation, oidc.EndpointUserinfo}
|
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}
|
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()}
|
validOIDCClientConsentModes = []string{auto, oidc.ClientConsentModeImplicit.String(), oidc.ClientConsentModeExplicit.String(), oidc.ClientConsentModePreConfigured.String()}
|
||||||
validOIDCClientResponseModes = []string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery, oidc.ResponseModeFragment}
|
validOIDCClientResponseModes = []string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery, oidc.ResponseModeFragment}
|
||||||
validOIDCClientResponseTypes = []string{oidc.ResponseTypeAuthorizationCodeFlow, oidc.ResponseTypeImplicitFlowIDToken, oidc.ResponseTypeImplicitFlowToken, oidc.ResponseTypeImplicitFlowBoth, oidc.ResponseTypeHybridFlowIDToken, oidc.ResponseTypeHybridFlowToken, oidc.ResponseTypeHybridFlowBoth}
|
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}
|
validOIDCClientTokenEndpointAuthMethods = []string{oidc.ClientAuthMethodNone, oidc.ClientAuthMethodClientSecretPost, oidc.ClientAuthMethodClientSecretBasic, oidc.ClientAuthMethodClientSecretJWT}
|
||||||
validOIDCClientTokenEndpointAuthMethodsConfidential = []string{oidc.ClientAuthMethodClientSecretPost, oidc.ClientAuthMethodClientSecretBasic}
|
validOIDCClientTokenEndpointAuthMethodsConfidential = []string{oidc.ClientAuthMethodClientSecretPost, oidc.ClientAuthMethodClientSecretBasic}
|
||||||
validOIDCClientTokenEndpointAuthSigAlgs = []string{oidc.SigningAlgHMACUsingSHA256, oidc.SigningAlgHMACUsingSHA384, oidc.SigningAlgHMACUsingSHA512}
|
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 (
|
var (
|
||||||
|
|
|
@ -1,12 +1,19 @@
|
||||||
package validator
|
package validator
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto"
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"crypto/ed25519"
|
||||||
|
"crypto/rsa"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"gopkg.in/square/go-jose.v2"
|
||||||
|
|
||||||
"github.com/authelia/authelia/v4/internal/configuration/schema"
|
"github.com/authelia/authelia/v4/internal/configuration/schema"
|
||||||
"github.com/authelia/authelia/v4/internal/oidc"
|
"github.com/authelia/authelia/v4/internal/oidc"
|
||||||
"github.com/authelia/authelia/v4/internal/utils"
|
"github.com/authelia/authelia/v4/internal/utils"
|
||||||
|
@ -24,26 +31,9 @@ func validateOIDC(config *schema.OpenIDConnectConfiguration, val *schema.StructV
|
||||||
|
|
||||||
setOIDCDefaults(config)
|
setOIDCDefaults(config)
|
||||||
|
|
||||||
switch {
|
validateOIDCIssuer(config, val)
|
||||||
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))
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := config.IssuerCertificateChain.Validate(); err != nil {
|
sort.Sort(oidc.SortedSigningAlgs(config.Discovery.RegisteredJWKSigningAlgs))
|
||||||
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))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if config.MinimumParameterEntropy != 0 && config.MinimumParameterEntropy < 8 {
|
if config.MinimumParameterEntropy != 0 && config.MinimumParameterEntropy < 8 {
|
||||||
val.PushWarning(fmt.Errorf(errFmtOIDCServerInsecureParameterEntropy, config.MinimumParameterEntropy))
|
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) {
|
func setOIDCDefaults(config *schema.OpenIDConnectConfiguration) {
|
||||||
if config.AccessTokenLifespan == time.Duration(0) {
|
if config.AccessTokenLifespan == time.Duration(0) {
|
||||||
config.AccessTokenLifespan = schema.DefaultOpenIDConnectConfiguration.AccessTokenLifespan
|
config.AccessTokenLifespan = schema.DefaultOpenIDConnectConfiguration.AccessTokenLifespan
|
||||||
|
@ -228,7 +375,7 @@ func validateOIDCClient(c int, config *schema.OpenIDConnectConfiguration, val *s
|
||||||
validateOIDCClientRedirectURIs(c, config, val, errDeprecatedFunc)
|
validateOIDCClientRedirectURIs(c, config, val, errDeprecatedFunc)
|
||||||
|
|
||||||
validateOIDCClientTokenEndpointAuth(c, config, val)
|
validateOIDCClientTokenEndpointAuth(c, config, val)
|
||||||
validateOIDDClientUserinfoAlgorithm(c, config, val)
|
validateOIDDClientSigningAlgs(c, config, val)
|
||||||
|
|
||||||
validateOIDCClientSectorIdentifier(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) {
|
func validateOIDDClientSigningAlgs(c int, config *schema.OpenIDConnectConfiguration, val *schema.StructValidator) {
|
||||||
if config.Clients[c].UserinfoSigningAlgorithm == "" {
|
if config.Clients[c].UserinfoSigningAlg == "" {
|
||||||
config.Clients[c].UserinfoSigningAlgorithm = schema.DefaultOpenIDConnectClientConfiguration.UserinfoSigningAlgorithm
|
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,
|
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
|
@ -1,10 +1,17 @@
|
||||||
package validator
|
package validator
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"crypto/ed25519"
|
||||||
|
"crypto/elliptic"
|
||||||
|
"crypto/rsa"
|
||||||
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"golang.org/x/net/publicsuffix"
|
"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"
|
"github.com/authelia/authelia/v4/internal/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -107,3 +114,47 @@ func validateList(values, valid []string, chkDuplicate bool) (invalid, duplicate
|
||||||
|
|
||||||
return
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -8,9 +8,9 @@ import (
|
||||||
|
|
||||||
// JSONWebKeySetGET returns the JSON Web Key Set. Used in OAuth 2.0 and OpenID Connect 1.0.
|
// JSONWebKeySetGET returns the JSON Web Key Set. Used in OAuth 2.0 and OpenID Connect 1.0.
|
||||||
func JSONWebKeySetGET(ctx *middlewares.AutheliaCtx) {
|
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")
|
ctx.Error(err, "failed to serve json web key set")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
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)
|
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",
|
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",
|
||||||
|
|
|
@ -145,11 +145,10 @@ func (s *ClientAuthenticationStrategySuite) SetupTest() {
|
||||||
s.ctrl = gomock.NewController(s.T())
|
s.ctrl = gomock.NewController(s.T())
|
||||||
s.store = mocks.NewMockStorage(s.ctrl)
|
s.store = mocks.NewMockStorage(s.ctrl)
|
||||||
|
|
||||||
var err error
|
|
||||||
|
|
||||||
secret := MustDecodeSecret("$plaintext$client-secret")
|
secret := MustDecodeSecret("$plaintext$client-secret")
|
||||||
|
|
||||||
s.provider, err = oidc.NewOpenIDConnectProvider(&schema.OpenIDConnectConfiguration{
|
s.provider = oidc.NewOpenIDConnectProvider(&schema.OpenIDConnectConfiguration{
|
||||||
|
IssuerJWKS: []schema.JWK{},
|
||||||
IssuerCertificateChain: schema.X509CertificateChain{},
|
IssuerCertificateChain: schema.X509CertificateChain{},
|
||||||
IssuerPrivateKey: MustParseRSAPrivateKey(exampleRSAPrivateKey),
|
IssuerPrivateKey: MustParseRSAPrivateKey(exampleRSAPrivateKey),
|
||||||
HMACSecret: "abc123",
|
HMACSecret: "abc123",
|
||||||
|
@ -370,8 +369,6 @@ func (s *ClientAuthenticationStrategySuite) SetupTest() {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}, s.store, nil)
|
}, s.store, nil)
|
||||||
|
|
||||||
s.Require().NoError(err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ClientAuthenticationStrategySuite) TestShouldValidateAssertionHS256() {
|
func (s *ClientAuthenticationStrategySuite) TestShouldValidateAssertionHS256() {
|
||||||
|
|
|
@ -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)
|
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() {
|
switch alg := client.GetUserinfoSigningAlg(); alg {
|
||||||
case oidc.SigningAlgRSAUsingSHA256:
|
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
|
var jti uuid.UUID
|
||||||
|
|
||||||
if jti, err = uuid.NewRandom(); err != nil {
|
if jti, err = uuid.NewRandom(); err != nil {
|
||||||
|
@ -114,11 +124,11 @@ func OpenIDConnectUserinfo(ctx *middlewares.AutheliaCtx, rw http.ResponseWriter,
|
||||||
|
|
||||||
headers := &jwt.Headers{
|
headers := &jwt.Headers{
|
||||||
Extra: map[string]any{
|
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)
|
ctx.Providers.OpenIDConnect.WriteError(rw, req, err)
|
||||||
|
|
||||||
return
|
return
|
||||||
|
@ -126,9 +136,5 @@ func OpenIDConnectUserinfo(ctx *middlewares.AutheliaCtx, rw http.ResponseWriter,
|
||||||
|
|
||||||
rw.Header().Set(fasthttp.HeaderContentType, "application/jwt")
|
rw.Header().Set(fasthttp.HeaderContentType, "application/jwt")
|
||||||
_, _ = rw.Write([]byte(token))
|
_, _ = 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())))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,7 +34,8 @@ func NewClient(config schema.OpenIDConnectClientConfiguration) (client Client) {
|
||||||
|
|
||||||
EnforcePAR: config.EnforcePAR,
|
EnforcePAR: config.EnforcePAR,
|
||||||
|
|
||||||
UserinfoSigningAlgorithm: config.UserinfoSigningAlgorithm,
|
IDTokenSigningAlg: config.IDTokenSigningAlg,
|
||||||
|
UserinfoSigningAlg: config.UserinfoSigningAlg,
|
||||||
|
|
||||||
Policy: authorization.NewLevel(config.Policy),
|
Policy: authorization.NewLevel(config.Policy),
|
||||||
|
|
||||||
|
@ -131,13 +132,22 @@ func (c *BaseClient) GetResponseModes() []fosite.ResponseModeType {
|
||||||
return c.ResponseModes
|
return c.ResponseModes
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetUserinfoSigningAlgorithm returns the UserinfoSigningAlgorithm.
|
// GetIDTokenSigningAlg returns the IDTokenSigningAlg.
|
||||||
func (c *BaseClient) GetUserinfoSigningAlgorithm() string {
|
func (c *BaseClient) GetIDTokenSigningAlg() (alg string) {
|
||||||
if c.UserinfoSigningAlgorithm == "" {
|
if c.IDTokenSigningAlg == "" {
|
||||||
c.UserinfoSigningAlgorithm = SigningAlgNone
|
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.
|
// GetPAREnforcement returns EnforcePAR.
|
||||||
|
@ -295,7 +305,7 @@ func (c *FullClient) GetTokenEndpointAuthMethod() string {
|
||||||
if c.Public {
|
if c.Public {
|
||||||
c.TokenEndpointAuthMethod = ClientAuthMethodNone
|
c.TokenEndpointAuthMethod = ClientAuthMethodNone
|
||||||
} else {
|
} else {
|
||||||
c.TokenEndpointAuthMethod = ClientAuthMethodClientSecretPost
|
c.TokenEndpointAuthMethod = ClientAuthMethodClientSecretBasic
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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()))
|
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]) {
|
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[HeaderParameterAlgorithm], oidcClient.GetTokenEndpointAuthSigningAlgorithm()))
|
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 {
|
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."))
|
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:
|
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]))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@ package oidc
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/ory/fosite"
|
"github.com/ory/fosite"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
@ -26,8 +27,8 @@ func TestNewClient(t *testing.T) {
|
||||||
|
|
||||||
bclient, ok := client.(*BaseClient)
|
bclient, ok := client.(*BaseClient)
|
||||||
require.True(t, ok)
|
require.True(t, ok)
|
||||||
assert.Equal(t, "", bclient.UserinfoSigningAlgorithm)
|
assert.Equal(t, "", bclient.UserinfoSigningAlg)
|
||||||
assert.Equal(t, SigningAlgNone, client.GetUserinfoSigningAlgorithm())
|
assert.Equal(t, SigningAlgNone, client.GetUserinfoSigningAlg())
|
||||||
|
|
||||||
_, ok = client.(*FullClient)
|
_, ok = client.(*FullClient)
|
||||||
assert.False(t, ok)
|
assert.False(t, ok)
|
||||||
|
@ -51,7 +52,7 @@ func TestNewClient(t *testing.T) {
|
||||||
assert.Equal(t, authorization.TwoFactor, client.GetAuthorizationPolicy())
|
assert.Equal(t, authorization.TwoFactor, client.GetAuthorizationPolicy())
|
||||||
|
|
||||||
config = schema.OpenIDConnectClientConfiguration{
|
config = schema.OpenIDConnectClientConfiguration{
|
||||||
TokenEndpointAuthMethod: ClientAuthMethodClientSecretBasic,
|
TokenEndpointAuthMethod: ClientAuthMethodClientSecretPost,
|
||||||
}
|
}
|
||||||
|
|
||||||
client = NewClient(config)
|
client = NewClient(config)
|
||||||
|
@ -61,18 +62,51 @@ func TestNewClient(t *testing.T) {
|
||||||
var niljwks *jose.JSONWebKeySet
|
var niljwks *jose.JSONWebKeySet
|
||||||
|
|
||||||
require.True(t, ok)
|
require.True(t, ok)
|
||||||
assert.Equal(t, "", fclient.UserinfoSigningAlgorithm)
|
|
||||||
assert.Equal(t, ClientAuthMethodClientSecretBasic, fclient.TokenEndpointAuthMethod)
|
assert.Equal(t, "", fclient.UserinfoSigningAlg)
|
||||||
assert.Equal(t, ClientAuthMethodClientSecretBasic, fclient.GetTokenEndpointAuthMethod())
|
assert.Equal(t, SigningAlgNone, client.GetUserinfoSigningAlg())
|
||||||
assert.Equal(t, SigningAlgNone, client.GetUserinfoSigningAlgorithm())
|
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, "", fclient.TokenEndpointAuthSigningAlgorithm)
|
||||||
assert.Equal(t, SigningAlgRSAUsingSHA256, fclient.GetTokenEndpointAuthSigningAlgorithm())
|
assert.Equal(t, SigningAlgRSAUsingSHA256, fclient.GetTokenEndpointAuthSigningAlgorithm())
|
||||||
|
assert.Equal(t, SigningAlgRSAUsingSHA256, fclient.TokenEndpointAuthSigningAlgorithm)
|
||||||
|
|
||||||
assert.Equal(t, "", fclient.RequestObjectSigningAlgorithm)
|
assert.Equal(t, "", fclient.RequestObjectSigningAlgorithm)
|
||||||
assert.Equal(t, "", fclient.GetRequestObjectSigningAlgorithm())
|
assert.Equal(t, "", fclient.GetRequestObjectSigningAlgorithm())
|
||||||
|
|
||||||
|
fclient.RequestObjectSigningAlgorithm = SigningAlgRSAUsingSHA256
|
||||||
|
assert.Equal(t, SigningAlgRSAUsingSHA256, fclient.GetRequestObjectSigningAlgorithm())
|
||||||
|
|
||||||
assert.Equal(t, "", fclient.JSONWebKeysURI)
|
assert.Equal(t, "", fclient.JSONWebKeysURI)
|
||||||
assert.Equal(t, "", fclient.GetJSONWebKeysURI())
|
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.JSONWebKeys)
|
||||||
assert.Equal(t, niljwks, fclient.GetJSONWebKeys())
|
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.RequestURIs)
|
||||||
assert.Equal(t, []string(nil), fclient.GetRequestURIs())
|
assert.Equal(t, []string(nil), fclient.GetRequestURIs())
|
||||||
}
|
}
|
||||||
|
|
|
@ -134,10 +134,6 @@ const (
|
||||||
PKCEChallengeMethodSHA256 = "S256"
|
PKCEChallengeMethodSHA256 = "S256"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
HeaderParameterAlgorithm = "alg"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
FormParameterClientID = "client_id"
|
FormParameterClientID = "client_id"
|
||||||
FormParameterClientSecret = "client_secret"
|
FormParameterClientSecret = "client_secret"
|
||||||
|
@ -170,6 +166,9 @@ const (
|
||||||
const (
|
const (
|
||||||
// JWTHeaderKeyIdentifier is the JWT Header referencing the JWS Key Identifier used to sign a token.
|
// JWTHeaderKeyIdentifier is the JWT Header referencing the JWS Key Identifier used to sign a token.
|
||||||
JWTHeaderKeyIdentifier = "kid"
|
JWTHeaderKeyIdentifier = "kid"
|
||||||
|
|
||||||
|
// JWTHeaderKeyAlgorithm is the JWT Header referencing the JWS Key algorithm used to sign a token.
|
||||||
|
JWTHeaderKeyAlgorithm = "alg"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
|
@ -1,15 +1,21 @@
|
||||||
package oidc
|
package oidc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"crypto/ed25519"
|
||||||
"crypto/rsa"
|
"crypto/rsa"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"encoding/pem"
|
"fmt"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/authelia/authelia/v4/internal/configuration/schema"
|
"github.com/authelia/authelia/v4/internal/configuration/schema"
|
||||||
|
"github.com/authelia/authelia/v4/internal/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
pathCrypto = "../configuration/test_resources/crypto/%s.%s"
|
||||||
myclient = "myclient"
|
myclient = "myclient"
|
||||||
myclientdesc = "My Client"
|
myclientdesc = "My Client"
|
||||||
onefactor = "one_factor"
|
onefactor = "one_factor"
|
||||||
|
@ -18,7 +24,6 @@ const (
|
||||||
examplecomsid = "example.com"
|
examplecomsid = "example.com"
|
||||||
badsecret = "$plaintext$a_bad_secret"
|
badsecret = "$plaintext$a_bad_secret"
|
||||||
badhmac = "asbdhaaskmdlkamdklasmdlkams"
|
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 {
|
func MustDecodeSecret(value string) *schema.PasswordDigest {
|
||||||
|
@ -37,20 +42,126 @@ func MustParseRequestURI(input string) *url.URL {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func MustParseRSAPrivateKey(data string) *rsa.PrivateKey {
|
func MustLoadCrypto(alg, mod, ext string, extra ...string) any {
|
||||||
block, _ := pem.Decode([]byte(data))
|
fparts := []string{alg, mod}
|
||||||
if block == nil || block.Bytes == nil || len(block.Bytes) == 0 {
|
if len(extra) != 0 {
|
||||||
panic("not pem encoded")
|
fparts = append(fparts, extra...)
|
||||||
}
|
}
|
||||||
|
|
||||||
if block.Type != "RSA PRIVATE KEY" {
|
var (
|
||||||
panic("not private key")
|
data []byte
|
||||||
}
|
decoded any
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
|
||||||
key, err := x509.ParsePKCS1PrivateKey(block.Bytes)
|
if data, err = os.ReadFile(fmt.Sprintf(pathCrypto, strings.Join(fparts, "_"), ext)); err != nil {
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
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
|
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")
|
||||||
|
}
|
||||||
|
|
|
@ -1,13 +1,93 @@
|
||||||
package oidc
|
package oidc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ory/fosite"
|
||||||
|
"github.com/ory/fosite/token/hmac"
|
||||||
"github.com/stretchr/testify/assert"
|
"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) {
|
func TestHMACCoreStrategy_TrimPrefix(t *testing.T) {
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
name string
|
name string
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
package oidc
|
package oidc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"sort"
|
||||||
|
|
||||||
"github.com/authelia/authelia/v4/internal/configuration/schema"
|
"github.com/authelia/authelia/v4/internal/configuration/schema"
|
||||||
|
"github.com/authelia/authelia/v4/internal/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewOpenIDConnectWellKnownConfiguration generates a new OpenIDConnectWellKnownConfiguration.
|
// NewOpenIDConnectWellKnownConfiguration generates a new OpenIDConnectWellKnownConfiguration.
|
||||||
|
@ -75,6 +78,21 @@ func NewOpenIDConnectWellKnownConfiguration(c *schema.OpenIDConnectConfiguration
|
||||||
CodeChallengeMethodsSupported: []string{
|
CodeChallengeMethodsSupported: []string{
|
||||||
PKCEChallengeMethodSHA256,
|
PKCEChallengeMethodSHA256,
|
||||||
},
|
},
|
||||||
|
RevocationEndpointAuthMethodsSupported: []string{
|
||||||
|
ClientAuthMethodClientSecretBasic,
|
||||||
|
ClientAuthMethodClientSecretPost,
|
||||||
|
ClientAuthMethodClientSecretJWT,
|
||||||
|
ClientAuthMethodNone,
|
||||||
|
},
|
||||||
|
RevocationEndpointAuthSigningAlgValuesSupported: []string{
|
||||||
|
SigningAlgHMACUsingSHA256,
|
||||||
|
SigningAlgHMACUsingSHA384,
|
||||||
|
SigningAlgHMACUsingSHA512,
|
||||||
|
},
|
||||||
|
IntrospectionEndpointAuthMethodsSupported: []string{
|
||||||
|
ClientAuthMethodClientSecretBasic,
|
||||||
|
ClientAuthMethodNone,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
OAuth2PushedAuthorizationDiscoveryOptions: &OAuth2PushedAuthorizationDiscoveryOptions{
|
OAuth2PushedAuthorizationDiscoveryOptions: &OAuth2PushedAuthorizationDiscoveryOptions{
|
||||||
RequirePushedAuthorizationRequests: c.PAR.Enforce,
|
RequirePushedAuthorizationRequests: c.PAR.Enforce,
|
||||||
|
@ -89,6 +107,10 @@ func NewOpenIDConnectWellKnownConfiguration(c *schema.OpenIDConnectConfiguration
|
||||||
SigningAlgNone,
|
SigningAlgNone,
|
||||||
SigningAlgRSAUsingSHA256,
|
SigningAlgRSAUsingSHA256,
|
||||||
},
|
},
|
||||||
|
RequestObjectSigningAlgValuesSupported: []string{
|
||||||
|
SigningAlgNone,
|
||||||
|
SigningAlgRSAUsingSHA256,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
OpenIDConnectFrontChannelLogoutDiscoveryOptions: &OpenIDConnectFrontChannelLogoutDiscoveryOptions{},
|
OpenIDConnectFrontChannelLogoutDiscoveryOptions: &OpenIDConnectFrontChannelLogoutDiscoveryOptions{},
|
||||||
OpenIDConnectBackChannelLogoutDiscoveryOptions: &OpenIDConnectBackChannelLogoutDiscoveryOptions{},
|
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 {
|
if c.EnablePKCEPlainChallenge {
|
||||||
config.CodeChallengeMethodsSupported = append(config.CodeChallengeMethodsSupported, PKCEChallengeMethodPlain)
|
config.CodeChallengeMethodsSupported = append(config.CodeChallengeMethodsSupported, PKCEChallengeMethodPlain)
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,8 +14,9 @@ func TestNewOpenIDConnectWellKnownConfiguration(t *testing.T) {
|
||||||
pkcePlainChallenge bool
|
pkcePlainChallenge bool
|
||||||
enforcePAR bool
|
enforcePAR bool
|
||||||
clients map[string]Client
|
clients map[string]Client
|
||||||
|
discovery schema.OpenIDConnectDiscovery
|
||||||
|
|
||||||
expectCodeChallengeMethodsSupported, expectSubjectTypesSupported []string
|
expectCodeChallengeMethodsSupported, expectSubjectTypesSupported, expectedIDTokenSigAlgsSupported, expectedUserInfoSigAlgsSupported []string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
desc: "ShouldHaveChallengeMethodsS256ANDSubjectTypesSupportedPublic",
|
desc: "ShouldHaveChallengeMethodsS256ANDSubjectTypesSupportedPublic",
|
||||||
|
@ -23,6 +24,20 @@ func TestNewOpenIDConnectWellKnownConfiguration(t *testing.T) {
|
||||||
clients: map[string]Client{"a": &BaseClient{}},
|
clients: map[string]Client{"a": &BaseClient{}},
|
||||||
expectCodeChallengeMethodsSupported: []string{PKCEChallengeMethodSHA256},
|
expectCodeChallengeMethodsSupported: []string{PKCEChallengeMethodSHA256},
|
||||||
expectSubjectTypesSupported: []string{SubjectTypePublic, SubjectTypePairwise},
|
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",
|
desc: "ShouldHaveChallengeMethodsS256PlainANDSubjectTypesSupportedPublic",
|
||||||
|
@ -30,6 +45,8 @@ func TestNewOpenIDConnectWellKnownConfiguration(t *testing.T) {
|
||||||
clients: map[string]Client{"a": &BaseClient{}},
|
clients: map[string]Client{"a": &BaseClient{}},
|
||||||
expectCodeChallengeMethodsSupported: []string{PKCEChallengeMethodSHA256, PKCEChallengeMethodPlain},
|
expectCodeChallengeMethodsSupported: []string{PKCEChallengeMethodSHA256, PKCEChallengeMethodPlain},
|
||||||
expectSubjectTypesSupported: []string{SubjectTypePublic, SubjectTypePairwise},
|
expectSubjectTypesSupported: []string{SubjectTypePublic, SubjectTypePairwise},
|
||||||
|
expectedIDTokenSigAlgsSupported: []string{SigningAlgRSAUsingSHA256},
|
||||||
|
expectedUserInfoSigAlgsSupported: []string{SigningAlgRSAUsingSHA256, SigningAlgNone},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "ShouldHaveChallengeMethodsS256ANDSubjectTypesSupportedPublicPairwise",
|
desc: "ShouldHaveChallengeMethodsS256ANDSubjectTypesSupportedPublicPairwise",
|
||||||
|
@ -37,6 +54,8 @@ func TestNewOpenIDConnectWellKnownConfiguration(t *testing.T) {
|
||||||
clients: map[string]Client{"a": &BaseClient{SectorIdentifier: "yes"}},
|
clients: map[string]Client{"a": &BaseClient{SectorIdentifier: "yes"}},
|
||||||
expectCodeChallengeMethodsSupported: []string{PKCEChallengeMethodSHA256},
|
expectCodeChallengeMethodsSupported: []string{PKCEChallengeMethodSHA256},
|
||||||
expectSubjectTypesSupported: []string{SubjectTypePublic, SubjectTypePairwise},
|
expectSubjectTypesSupported: []string{SubjectTypePublic, SubjectTypePairwise},
|
||||||
|
expectedIDTokenSigAlgsSupported: []string{SigningAlgRSAUsingSHA256},
|
||||||
|
expectedUserInfoSigAlgsSupported: []string{SigningAlgRSAUsingSHA256, SigningAlgNone},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "ShouldHaveChallengeMethodsS256PlainANDSubjectTypesSupportedPublicPairwise",
|
desc: "ShouldHaveChallengeMethodsS256PlainANDSubjectTypesSupportedPublicPairwise",
|
||||||
|
@ -44,6 +63,8 @@ func TestNewOpenIDConnectWellKnownConfiguration(t *testing.T) {
|
||||||
clients: map[string]Client{"a": &BaseClient{SectorIdentifier: "yes"}},
|
clients: map[string]Client{"a": &BaseClient{SectorIdentifier: "yes"}},
|
||||||
expectCodeChallengeMethodsSupported: []string{PKCEChallengeMethodSHA256, PKCEChallengeMethodPlain},
|
expectCodeChallengeMethodsSupported: []string{PKCEChallengeMethodSHA256, PKCEChallengeMethodPlain},
|
||||||
expectSubjectTypesSupported: []string{SubjectTypePublic, SubjectTypePairwise},
|
expectSubjectTypesSupported: []string{SubjectTypePublic, SubjectTypePairwise},
|
||||||
|
expectedIDTokenSigAlgsSupported: []string{SigningAlgRSAUsingSHA256},
|
||||||
|
expectedUserInfoSigAlgsSupported: []string{SigningAlgRSAUsingSHA256, SigningAlgNone},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "ShouldHaveTokenAuthMethodsNone",
|
desc: "ShouldHaveTokenAuthMethodsNone",
|
||||||
|
@ -51,6 +72,8 @@ func TestNewOpenIDConnectWellKnownConfiguration(t *testing.T) {
|
||||||
clients: map[string]Client{"a": &BaseClient{SectorIdentifier: "yes"}},
|
clients: map[string]Client{"a": &BaseClient{SectorIdentifier: "yes"}},
|
||||||
expectCodeChallengeMethodsSupported: []string{PKCEChallengeMethodSHA256, PKCEChallengeMethodPlain},
|
expectCodeChallengeMethodsSupported: []string{PKCEChallengeMethodSHA256, PKCEChallengeMethodPlain},
|
||||||
expectSubjectTypesSupported: []string{SubjectTypePublic, SubjectTypePairwise},
|
expectSubjectTypesSupported: []string{SubjectTypePublic, SubjectTypePairwise},
|
||||||
|
expectedIDTokenSigAlgsSupported: []string{SigningAlgRSAUsingSHA256},
|
||||||
|
expectedUserInfoSigAlgsSupported: []string{SigningAlgRSAUsingSHA256, SigningAlgNone},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "ShouldHaveTokenAuthMethodsNone",
|
desc: "ShouldHaveTokenAuthMethodsNone",
|
||||||
|
@ -61,6 +84,20 @@ func TestNewOpenIDConnectWellKnownConfiguration(t *testing.T) {
|
||||||
},
|
},
|
||||||
expectCodeChallengeMethodsSupported: []string{PKCEChallengeMethodSHA256, PKCEChallengeMethodPlain},
|
expectCodeChallengeMethodsSupported: []string{PKCEChallengeMethodSHA256, PKCEChallengeMethodPlain},
|
||||||
expectSubjectTypesSupported: []string{SubjectTypePublic, SubjectTypePairwise},
|
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{
|
PAR: schema.OpenIDConnectPARConfiguration{
|
||||||
Enforce: tc.enforcePAR,
|
Enforce: tc.enforcePAR,
|
||||||
},
|
},
|
||||||
|
Discovery: tc.discovery,
|
||||||
}
|
}
|
||||||
|
|
||||||
actual := NewOpenIDConnectWellKnownConfiguration(&c)
|
actual := NewOpenIDConnectWellKnownConfiguration(&c)
|
||||||
|
@ -89,6 +127,9 @@ func TestNewOpenIDConnectWellKnownConfiguration(t *testing.T) {
|
||||||
for _, subjectType := range actual.SubjectTypesSupported {
|
for _, subjectType := range actual.SubjectTypesSupported {
|
||||||
assert.Contains(t, tc.expectSubjectTypesSupported, subjectType)
|
assert.Contains(t, tc.expectSubjectTypesSupported, subjectType)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, tc.expectedUserInfoSigAlgsSupported, actual.UserinfoSigningAlgValuesSupported)
|
||||||
|
assert.Equal(t, tc.expectedIDTokenSigAlgsSupported, actual.IDTokenSigningAlgValuesSupported)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,181 +3,398 @@ package oidc
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto"
|
"crypto"
|
||||||
|
"crypto/ecdsa"
|
||||||
"crypto/rsa"
|
"crypto/rsa"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"sort"
|
||||||
"strings"
|
"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"
|
"gopkg.in/square/go-jose.v2"
|
||||||
|
|
||||||
"github.com/authelia/authelia/v4/internal/configuration/schema"
|
"github.com/authelia/authelia/v4/internal/configuration/schema"
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewKeyManagerWithConfiguration when provided a schema.OpenIDConnectConfiguration creates a new KeyManager and adds an
|
// NewKeyManager news up a KeyManager.
|
||||||
// initial key to the manager.
|
func NewKeyManager(config *schema.OpenIDConnectConfiguration) (manager *KeyManager) {
|
||||||
func NewKeyManagerWithConfiguration(config *schema.OpenIDConnectConfiguration) (manager *KeyManager, err error) {
|
manager = &KeyManager{
|
||||||
manager = NewKeyManager()
|
kids: map[string]*JWK{},
|
||||||
|
algs: map[string]*JWK{},
|
||||||
if _, err = manager.AddActiveJWK(config.IssuerCertificateChain, config.IssuerPrivateKey); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return manager, nil
|
for _, sjwk := range config.IssuerJWKS {
|
||||||
}
|
jwk := NewJWK(sjwk)
|
||||||
|
|
||||||
// NewKeyManager creates a new empty KeyManager.
|
manager.kids[sjwk.KeyID] = jwk
|
||||||
func NewKeyManager() (manager *KeyManager) {
|
manager.algs[jwk.alg.Alg()] = jwk
|
||||||
return &KeyManager{
|
|
||||||
jwks: &jose.JSONWebKeySet{},
|
if jwk.kid == config.Discovery.DefaultKeyID {
|
||||||
|
manager.kid = jwk.kid
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Strategy returns the fosite jwt.JWTStrategy.
|
return manager
|
||||||
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 nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return m.jwk.Strategy()
|
func (m *KeyManager) GetByKID(ctx context.Context, kid string) *JWK {
|
||||||
|
if kid == "" {
|
||||||
|
return m.kids[m.kid]
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetKeySet returns the joseJSONWebKeySet containing the rsa.PublicKey types.
|
if jwk, ok := m.kids[kid]; ok {
|
||||||
func (m *KeyManager) GetKeySet() (jwks *jose.JSONWebKeySet) {
|
return jwk
|
||||||
return m.jwks
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetActiveJWK obtains the currently active jose.JSONWebKey.
|
return nil
|
||||||
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")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
jwks := m.jwks.Key(m.jwk.id)
|
func (m *KeyManager) GetByHeader(ctx context.Context, header fjwt.Mapper) (jwk *JWK, err error) {
|
||||||
|
var (
|
||||||
|
kid string
|
||||||
|
ok bool
|
||||||
|
)
|
||||||
|
|
||||||
if len(jwks) == 1 {
|
if header == nil {
|
||||||
return &jwks[0], nil
|
return nil, fmt.Errorf("jwt header was nil")
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(jwks) == 0 {
|
if kid, ok = header.Get(JWTHeaderKeyIdentifier).(string); !ok {
|
||||||
return nil, errors.New("could not find a key with the active key id")
|
return nil, fmt.Errorf("jwt header did not have a kid")
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, errors.New("multiple keys with the same 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)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetActiveKeyID returns the key id of the currently active key.
|
return jwk, nil
|
||||||
func (m *KeyManager) GetActiveKeyID() (keyID string) {
|
|
||||||
if m.jwk == nil {
|
|
||||||
return ""
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return m.jwk.id
|
func (m *KeyManager) GetByTokenString(ctx context.Context, tokenString string) (jwk *JWK, err error) {
|
||||||
}
|
var (
|
||||||
|
token *jwt.Token
|
||||||
|
)
|
||||||
|
|
||||||
// GetActivePrivateKey returns the rsa.PrivateKey of the currently active key.
|
if token, _, err = jwt.NewParser().ParseUnverified(tokenString, jwt.MapClaims{}); err != nil {
|
||||||
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 {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
m.jwks.Keys = append(m.jwks.Keys, *m.jwk.JSONWebKey())
|
return m.GetByHeader(ctx, &fjwt.Headers{Extra: token.Header})
|
||||||
|
|
||||||
return m.jwk, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// JWTStrategy is a decorator struct for the fosite jwt.JWTStrategy.
|
func (m *KeyManager) Set(ctx context.Context) *jose.JSONWebKeySet {
|
||||||
type JWTStrategy struct {
|
keys := make([]jose.JSONWebKey, 0, len(m.kids))
|
||||||
jwt.Signer
|
|
||||||
|
|
||||||
id string
|
for _, jwk := range m.kids {
|
||||||
|
keys = append(keys, jwk.JWK())
|
||||||
}
|
}
|
||||||
|
|
||||||
// KeyID returns the key id.
|
sort.Sort(SortedJSONWebKey(keys))
|
||||||
func (s *JWTStrategy) KeyID() (id string) {
|
|
||||||
return s.id
|
return &jose.JSONWebKeySet{
|
||||||
|
Keys: keys,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetPublicKeyID is a decorator func for the underlying fosite RS256JWTStrategy.
|
func (m *KeyManager) Generate(ctx context.Context, claims fjwt.MapClaims, header fjwt.Mapper) (tokenString string, sig string, err error) {
|
||||||
func (s *JWTStrategy) GetPublicKeyID(_ context.Context) (string, error) {
|
var jwk *JWK
|
||||||
return s.id, nil
|
|
||||||
|
if jwk, err = m.GetByHeader(ctx, header); err != nil {
|
||||||
|
return "", "", fmt.Errorf("error getting jwk from header: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewJWK creates a new JWK.
|
return jwk.Strategy().Generate(ctx, claims, header)
|
||||||
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")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
j = &JWK{
|
func (m *KeyManager) Validate(ctx context.Context, tokenString string) (sig string, err error) {
|
||||||
key: key,
|
var jwk *JWK
|
||||||
chain: chain,
|
|
||||||
|
if jwk, err = m.GetByTokenString(ctx, tokenString); err != nil {
|
||||||
|
return "", fmt.Errorf("error getting jwk from token string: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
jwk := &jose.JSONWebKey{
|
return jwk.Strategy().Validate(ctx, tokenString)
|
||||||
Algorithm: SigningAlgRSAUsingSHA256,
|
|
||||||
Use: KeyUseSignature,
|
|
||||||
Key: &key.PublicKey,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var thumbprint []byte
|
func (m *KeyManager) Hash(ctx context.Context, in []byte) (sum []byte, err error) {
|
||||||
|
return m.GetByKID(ctx, "").Strategy().Hash(ctx, in)
|
||||||
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))
|
func (m *KeyManager) Decode(ctx context.Context, tokenString string) (token *fjwt.Token, err error) {
|
||||||
|
var jwk *JWK
|
||||||
|
|
||||||
if len(j.id) >= 7 {
|
if jwk, err = m.GetByTokenString(ctx, tokenString); err != nil {
|
||||||
j.id = j.id[:6]
|
return nil, fmt.Errorf("error getting jwk from token string: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(j.id) >= 7 {
|
return jwk.Strategy().Decode(ctx, tokenString)
|
||||||
j.id = j.id[:6]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return j, nil
|
func (m *KeyManager) GetSignature(ctx context.Context, tokenString string) (sig string, err error) {
|
||||||
|
return getTokenSignature(tokenString)
|
||||||
}
|
}
|
||||||
|
|
||||||
// JWK is a utility wrapper for JSON Web Key's.
|
func (m *KeyManager) GetSigningMethodLength(ctx context.Context) (size int) {
|
||||||
type JWK struct {
|
return m.GetByKID(ctx, "").Strategy().GetSigningMethodLength(ctx)
|
||||||
id string
|
|
||||||
key *rsa.PrivateKey
|
|
||||||
chain schema.X509CertificateChain
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Strategy returns the relevant jwt.JWTStrategy for this JWT.
|
func NewJWK(s schema.JWK) (jwk *JWK) {
|
||||||
func (j *JWK) Strategy() (strategy jwt.Signer) {
|
jwk = &JWK{
|
||||||
return &JWTStrategy{id: j.id, Signer: &jwt.DefaultSigner{GetPrivateKey: j.GetPrivateKey}}
|
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),
|
||||||
}
|
}
|
||||||
|
|
||||||
func (j *JWK) GetPrivateKey(ctx context.Context) (key any, err error) {
|
switch jwk.alg {
|
||||||
return j.key, nil
|
case jwt.SigningMethodRS256, jwt.SigningMethodPS256, jwt.SigningMethodES256:
|
||||||
}
|
jwk.hash = crypto.SHA256
|
||||||
|
case jwt.SigningMethodRS384, jwt.SigningMethodPS384, jwt.SigningMethodES384:
|
||||||
// JSONWebKey returns the relevant *jose.JSONWebKey for this JWT.
|
jwk.hash = crypto.SHA384
|
||||||
func (j *JWK) JSONWebKey() (jwk *jose.JSONWebKey) {
|
case jwt.SigningMethodRS512, jwt.SigningMethodPS512, jwt.SigningMethodES512:
|
||||||
jwk = &jose.JSONWebKey{
|
jwk.hash = crypto.SHA512
|
||||||
Key: &j.key.PublicKey,
|
default:
|
||||||
KeyID: j.id,
|
jwk.hash = crypto.SHA256
|
||||||
Algorithm: SigningAlgRSAUsingSHA256,
|
|
||||||
Use: KeyUseSignature,
|
|
||||||
Certificates: j.chain.Certificates(),
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(jwk.Certificates) != 0 {
|
|
||||||
jwk.CertificateThumbprintSHA1, jwk.CertificateThumbprintSHA256 = j.chain.Thumbprint(crypto.SHA1), j.chain.Thumbprint(crypto.SHA256)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return jwk
|
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
|
||||||
|
}
|
||||||
|
|
|
@ -1,48 +1,450 @@
|
||||||
package oidc
|
package oidc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
fjwt "github.com/ory/fosite/token/jwt"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
"gopkg.in/square/go-jose.v2"
|
||||||
|
|
||||||
"github.com/authelia/authelia/v4/internal/configuration/schema"
|
"github.com/authelia/authelia/v4/internal/configuration/schema"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestKeyManager_AddActiveJWK(t *testing.T) {
|
func TestKeyManager(t *testing.T) {
|
||||||
manager := NewKeyManager()
|
config := &schema.OpenIDConnectConfiguration{
|
||||||
assert.Nil(t, manager.jwk)
|
Discovery: schema.OpenIDConnectDiscovery{
|
||||||
assert.Nil(t, manager.Strategy())
|
DefaultKeyID: "kid-RS256-sig",
|
||||||
|
},
|
||||||
j, err := manager.AddActiveJWK(schema.X509CertificateChain{}, MustParseRSAPrivateKey(exampleIssuerPrivateKey))
|
IssuerJWKS: []schema.JWK{
|
||||||
require.NoError(t, err)
|
{
|
||||||
require.NotNil(t, j)
|
Use: KeyUseSignature,
|
||||||
require.NotNil(t, manager.jwk)
|
Algorithm: SigningAlgRSAUsingSHA256,
|
||||||
require.NotNil(t, manager.Strategy())
|
Key: keyRSA2048,
|
||||||
|
CertificateChain: certRSA2048,
|
||||||
thumbprint, err := j.JSONWebKey().Thumbprint(crypto.SHA1)
|
},
|
||||||
assert.NoError(t, err)
|
{
|
||||||
|
Use: KeyUseSignature,
|
||||||
kid := strings.ToLower(fmt.Sprintf("%x", thumbprint)[:6])
|
Algorithm: SigningAlgRSAUsingSHA384,
|
||||||
assert.Equal(t, manager.jwk.id, kid)
|
Key: keyRSA2048,
|
||||||
assert.Equal(t, kid, j.JSONWebKey().KeyID)
|
CertificateChain: certRSA2048,
|
||||||
assert.Len(t, manager.jwks.Keys, 1)
|
},
|
||||||
|
{
|
||||||
keys := manager.jwks.Key(kid)
|
Use: KeyUseSignature,
|
||||||
assert.Equal(t, keys[0].KeyID, kid)
|
Algorithm: SigningAlgRSAUsingSHA512,
|
||||||
|
Key: keyRSA4096,
|
||||||
privKey, err := manager.GetActivePrivateKey()
|
CertificateChain: certRSA4096,
|
||||||
assert.NoError(t, err)
|
},
|
||||||
assert.NotNil(t, privKey)
|
{
|
||||||
|
Use: KeyUseSignature,
|
||||||
webKey, err := manager.GetActiveJWK()
|
Algorithm: SigningAlgRSAPSSUsingSHA256,
|
||||||
assert.NoError(t, err)
|
Key: keyRSA2048,
|
||||||
assert.NotNil(t, webKey)
|
CertificateChain: certRSA2048,
|
||||||
|
},
|
||||||
keySet := manager.GetKeySet()
|
{
|
||||||
assert.NotNil(t, keySet)
|
Use: KeyUseSignature,
|
||||||
assert.Equal(t, kid, manager.GetActiveKeyID())
|
Algorithm: SigningAlgRSAPSSUsingSHA384,
|
||||||
|
Key: keyRSA2048,
|
||||||
|
CertificateChain: certRSA2048,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Use: KeyUseSignature,
|
||||||
|
Algorithm: SigningAlgRSAPSSUsingSHA512,
|
||||||
|
Key: keyRSA4096,
|
||||||
|
CertificateChain: certRSA4096,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Use: KeyUseSignature,
|
||||||
|
Algorithm: SigningAlgECDSAUsingP256AndSHA256,
|
||||||
|
Key: keyECDSAP256,
|
||||||
|
CertificateChain: certECDSAP256,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Use: KeyUseSignature,
|
||||||
|
Algorithm: SigningAlgECDSAUsingP384AndSHA384,
|
||||||
|
Key: keyECDSAP384,
|
||||||
|
CertificateChain: certECDSAP384,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Use: KeyUseSignature,
|
||||||
|
Algorithm: SigningAlgECDSAUsingP521AndSHA512,
|
||||||
|
Key: keyECDSAP521,
|
||||||
|
CertificateChain: certECDSAP521,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, key := range config.IssuerJWKS {
|
||||||
|
config.IssuerJWKS[i].KeyID = fmt.Sprintf("kid-%s-%s", key.Algorithm, key.Use)
|
||||||
|
}
|
||||||
|
|
||||||
|
manager := NewKeyManager(config)
|
||||||
|
|
||||||
|
assert.NotNil(t, manager)
|
||||||
|
|
||||||
|
assert.Len(t, manager.kids, len(config.IssuerJWKS))
|
||||||
|
assert.Len(t, manager.algs, len(config.IssuerJWKS))
|
||||||
|
|
||||||
|
assert.Equal(t, "kid-RS256-sig", manager.kid)
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
var (
|
||||||
|
jwk *JWK
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
|
||||||
|
jwk = manager.GetByAlg(ctx, "notalg")
|
||||||
|
assert.Nil(t, jwk)
|
||||||
|
|
||||||
|
jwk = manager.GetByKID(ctx, "notalg")
|
||||||
|
assert.Nil(t, jwk)
|
||||||
|
|
||||||
|
jwk = manager.GetByKID(ctx, "")
|
||||||
|
assert.NotNil(t, jwk)
|
||||||
|
assert.Equal(t, config.Discovery.DefaultKeyID, jwk.KeyID())
|
||||||
|
|
||||||
|
jwk, err = manager.GetByHeader(ctx, &fjwt.Headers{Extra: map[string]any{JWTHeaderKeyIdentifier: "notalg"}})
|
||||||
|
assert.EqualError(t, err, "jwt header 'kid' with value 'notalg' does not match a managed jwk")
|
||||||
|
assert.Nil(t, jwk)
|
||||||
|
|
||||||
|
jwk, err = manager.GetByHeader(ctx, &fjwt.Headers{Extra: map[string]any{}})
|
||||||
|
assert.EqualError(t, err, "jwt header did not have a kid")
|
||||||
|
assert.Nil(t, jwk)
|
||||||
|
|
||||||
|
jwk, err = manager.GetByHeader(ctx, nil)
|
||||||
|
assert.EqualError(t, err, "jwt header was nil")
|
||||||
|
assert.Nil(t, jwk)
|
||||||
|
|
||||||
|
kid, err := manager.GetKIDFromAlgStrict(ctx, "notalg")
|
||||||
|
assert.EqualError(t, err, "alg not found")
|
||||||
|
assert.Equal(t, "", kid)
|
||||||
|
|
||||||
|
kid = manager.GetKIDFromAlg(ctx, "notalg")
|
||||||
|
assert.Equal(t, config.Discovery.DefaultKeyID, kid)
|
||||||
|
|
||||||
|
set := manager.Set(ctx)
|
||||||
|
|
||||||
|
assert.NotNil(t, set)
|
||||||
|
assert.Len(t, set.Keys, len(config.IssuerJWKS))
|
||||||
|
|
||||||
|
data, err := json.Marshal(&set)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotNil(t, data)
|
||||||
|
|
||||||
|
out := jose.JSONWebKeySet{}
|
||||||
|
assert.NoError(t, json.Unmarshal(data, &out))
|
||||||
|
assert.Equal(t, *set, out)
|
||||||
|
|
||||||
|
for _, alg := range []string{SigningAlgRSAUsingSHA256, SigningAlgRSAUsingSHA384, SigningAlgRSAPSSUsingSHA512, SigningAlgRSAPSSUsingSHA256, SigningAlgRSAPSSUsingSHA384, SigningAlgRSAPSSUsingSHA512, SigningAlgECDSAUsingP256AndSHA256, SigningAlgECDSAUsingP384AndSHA384, SigningAlgECDSAUsingP521AndSHA512} {
|
||||||
|
t.Run(alg, func(t *testing.T) {
|
||||||
|
expectedKID := fmt.Sprintf("kid-%s-%s", alg, KeyUseSignature)
|
||||||
|
|
||||||
|
t.Run("ShouldGetCorrectKey", func(t *testing.T) {
|
||||||
|
jwk = manager.GetByKID(ctx, expectedKID)
|
||||||
|
assert.NotNil(t, jwk)
|
||||||
|
assert.Equal(t, expectedKID, jwk.KeyID())
|
||||||
|
|
||||||
|
jwk = manager.GetByAlg(ctx, alg)
|
||||||
|
assert.NotNil(t, jwk)
|
||||||
|
|
||||||
|
assert.Equal(t, alg, jwk.alg.Alg())
|
||||||
|
assert.Equal(t, expectedKID, jwk.KeyID())
|
||||||
|
|
||||||
|
kid, err = manager.GetKIDFromAlgStrict(ctx, alg)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, expectedKID, kid)
|
||||||
|
|
||||||
|
kid = manager.GetKIDFromAlg(ctx, alg)
|
||||||
|
assert.Equal(t, expectedKID, kid)
|
||||||
|
|
||||||
|
jwk, err = manager.GetByHeader(ctx, &fjwt.Headers{Extra: map[string]any{JWTHeaderKeyIdentifier: expectedKID}})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotNil(t, jwk)
|
||||||
|
|
||||||
|
assert.Equal(t, expectedKID, jwk.KeyID())
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("ShouldUseCorrectSigner", func(t *testing.T) {
|
||||||
|
var tokenString, sig, sigb string
|
||||||
|
var token *fjwt.Token
|
||||||
|
|
||||||
|
tokenString, sig, err = manager.Generate(ctx, fjwt.MapClaims{}, &fjwt.Headers{Extra: map[string]any{JWTHeaderKeyIdentifier: expectedKID}})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
sigb, err = manager.GetSignature(ctx, tokenString)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, sig, sigb)
|
||||||
|
|
||||||
|
sigb, err = manager.Validate(ctx, tokenString)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, sig, sigb)
|
||||||
|
|
||||||
|
token, err = manager.Decode(ctx, tokenString)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, expectedKID, token.Header[JWTHeaderKeyIdentifier])
|
||||||
|
|
||||||
|
jwk, err = manager.GetByTokenString(ctx, tokenString)
|
||||||
|
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
sigb, err = jwk.Strategy().Validate(ctx, tokenString)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, sig, sigb)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestJWKFunctionality(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
have schema.JWK
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
schema.JWK{
|
||||||
|
KeyID: "rsa2048-rs256",
|
||||||
|
Use: KeyUseSignature,
|
||||||
|
Algorithm: SigningAlgRSAUsingSHA256,
|
||||||
|
Key: keyRSA2048,
|
||||||
|
CertificateChain: certRSA2048,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
schema.JWK{
|
||||||
|
KeyID: "rsa2048-rs384",
|
||||||
|
Use: KeyUseSignature,
|
||||||
|
Algorithm: SigningAlgRSAUsingSHA384,
|
||||||
|
Key: keyRSA2048,
|
||||||
|
CertificateChain: certRSA2048,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
schema.JWK{
|
||||||
|
KeyID: "rsa2048-rs512",
|
||||||
|
Use: KeyUseSignature,
|
||||||
|
Algorithm: SigningAlgRSAUsingSHA512,
|
||||||
|
Key: keyRSA2048,
|
||||||
|
CertificateChain: certRSA2048,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
schema.JWK{
|
||||||
|
KeyID: "rsa4096-rs256",
|
||||||
|
Use: KeyUseSignature,
|
||||||
|
Algorithm: SigningAlgRSAUsingSHA256,
|
||||||
|
Key: keyRSA4096,
|
||||||
|
CertificateChain: certRSA4096,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
schema.JWK{
|
||||||
|
KeyID: "rsa4096-rs384",
|
||||||
|
Use: KeyUseSignature,
|
||||||
|
Algorithm: SigningAlgRSAUsingSHA384,
|
||||||
|
Key: keyRSA4096,
|
||||||
|
CertificateChain: certRSA4096,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
schema.JWK{
|
||||||
|
KeyID: "rsa4096-rs512",
|
||||||
|
Use: KeyUseSignature,
|
||||||
|
Algorithm: SigningAlgRSAUsingSHA512,
|
||||||
|
Key: keyRSA4096,
|
||||||
|
CertificateChain: certRSA4096,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
schema.JWK{
|
||||||
|
KeyID: "rsa2048-rs256",
|
||||||
|
Use: KeyUseSignature,
|
||||||
|
Algorithm: SigningAlgRSAPSSUsingSHA256,
|
||||||
|
Key: keyRSA2048,
|
||||||
|
CertificateChain: certRSA2048,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
schema.JWK{
|
||||||
|
KeyID: "rsa2048-ps384",
|
||||||
|
Use: KeyUseSignature,
|
||||||
|
Algorithm: SigningAlgRSAPSSUsingSHA384,
|
||||||
|
Key: keyRSA2048,
|
||||||
|
CertificateChain: certRSA2048,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
schema.JWK{
|
||||||
|
KeyID: "rsa2048-ps512",
|
||||||
|
Use: KeyUseSignature,
|
||||||
|
Algorithm: SigningAlgRSAPSSUsingSHA512,
|
||||||
|
Key: keyRSA2048,
|
||||||
|
CertificateChain: certRSA2048,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
schema.JWK{
|
||||||
|
KeyID: "rsa4096-ps256",
|
||||||
|
Use: KeyUseSignature,
|
||||||
|
Algorithm: SigningAlgRSAPSSUsingSHA256,
|
||||||
|
Key: keyRSA4096,
|
||||||
|
CertificateChain: certRSA4096,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
schema.JWK{
|
||||||
|
KeyID: "rsa4096-ps384",
|
||||||
|
Use: KeyUseSignature,
|
||||||
|
Algorithm: SigningAlgRSAPSSUsingSHA384,
|
||||||
|
Key: keyRSA4096,
|
||||||
|
CertificateChain: certRSA4096,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
schema.JWK{
|
||||||
|
KeyID: "rsa4096-ps512",
|
||||||
|
Use: KeyUseSignature,
|
||||||
|
Algorithm: SigningAlgRSAPSSUsingSHA512,
|
||||||
|
Key: keyRSA4096,
|
||||||
|
CertificateChain: certRSA4096,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
schema.JWK{
|
||||||
|
KeyID: "ecdsaP256",
|
||||||
|
Use: KeyUseSignature,
|
||||||
|
Algorithm: SigningAlgECDSAUsingP256AndSHA256,
|
||||||
|
Key: keyECDSAP256,
|
||||||
|
CertificateChain: certECDSAP256,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
schema.JWK{
|
||||||
|
KeyID: "ecdsaP384",
|
||||||
|
Use: KeyUseSignature,
|
||||||
|
Algorithm: SigningAlgECDSAUsingP384AndSHA384,
|
||||||
|
Key: keyECDSAP384,
|
||||||
|
CertificateChain: certECDSAP384,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
schema.JWK{
|
||||||
|
KeyID: "ecdsaP521",
|
||||||
|
Use: KeyUseSignature,
|
||||||
|
Algorithm: SigningAlgECDSAUsingP521AndSHA512,
|
||||||
|
Key: keyECDSAP521,
|
||||||
|
CertificateChain: certECDSAP521,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.have.KeyID, func(t *testing.T) {
|
||||||
|
t.Run("Generating", func(t *testing.T) {
|
||||||
|
var (
|
||||||
|
jwk *JWK
|
||||||
|
)
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
jwk = NewJWK(tc.have)
|
||||||
|
|
||||||
|
signer := jwk.Strategy()
|
||||||
|
|
||||||
|
claims := fjwt.MapClaims{}
|
||||||
|
header := &fjwt.Headers{
|
||||||
|
Extra: map[string]any{
|
||||||
|
"kid": jwk.kid,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
tokenString, sig, err := signer.Generate(ctx, claims, header)
|
||||||
|
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotEqual(t, "", tokenString)
|
||||||
|
assert.NotEqual(t, "", sig)
|
||||||
|
|
||||||
|
sigd, err := signer.GetSignature(ctx, tokenString)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, sig, sigd)
|
||||||
|
|
||||||
|
token, err := signer.Decode(ctx, tokenString)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotNil(t, token)
|
||||||
|
fmt.Println(tokenString)
|
||||||
|
|
||||||
|
assert.True(t, token.Valid())
|
||||||
|
assert.Equal(t, jwk.alg.Alg(), string(token.Method))
|
||||||
|
|
||||||
|
sigv, err := signer.Validate(ctx, tokenString)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, sig, sigv)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Marshalling", func(t *testing.T) {
|
||||||
|
var (
|
||||||
|
jwk *JWK
|
||||||
|
out jose.JSONWebKey
|
||||||
|
data []byte
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
|
||||||
|
jwk = NewJWK(tc.have)
|
||||||
|
|
||||||
|
strategy := jwk.Strategy()
|
||||||
|
|
||||||
|
assert.NotNil(t, strategy)
|
||||||
|
|
||||||
|
signer, ok := strategy.(*Signer)
|
||||||
|
|
||||||
|
require.True(t, ok)
|
||||||
|
|
||||||
|
assert.NotNil(t, signer)
|
||||||
|
|
||||||
|
key, err := signer.GetPublicKey(context.Background())
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotNil(t, key)
|
||||||
|
|
||||||
|
key, err = jwk.GetPrivateKey(context.Background())
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotNil(t, key)
|
||||||
|
|
||||||
|
data, err = json.Marshal(jwk.JWK())
|
||||||
|
|
||||||
|
assert.NoError(t, err)
|
||||||
|
require.NotNil(t, data)
|
||||||
|
|
||||||
|
assert.NoError(t, json.Unmarshal(data, &out))
|
||||||
|
|
||||||
|
assert.True(t, out.IsPublic())
|
||||||
|
assert.Equal(t, tc.have.KeyID, out.KeyID)
|
||||||
|
assert.Equal(t, tc.have.KeyID, jwk.KeyID())
|
||||||
|
assert.Equal(t, tc.have.Use, out.Use)
|
||||||
|
assert.Equal(t, tc.have.Algorithm, out.Algorithm)
|
||||||
|
assert.NotNil(t, out.Key)
|
||||||
|
assert.NotNil(t, out.Certificates)
|
||||||
|
assert.NotNil(t, out.CertificateThumbprintSHA1)
|
||||||
|
assert.NotNil(t, out.CertificateThumbprintSHA256)
|
||||||
|
assert.True(t, out.Valid())
|
||||||
|
|
||||||
|
data, err = json.Marshal(jwk.PrivateJWK())
|
||||||
|
|
||||||
|
assert.NoError(t, err)
|
||||||
|
require.NotNil(t, data)
|
||||||
|
assert.NoError(t, json.Unmarshal(data, &out))
|
||||||
|
|
||||||
|
assert.False(t, out.IsPublic())
|
||||||
|
assert.Equal(t, tc.have.KeyID, out.KeyID)
|
||||||
|
assert.Equal(t, tc.have.Use, out.Use)
|
||||||
|
assert.Equal(t, tc.have.Algorithm, out.Algorithm)
|
||||||
|
assert.NotNil(t, out.Key)
|
||||||
|
assert.NotNil(t, out.Certificates)
|
||||||
|
assert.NotNil(t, out.CertificateThumbprintSHA1)
|
||||||
|
assert.NotNil(t, out.CertificateThumbprintSHA256)
|
||||||
|
assert.True(t, out.Valid())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,34 +13,31 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewOpenIDConnectProvider new-ups a OpenIDConnectProvider.
|
// 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 {
|
if config == nil {
|
||||||
return nil, nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
provider = &OpenIDConnectProvider{
|
provider = &OpenIDConnectProvider{
|
||||||
JSONWriter: herodot.NewJSONWriter(nil),
|
JSONWriter: herodot.NewJSONWriter(nil),
|
||||||
Store: NewStore(config, store),
|
Store: NewStore(config, store),
|
||||||
|
KeyManager: NewKeyManager(config),
|
||||||
Config: NewConfig(config, templates),
|
Config: NewConfig(config, templates),
|
||||||
}
|
}
|
||||||
|
|
||||||
provider.OAuth2Provider = fosite.NewOAuth2Provider(provider.Store, provider.Config)
|
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{
|
provider.Config.Strategy.OpenID = &openid.DefaultStrategy{
|
||||||
Signer: provider.KeyManager.Strategy(),
|
Signer: provider.KeyManager,
|
||||||
Config: provider.Config,
|
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.Config.Strategy.ClientAuthentication = provider.DefaultClientAuthenticationStrategy
|
||||||
|
|
||||||
provider.discovery = NewOpenIDConnectWellKnownConfiguration(config)
|
provider.discovery = NewOpenIDConnectWellKnownConfiguration(config)
|
||||||
|
|
||||||
return provider, nil
|
return provider
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetOAuth2WellKnownConfiguration returns the discovery document for the OAuth Configuration.
|
// GetOAuth2WellKnownConfiguration returns the discovery document for the OAuth Configuration.
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package oidc
|
package oidc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"net/url"
|
"net/url"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
@ -11,16 +12,15 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestOpenIDConnectProvider_NewOpenIDConnectProvider_NotConfigured(t *testing.T) {
|
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)
|
assert.Nil(t, provider)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewOpenIDConnectProvider_ShouldEnableOptionalDiscoveryValues(t *testing.T) {
|
func TestNewOpenIDConnectProvider_ShouldEnableOptionalDiscoveryValues(t *testing.T) {
|
||||||
provider, err := NewOpenIDConnectProvider(&schema.OpenIDConnectConfiguration{
|
provider := NewOpenIDConnectProvider(&schema.OpenIDConnectConfiguration{
|
||||||
IssuerCertificateChain: schema.X509CertificateChain{},
|
IssuerCertificateChain: schema.X509CertificateChain{},
|
||||||
IssuerPrivateKey: MustParseRSAPrivateKey(exampleIssuerPrivateKey),
|
IssuerPrivateKey: keyRSA2048,
|
||||||
EnablePKCEPlainChallenge: true,
|
EnablePKCEPlainChallenge: true,
|
||||||
HMACSecret: badhmac,
|
HMACSecret: badhmac,
|
||||||
Clients: []schema.OpenIDConnectClientConfiguration{
|
Clients: []schema.OpenIDConnectClientConfiguration{
|
||||||
|
@ -36,7 +36,7 @@ func TestNewOpenIDConnectProvider_ShouldEnableOptionalDiscoveryValues(t *testing
|
||||||
},
|
},
|
||||||
}, nil, nil)
|
}, nil, nil)
|
||||||
|
|
||||||
assert.NoError(t, err)
|
require.NotNil(t, provider)
|
||||||
|
|
||||||
disco := provider.GetOpenIDConnectWellKnownConfiguration(examplecom)
|
disco := provider.GetOpenIDConnectWellKnownConfiguration(examplecom)
|
||||||
|
|
||||||
|
@ -50,9 +50,9 @@ func TestNewOpenIDConnectProvider_ShouldEnableOptionalDiscoveryValues(t *testing
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GoodConfiguration(t *testing.T) {
|
func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GoodConfiguration(t *testing.T) {
|
||||||
provider, err := NewOpenIDConnectProvider(&schema.OpenIDConnectConfiguration{
|
provider := NewOpenIDConnectProvider(&schema.OpenIDConnectConfiguration{
|
||||||
IssuerCertificateChain: schema.X509CertificateChain{},
|
IssuerCertificateChain: schema.X509CertificateChain{},
|
||||||
IssuerPrivateKey: MustParseRSAPrivateKey(exampleIssuerPrivateKey),
|
IssuerPrivateKey: keyRSA2048,
|
||||||
HMACSecret: badhmac,
|
HMACSecret: badhmac,
|
||||||
Clients: []schema.OpenIDConnectClientConfiguration{
|
Clients: []schema.OpenIDConnectClientConfiguration{
|
||||||
{
|
{
|
||||||
|
@ -86,13 +86,12 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GoodConfiguration(t *tes
|
||||||
}, nil, nil)
|
}, nil, nil)
|
||||||
|
|
||||||
assert.NotNil(t, provider)
|
assert.NotNil(t, provider)
|
||||||
assert.NoError(t, err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOpenIDConnectWellKnownConfiguration(t *testing.T) {
|
func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOpenIDConnectWellKnownConfiguration(t *testing.T) {
|
||||||
provider, err := NewOpenIDConnectProvider(&schema.OpenIDConnectConfiguration{
|
provider := NewOpenIDConnectProvider(&schema.OpenIDConnectConfiguration{
|
||||||
IssuerCertificateChain: schema.X509CertificateChain{},
|
IssuerCertificateChain: schema.X509CertificateChain{},
|
||||||
IssuerPrivateKey: MustParseRSAPrivateKey(exampleIssuerPrivateKey),
|
IssuerPrivateKey: keyRSA2048,
|
||||||
HMACSecret: "asbdhaaskmdlkamdklasmdlkams",
|
HMACSecret: "asbdhaaskmdlkamdklasmdlkams",
|
||||||
Clients: []schema.OpenIDConnectClientConfiguration{
|
Clients: []schema.OpenIDConnectClientConfiguration{
|
||||||
{
|
{
|
||||||
|
@ -106,7 +105,7 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOpenIDConnectWellKnow
|
||||||
},
|
},
|
||||||
}, nil, nil)
|
}, nil, nil)
|
||||||
|
|
||||||
assert.NoError(t, err)
|
require.NotNil(t, provider)
|
||||||
|
|
||||||
disco := provider.GetOpenIDConnectWellKnownConfiguration(examplecom)
|
disco := provider.GetOpenIDConnectWellKnownConfiguration(examplecom)
|
||||||
|
|
||||||
|
@ -153,19 +152,41 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOpenIDConnectWellKnow
|
||||||
assert.Contains(t, disco.TokenEndpointAuthMethodsSupported, ClientAuthMethodClientSecretJWT)
|
assert.Contains(t, disco.TokenEndpointAuthMethodsSupported, ClientAuthMethodClientSecretJWT)
|
||||||
assert.Contains(t, disco.TokenEndpointAuthMethodsSupported, ClientAuthMethodNone)
|
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.Len(t, disco.GrantTypesSupported, 3)
|
||||||
assert.Contains(t, disco.GrantTypesSupported, GrantTypeAuthorizationCode)
|
assert.Contains(t, disco.GrantTypesSupported, GrantTypeAuthorizationCode)
|
||||||
assert.Contains(t, disco.GrantTypesSupported, GrantTypeRefreshToken)
|
assert.Contains(t, disco.GrantTypesSupported, GrantTypeRefreshToken)
|
||||||
assert.Contains(t, disco.GrantTypesSupported, GrantTypeImplicit)
|
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.Len(t, disco.IDTokenSigningAlgValuesSupported, 1)
|
||||||
assert.Contains(t, disco.IDTokenSigningAlgValuesSupported, SigningAlgRSAUsingSHA256)
|
assert.Contains(t, disco.IDTokenSigningAlgValuesSupported, SigningAlgRSAUsingSHA256)
|
||||||
|
|
||||||
assert.Len(t, disco.UserinfoSigningAlgValuesSupported, 2)
|
assert.Len(t, disco.UserinfoSigningAlgValuesSupported, 2)
|
||||||
assert.Contains(t, disco.UserinfoSigningAlgValuesSupported, SigningAlgRSAUsingSHA256)
|
assert.Equal(t, disco.UserinfoSigningAlgValuesSupported[0], SigningAlgRSAUsingSHA256)
|
||||||
assert.Contains(t, disco.UserinfoSigningAlgValuesSupported, SigningAlgNone)
|
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.Len(t, disco.ClaimsSupported, 18)
|
||||||
assert.Contains(t, disco.ClaimsSupported, ClaimAuthenticationMethodsReference)
|
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, ClaimGroups)
|
||||||
assert.Contains(t, disco.ClaimsSupported, ClaimPreferredUsername)
|
assert.Contains(t, disco.ClaimsSupported, ClaimPreferredUsername)
|
||||||
assert.Contains(t, disco.ClaimsSupported, ClaimFullName)
|
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) {
|
func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOAuth2WellKnownConfiguration(t *testing.T) {
|
||||||
provider, err := NewOpenIDConnectProvider(&schema.OpenIDConnectConfiguration{
|
provider := NewOpenIDConnectProvider(&schema.OpenIDConnectConfiguration{
|
||||||
IssuerCertificateChain: schema.X509CertificateChain{},
|
IssuerCertificateChain: schema.X509CertificateChain{},
|
||||||
IssuerPrivateKey: MustParseRSAPrivateKey(exampleIssuerPrivateKey),
|
IssuerPrivateKey: keyRSA2048,
|
||||||
HMACSecret: "asbdhaaskmdlkamdklasmdlkams",
|
HMACSecret: "asbdhaaskmdlkamdklasmdlkams",
|
||||||
Clients: []schema.OpenIDConnectClientConfiguration{
|
Clients: []schema.OpenIDConnectClientConfiguration{
|
||||||
{
|
{
|
||||||
|
@ -205,7 +230,7 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOAuth2WellKnownConfig
|
||||||
},
|
},
|
||||||
}, nil, nil)
|
}, nil, nil)
|
||||||
|
|
||||||
assert.NoError(t, err)
|
require.NotNil(t, provider)
|
||||||
|
|
||||||
disco := provider.GetOAuth2WellKnownConfiguration(examplecom)
|
disco := provider.GetOAuth2WellKnownConfiguration(examplecom)
|
||||||
|
|
||||||
|
@ -278,9 +303,9 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOAuth2WellKnownConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOpenIDConnectWellKnownConfigurationWithPlainPKCE(t *testing.T) {
|
func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOpenIDConnectWellKnownConfigurationWithPlainPKCE(t *testing.T) {
|
||||||
provider, err := NewOpenIDConnectProvider(&schema.OpenIDConnectConfiguration{
|
provider := NewOpenIDConnectProvider(&schema.OpenIDConnectConfiguration{
|
||||||
IssuerCertificateChain: schema.X509CertificateChain{},
|
IssuerCertificateChain: schema.X509CertificateChain{},
|
||||||
IssuerPrivateKey: MustParseRSAPrivateKey(exampleIssuerPrivateKey),
|
IssuerPrivateKey: keyRSA2048,
|
||||||
HMACSecret: "asbdhaaskmdlkamdklasmdlkams",
|
HMACSecret: "asbdhaaskmdlkamdklasmdlkams",
|
||||||
EnablePKCEPlainChallenge: true,
|
EnablePKCEPlainChallenge: true,
|
||||||
Clients: []schema.OpenIDConnectClientConfiguration{
|
Clients: []schema.OpenIDConnectClientConfiguration{
|
||||||
|
@ -295,7 +320,7 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOpenIDConnectWellKnow
|
||||||
},
|
},
|
||||||
}, nil, nil)
|
}, nil, nil)
|
||||||
|
|
||||||
assert.NoError(t, err)
|
require.NotNil(t, provider)
|
||||||
|
|
||||||
disco := provider.GetOpenIDConnectWellKnownConfiguration(examplecom)
|
disco := provider.GetOpenIDConnectWellKnownConfiguration(examplecom)
|
||||||
|
|
||||||
|
@ -303,3 +328,44 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOpenIDConnectWellKnow
|
||||||
assert.Equal(t, PKCEChallengeMethodSHA256, disco.CodeChallengeMethodsSupported[0])
|
assert.Equal(t, PKCEChallengeMethodSHA256, disco.CodeChallengeMethodsSupported[0])
|
||||||
assert.Equal(t, PKCEChallengeMethodPlain, disco.CodeChallengeMethodsSupported[1])
|
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)
|
||||||
|
}
|
||||||
|
|
|
@ -15,7 +15,7 @@ import (
|
||||||
func TestOpenIDConnectStore_GetClientPolicy(t *testing.T) {
|
func TestOpenIDConnectStore_GetClientPolicy(t *testing.T) {
|
||||||
s := NewStore(&schema.OpenIDConnectConfiguration{
|
s := NewStore(&schema.OpenIDConnectConfiguration{
|
||||||
IssuerCertificateChain: schema.X509CertificateChain{},
|
IssuerCertificateChain: schema.X509CertificateChain{},
|
||||||
IssuerPrivateKey: MustParseRSAPrivateKey(exampleIssuerPrivateKey),
|
IssuerPrivateKey: keyRSA2048,
|
||||||
Clients: []schema.OpenIDConnectClientConfiguration{
|
Clients: []schema.OpenIDConnectClientConfiguration{
|
||||||
{
|
{
|
||||||
ID: myclient,
|
ID: myclient,
|
||||||
|
@ -47,7 +47,7 @@ func TestOpenIDConnectStore_GetClientPolicy(t *testing.T) {
|
||||||
func TestOpenIDConnectStore_GetInternalClient(t *testing.T) {
|
func TestOpenIDConnectStore_GetInternalClient(t *testing.T) {
|
||||||
s := NewStore(&schema.OpenIDConnectConfiguration{
|
s := NewStore(&schema.OpenIDConnectConfiguration{
|
||||||
IssuerCertificateChain: schema.X509CertificateChain{},
|
IssuerCertificateChain: schema.X509CertificateChain{},
|
||||||
IssuerPrivateKey: MustParseRSAPrivateKey(exampleIssuerPrivateKey),
|
IssuerPrivateKey: keyRSA2048,
|
||||||
Clients: []schema.OpenIDConnectClientConfiguration{
|
Clients: []schema.OpenIDConnectClientConfiguration{
|
||||||
{
|
{
|
||||||
ID: myclient,
|
ID: myclient,
|
||||||
|
@ -82,7 +82,7 @@ func TestOpenIDConnectStore_GetInternalClient_ValidClient(t *testing.T) {
|
||||||
|
|
||||||
s := NewStore(&schema.OpenIDConnectConfiguration{
|
s := NewStore(&schema.OpenIDConnectConfiguration{
|
||||||
IssuerCertificateChain: schema.X509CertificateChain{},
|
IssuerCertificateChain: schema.X509CertificateChain{},
|
||||||
IssuerPrivateKey: MustParseRSAPrivateKey(exampleIssuerPrivateKey),
|
IssuerPrivateKey: keyRSA2048,
|
||||||
Clients: []schema.OpenIDConnectClientConfiguration{c1},
|
Clients: []schema.OpenIDConnectClientConfiguration{c1},
|
||||||
}, nil)
|
}, nil)
|
||||||
|
|
||||||
|
@ -110,7 +110,7 @@ func TestOpenIDConnectStore_GetInternalClient_InvalidClient(t *testing.T) {
|
||||||
|
|
||||||
s := NewStore(&schema.OpenIDConnectConfiguration{
|
s := NewStore(&schema.OpenIDConnectConfiguration{
|
||||||
IssuerCertificateChain: schema.X509CertificateChain{},
|
IssuerCertificateChain: schema.X509CertificateChain{},
|
||||||
IssuerPrivateKey: MustParseRSAPrivateKey(exampleIssuerPrivateKey),
|
IssuerPrivateKey: keyRSA2048,
|
||||||
Clients: []schema.OpenIDConnectClientConfiguration{c1},
|
Clients: []schema.OpenIDConnectClientConfiguration{c1},
|
||||||
}, nil)
|
}, nil)
|
||||||
|
|
||||||
|
@ -122,7 +122,7 @@ func TestOpenIDConnectStore_GetInternalClient_InvalidClient(t *testing.T) {
|
||||||
func TestOpenIDConnectStore_IsValidClientID(t *testing.T) {
|
func TestOpenIDConnectStore_IsValidClientID(t *testing.T) {
|
||||||
s := NewStore(&schema.OpenIDConnectConfiguration{
|
s := NewStore(&schema.OpenIDConnectConfiguration{
|
||||||
IssuerCertificateChain: schema.X509CertificateChain{},
|
IssuerCertificateChain: schema.X509CertificateChain{},
|
||||||
IssuerPrivateKey: MustParseRSAPrivateKey(exampleIssuerPrivateKey),
|
IssuerPrivateKey: keyRSA2048,
|
||||||
Clients: []schema.OpenIDConnectClientConfiguration{
|
Clients: []schema.OpenIDConnectClientConfiguration{
|
||||||
{
|
{
|
||||||
ID: myclient,
|
ID: myclient,
|
||||||
|
|
|
@ -122,7 +122,8 @@ type BaseClient struct {
|
||||||
ResponseTypes []string
|
ResponseTypes []string
|
||||||
ResponseModes []fosite.ResponseModeType
|
ResponseModes []fosite.ResponseModeType
|
||||||
|
|
||||||
UserinfoSigningAlgorithm string
|
IDTokenSigningAlg string
|
||||||
|
UserinfoSigningAlg string
|
||||||
|
|
||||||
Policy authorization.Level
|
Policy authorization.Level
|
||||||
|
|
||||||
|
@ -150,7 +151,9 @@ type Client interface {
|
||||||
GetSecret() algorithm.Digest
|
GetSecret() algorithm.Digest
|
||||||
GetSectorIdentifier() string
|
GetSectorIdentifier() string
|
||||||
GetConsentResponseBody(consent *model.OAuth2ConsentSession) ConsentGetResponseBody
|
GetConsentResponseBody(consent *model.OAuth2ConsentSession) ConsentGetResponseBody
|
||||||
GetUserinfoSigningAlgorithm() string
|
|
||||||
|
GetUserinfoSigningAlg() string
|
||||||
|
GetIDTokenSigningAlg() string
|
||||||
|
|
||||||
GetPAREnforcement() bool
|
GetPAREnforcement() bool
|
||||||
GetPKCEEnforcement() 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.
|
// ConsentGetResponseBody schema of the response body of the consent GET endpoint.
|
||||||
type ConsentGetResponseBody struct {
|
type ConsentGetResponseBody struct {
|
||||||
ClientID string `json:"client_id"`
|
ClientID string `json:"client_id"`
|
||||||
|
|
|
@ -4,9 +4,101 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/ory/fosite"
|
"github.com/ory/fosite"
|
||||||
|
"gopkg.in/square/go-jose.v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
// IsPushedAuthorizedRequest returns true if the requester has a PushedAuthorizationRequest redirect_uri value.
|
// IsPushedAuthorizedRequest returns true if the requester has a PushedAuthorizationRequest redirect_uri value.
|
||||||
func IsPushedAuthorizedRequest(r fosite.Requester, prefix string) bool {
|
func IsPushedAuthorizedRequest(r fosite.Requester, prefix string) bool {
|
||||||
return strings.HasPrefix(r.GetRequestForm().Get(FormParameterRequestURI), prefix)
|
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"
|
||||||
|
)
|
||||||
|
|
|
@ -31,6 +31,7 @@ const (
|
||||||
BlockTypePKIXPublicKey = "PUBLIC KEY"
|
BlockTypePKIXPublicKey = "PUBLIC KEY"
|
||||||
BlockTypeCertificate = "CERTIFICATE"
|
BlockTypeCertificate = "CERTIFICATE"
|
||||||
BlockTypeCertificateRequest = "CERTIFICATE REQUEST"
|
BlockTypeCertificateRequest = "CERTIFICATE REQUEST"
|
||||||
|
BlockTypeX509CRL = "X509 CRL"
|
||||||
|
|
||||||
KeyAlgorithmRSA = "RSA"
|
KeyAlgorithmRSA = "RSA"
|
||||||
KeyAlgorithmECDSA = "ECDSA"
|
KeyAlgorithmECDSA = "ECDSA"
|
||||||
|
|
|
@ -13,6 +13,7 @@ import (
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"math/big"
|
"math/big"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"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 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 {
|
switch block.Type {
|
||||||
case BlockTypeRSAPrivateKey:
|
case BlockTypeRSAPrivateKey:
|
||||||
key, err = x509.ParsePKCS1PrivateKey(block.Bytes)
|
return x509.ParsePKCS1PrivateKey(block.Bytes)
|
||||||
case BlockTypeECDSAPrivateKey:
|
case BlockTypeECDSAPrivateKey:
|
||||||
key, err = x509.ParseECPrivateKey(block.Bytes)
|
return x509.ParseECPrivateKey(block.Bytes)
|
||||||
case BlockTypePKCS8PrivateKey:
|
case BlockTypePKCS8PrivateKey:
|
||||||
key, err = x509.ParsePKCS8PrivateKey(block.Bytes)
|
return x509.ParsePKCS8PrivateKey(block.Bytes)
|
||||||
case BlockTypeRSAPublicKey:
|
case BlockTypeRSAPublicKey:
|
||||||
key, err = x509.ParsePKCS1PublicKey(block.Bytes)
|
return x509.ParsePKCS1PublicKey(block.Bytes)
|
||||||
case BlockTypePKIXPublicKey:
|
case BlockTypePKIXPublicKey:
|
||||||
key, err = x509.ParsePKIXPublicKey(block.Bytes)
|
return x509.ParsePKIXPublicKey(block.Bytes)
|
||||||
case BlockTypeCertificate:
|
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:
|
default:
|
||||||
return nil, fmt.Errorf("unknown block type: %s", block.Type)
|
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.
|
// 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
|
return certPool, warnings, errors
|
||||||
}
|
}
|
||||||
|
|
||||||
// WriteCertificateBytesToPEM writes a certificate/csr to a file in the PEM format.
|
// WriteCertificateBytesAsPEMToPath writes a certificate/csr to a file in the PEM format.
|
||||||
func WriteCertificateBytesToPEM(path string, csr bool, certs ...[]byte) (err error) {
|
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
|
blockType := BlockTypeCertificate
|
||||||
if csr {
|
if csr {
|
||||||
blockType = BlockTypeCertificateRequest
|
blockType = BlockTypeCertificateRequest
|
||||||
|
@ -319,28 +404,36 @@ func WriteCertificateBytesToPEM(path string, csr bool, certs ...[]byte) (err err
|
||||||
blocks[i] = &pem.Block{Type: blockType, Bytes: cert}
|
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.
|
// WritePEMBlocksToPath writes a set of *pem.Blocks to a file.
|
||||||
func WritePEM(path string, blocks ...*pem.Block) (err error) {
|
func WritePEMBlocksToPath(path string, blocks ...*pem.Block) (err error) {
|
||||||
var out *os.File
|
var out *os.File
|
||||||
|
|
||||||
if out, err = os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600); err != nil {
|
if out, err = os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, block := range blocks {
|
if err = WritePEMBlocksToWriter(out, blocks...); err != nil {
|
||||||
if err = pem.Encode(out, block); err != nil {
|
|
||||||
_ = out.Close()
|
_ = out.Close()
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return out.Close()
|
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.
|
// 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) {
|
func WriteKeyToPEM(key any, path string, pkcs8 bool) (err error) {
|
||||||
block, err := PEMBlockFromX509Key(key, pkcs8)
|
block, err := PEMBlockFromX509Key(key, pkcs8)
|
||||||
|
@ -348,7 +441,7 @@ func WriteKeyToPEM(key any, path string, pkcs8 bool) (err error) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return WritePEM(path, block)
|
return WritePEMBlocksToPath(path, block)
|
||||||
}
|
}
|
||||||
|
|
||||||
// PEMBlockFromX509Key turns a PublicKey or PrivateKey into a pem.Block.
|
// PEMBlockFromX509Key turns a PublicKey or PrivateKey into a pem.Block.
|
||||||
|
|
Loading…
Reference in New Issue