feat(oidc): multiple jwk algorithms (#5279)
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>pull/5437/head
parent
1dbfbc5f88
commit
cef374cdc1
|
@ -1521,6 +1521,10 @@ notifier:
|
|||
## the 'client_secret_jwt' token_endpoint_auth_method.
|
||||
# token_endpoint_auth_signing_alg: HS256
|
||||
|
||||
## The permitted client authentication signing algorithm for the Token Endpoint for this client when using
|
||||
## the 'client_secret_jwt' token_endpoint_auth_method.
|
||||
# token_endpoint_auth_signing_alg: HS256
|
||||
|
||||
## The policy to require for this client; one_factor or two_factor.
|
||||
# authorization_policy: 'two_factor'
|
||||
|
||||
|
|
|
@ -28,12 +28,12 @@ More information about the beta can be found in the [roadmap](../../roadmap/acti
|
|||
|
||||
## Configuration
|
||||
|
||||
{{< config-alert-example >}}
|
||||
The following snippet provides a sample-configuration for the OIDC identity provider explaining each field in detail.
|
||||
|
||||
```yaml
|
||||
identity_providers:
|
||||
oidc:
|
||||
hmac_secret: 'this_is_a_secret_abc123abc123abc'
|
||||
hmac_secret: this_is_a_secret_abc123abc123abc
|
||||
issuer_certificate_chain: |
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIC5jCCAc6gAwIBAgIRAK4Sj7FiN6PXo/urPfO4E7owDQYJKoZIhvcNAQELBQAw
|
||||
|
@ -101,54 +101,59 @@ identity_providers:
|
|||
27GoE2i5mh6Yez6VAYbUuns3FcwIsMyWLq043Tu2DNkx9ijOOAuQzw^invalid..
|
||||
DO NOT USE==
|
||||
-----END RSA PRIVATE KEY-----
|
||||
access_token_lifespan: '1h'
|
||||
authorize_code_lifespan: '1m'
|
||||
id_token_lifespan: '1h'
|
||||
refresh_token_lifespan: '90m'
|
||||
issuer_jwks:
|
||||
- key_id: ''
|
||||
algorithm: 'RS256'
|
||||
key: |
|
||||
<private key data>
|
||||
certificate_chain: |
|
||||
<certificate chain data>
|
||||
access_token_lifespan: 1h
|
||||
authorize_code_lifespan: 1m
|
||||
id_token_lifespan: 1h
|
||||
refresh_token_lifespan: 90m
|
||||
enable_client_debug_messages: false
|
||||
enforce_pkce: 'public_clients_only'
|
||||
enforce_pkce: public_clients_only
|
||||
cors:
|
||||
endpoints:
|
||||
- 'authorization'
|
||||
- 'token'
|
||||
- 'revocation'
|
||||
- 'introspection'
|
||||
- authorization
|
||||
- token
|
||||
- revocation
|
||||
- introspection
|
||||
allowed_origins:
|
||||
- 'https://example.com'
|
||||
- https://example.com
|
||||
allowed_origins_from_client_redirect_uris: false
|
||||
clients:
|
||||
- id: 'myapp'
|
||||
description: 'My Application'
|
||||
- id: myapp
|
||||
description: My Application
|
||||
secret: '$pbkdf2-sha512$310000$c8p78n7pUMln0jzvd4aK4Q$JNRBzwAo0ek5qKn50cFzzvE9RXV88h1wJn5KGiHrD0YKtZaR/nCb2CJPOsKaPK0hjf.9yHxzQGZziziccp6Yng' # The digest of 'insecure_secret'.
|
||||
sector_identifier: ''
|
||||
public: false
|
||||
authorization_policy: 'two_factor'
|
||||
consent_mode: 'explicit'
|
||||
pre_configured_consent_duration: '1w'
|
||||
authorization_policy: two_factor
|
||||
consent_mode: explicit
|
||||
pre_configured_consent_duration: 1w
|
||||
audience: []
|
||||
scopes:
|
||||
- 'openid'
|
||||
- 'groups'
|
||||
- 'email'
|
||||
- 'profile'
|
||||
- openid
|
||||
- groups
|
||||
- email
|
||||
- profile
|
||||
redirect_uris:
|
||||
- 'https://oidc.example.com:8080/oauth2/callback'
|
||||
- https://oidc.example.com:8080/oauth2/callback
|
||||
grant_types:
|
||||
- 'refresh_token'
|
||||
- 'authorization_code'
|
||||
- refresh_token
|
||||
- authorization_code
|
||||
response_types:
|
||||
- 'code'
|
||||
- code
|
||||
response_modes:
|
||||
- 'form_post'
|
||||
- 'query'
|
||||
- 'fragment'
|
||||
userinfo_signing_algorithm: 'none'
|
||||
- form_post
|
||||
- query
|
||||
- fragment
|
||||
userinfo_signing_algorithm: none
|
||||
```
|
||||
|
||||
## Options
|
||||
|
||||
This section describes the individual configuration options.
|
||||
|
||||
### hmac_secret
|
||||
|
||||
{{< confkey type="string" required="yes" >}}
|
||||
|
@ -163,23 +168,6 @@ It's __strongly recommended__ this is a
|
|||
[Random Alphanumeric String](../../reference/guides/generating-secure-values.md#generating-a-random-alphanumeric-string)
|
||||
with 64 or more characters.
|
||||
|
||||
### issuer_certificate_chain
|
||||
|
||||
{{< confkey type="string" required="no" >}}
|
||||
|
||||
The certificate chain/bundle to be used with the [issuer_private_key](#issuer_private_key) DER base64 ([RFC4648])
|
||||
encoded PEM format used to sign/encrypt the [OpenID Connect 1.0] [JWT]'s. When configured it enables the [x5c] and [x5t]
|
||||
JSON key's in the JWKs [Discoverable Endpoint](../../integration/openid-connect/introduction.md#discoverable-endpoints)
|
||||
as per [RFC7517].
|
||||
|
||||
[RFC7517]: https://datatracker.ietf.org/doc/html/rfc7517
|
||||
[x5c]: https://datatracker.ietf.org/doc/html/rfc7517#section-4.7
|
||||
[x5t]: https://datatracker.ietf.org/doc/html/rfc7517#section-4.8
|
||||
|
||||
The first certificate in the chain must have the public key for the [issuer_private_key](#issuerprivatekey), each
|
||||
certificate in the chain must be valid for the current date, and each certificate in the chain should be signed by the
|
||||
certificate immediately following it if present.
|
||||
|
||||
### issuer_private_key
|
||||
|
||||
{{< confkey type="string" required="yes" >}}
|
||||
|
@ -199,13 +187,85 @@ The private key *__MUST__*:
|
|||
If the [issuer_certificate_chain](#issuercertificatechain) is provided the private key must include matching public
|
||||
key data for the first certificate in the chain.
|
||||
|
||||
### issuer_certificate_chain
|
||||
|
||||
{{< confkey type="string" required="no" >}}
|
||||
|
||||
The certificate chain/bundle to be used with the [issuer_private_key](#issuer_private_key) DER base64 ([RFC4648])
|
||||
encoded PEM format used to sign/encrypt the [OpenID Connect 1.0] [JWT]'s. When configured it enables the [x5c] and [x5t]
|
||||
JSON key's in the JWKs [Discoverable Endpoint](../../integration/openid-connect/introduction.md#discoverable-endpoints)
|
||||
as per [RFC7517].
|
||||
|
||||
[RFC7517]: https://datatracker.ietf.org/doc/html/rfc7517
|
||||
[x5c]: https://datatracker.ietf.org/doc/html/rfc7517#section-4.7
|
||||
[x5t]: https://datatracker.ietf.org/doc/html/rfc7517#section-4.8
|
||||
|
||||
The first certificate in the chain must have the public key for the [issuer_private_key](#issuerprivatekey), each
|
||||
certificate in the chain must be valid for the current date, and each certificate in the chain should be signed by the
|
||||
certificate immediately following it if present.
|
||||
|
||||
### issuer_jwks
|
||||
|
||||
{{< confkey type="list(object" required="no" >}}
|
||||
|
||||
The list of JWKS instead of or in addition to the [issuer_private_key](#issuerprivatekey) and
|
||||
[issuer_certificate_chain](#issuercertificatechain). Can also accept ECDSA Private Key's and Certificates.
|
||||
|
||||
#### key_id
|
||||
|
||||
{{< confkey type="string" default="<thumbprint of public key>" required="no" >}}
|
||||
|
||||
Completely optional, and generally discouraged unless there is a collision between the automatically generated key id's.
|
||||
If provided must be a unique string with 7 or less alphanumeric characters.
|
||||
|
||||
This value is the first 7 characters of the public key thumbprint (SHA1) encoded into hexadecimal.
|
||||
|
||||
#### algorithm
|
||||
|
||||
{{< confkey type="string" required="no" >}}
|
||||
|
||||
The algorithm for this key. This value must be unique. It's automatically detected based on the type of key.
|
||||
|
||||
#### key
|
||||
|
||||
{{< confkey type="string" required="yes" >}}
|
||||
|
||||
The private key associated with this key entry.
|
||||
|
||||
The private key used to sign/encrypt the [OpenID Connect 1.0] issued [JWT]'s. The key must be generated by the administrator
|
||||
and can be done by following the
|
||||
[Generating an RSA Keypair](../../reference/guides/generating-secure-values.md#generating-an-rsa-keypair) guide.
|
||||
|
||||
The private key *__MUST__*:
|
||||
* Be a PEM block encoded in the DER base64 format ([RFC4648]).
|
||||
* Be one of:
|
||||
* An RSA key with a key size of at least 2048 bits.
|
||||
* An ECDSA private key with one of the P-256, P-384, or P-521 elliptical curves.
|
||||
|
||||
If the [certificate_chain](#certificatechain) is provided the private key must include matching public
|
||||
key data for the first certificate in the chain.
|
||||
|
||||
#### certificate_chain
|
||||
|
||||
{{< confkey type="string" required="no" >}}
|
||||
|
||||
The certificate chain/bundle to be used with the [key](#key) DER base64 ([RFC4648])
|
||||
encoded PEM format used to sign/encrypt the [OpenID Connect 1.0] [JWT]'s. When configured it enables the [x5c] and [x5t]
|
||||
JSON key's in the JWKs [Discoverable Endpoint](../../integration/openid-connect/introduction.md#discoverable-endpoints)
|
||||
as per [RFC7517].
|
||||
|
||||
[RFC7517]: https://datatracker.ietf.org/doc/html/rfc7517
|
||||
[x5c]: https://datatracker.ietf.org/doc/html/rfc7517#section-4.7
|
||||
[x5t]: https://datatracker.ietf.org/doc/html/rfc7517#section-4.8
|
||||
|
||||
The first certificate in the chain must have the public key for the [key](#key), each certificate in the chain must be
|
||||
valid for the current date, and each certificate in the chain should be signed by the certificate immediately following
|
||||
it if present.
|
||||
|
||||
### access_token_lifespan
|
||||
|
||||
{{< confkey type="duration" default="1h" required="no" >}}
|
||||
|
||||
*__Reference Note:__ This configuration option uses the [duration common syntax](../prologue/common.md#duration).
|
||||
Please see the [documentation](../prologue/common.md#duration) on this format for more information.*
|
||||
|
||||
The maximum lifetime of an access token. It's generally recommended keeping this short similar to the default.
|
||||
For more information read these docs about [token lifespan].
|
||||
|
||||
|
@ -213,9 +273,6 @@ For more information read these docs about [token lifespan].
|
|||
|
||||
{{< confkey type="duration" default="1m" required="no" >}}
|
||||
|
||||
*__Reference Note:__ This configuration option uses the [duration common syntax](../prologue/common.md#duration).
|
||||
Please see the [documentation](../prologue/common.md#duration) on this format for more information.*
|
||||
|
||||
The maximum lifetime of an authorize code. This can be rather short, as the authorize code should only be needed to
|
||||
obtain the other token types. For more information read these docs about [token lifespan].
|
||||
|
||||
|
@ -223,18 +280,12 @@ obtain the other token types. For more information read these docs about [token
|
|||
|
||||
{{< confkey type="duration" default="1h" required="no" >}}
|
||||
|
||||
*__Reference Note:__ This configuration option uses the [duration common syntax](../prologue/common.md#duration).
|
||||
Please see the [documentation](../prologue/common.md#duration) on this format for more information.*
|
||||
|
||||
The maximum lifetime of an ID token. For more information read these docs about [token lifespan].
|
||||
|
||||
### refresh_token_lifespan
|
||||
|
||||
{{< confkey type="string" default="90m" required="no" >}}
|
||||
|
||||
*__Reference Note:__ This configuration option uses the [duration common syntax](../prologue/common.md#duration).
|
||||
Please see the [documentation](../prologue/common.md#duration) on this format for more information.*
|
||||
|
||||
The maximum lifetime of a refresh token. The
|
||||
refresh token can be used to obtain new refresh tokens as well as access tokens or id tokens with an
|
||||
up-to-date expiration. For more information read these docs about [token lifespan].
|
||||
|
@ -300,9 +351,6 @@ When enabled all authorization requests must use the [Pushed Authorization Reque
|
|||
|
||||
{{< confkey type="duration" default="5m" required="no" >}}
|
||||
|
||||
*__Reference Note:__ This configuration option uses the [duration common syntax](../prologue/common.md#duration).
|
||||
Please see the [documentation](../prologue/common.md#duration) on this format for more information.*
|
||||
|
||||
The maximum amount of time between the [Pushed Authorization Requests] flow being initiated and the generated
|
||||
`request_uri` being utilized by a client.
|
||||
|
||||
|
@ -590,8 +638,8 @@ Configures the consent mode. The following table describes the different modes:
|
|||
|
||||
{{< confkey type="duration" default="1w" required="no" >}}
|
||||
|
||||
*__Reference Note:__ This configuration option uses the [duration common syntax](../prologue/common.md#duration).
|
||||
Please see the [documentation](../prologue/common.md#duration) on this format for more information.*
|
||||
*__Note:__ This setting uses the [duration notation format](../prologue/common.md#duration-notation-format). Please see
|
||||
the [common options](../prologue/common.md#duration-notation-format) documentation for information on this format.*
|
||||
|
||||
Specifying this in the configuration without a consent [consent_mode] enables the `pre-configured` mode. If this is
|
||||
specified as well as the [consent_mode] then it only has an effect if the [consent_mode] is `pre-configured` or `auto`.
|
||||
|
|
|
@ -101,6 +101,39 @@ This scope includes the profile information the authentication backend reports a
|
|||
| preferred_username | string | username | The username the user used to login with |
|
||||
| name | string | display_name | The users display name |
|
||||
|
||||
## Signing and Encryption Algorithms
|
||||
|
||||
[OpenID Connect 1.0] and OAuth 2.0 support a wide variety of signature and encryption algorithms. Authelia supports
|
||||
a subset of these.
|
||||
|
||||
### Response Object
|
||||
|
||||
Authelia's response objects can have the following signature algorithms:
|
||||
|
||||
| Algorithm | Key Type | Hashing Algorithm | Use | JWK Default Conditions | Notes |
|
||||
|:---------:|:-----------:|:-----------------:|:---------:|:--------------------------------------------:|:----------------------------------------------------:|
|
||||
| RS256 | RSA | SHA-256 | Signature | RSA Private Key without a specific algorithm | Requires an RSA Private Key with 2048 bits or more |
|
||||
| RS384 | RSA | SHA-384 | Signature | N/A | Requires an RSA Private Key with 2048 bits or more |
|
||||
| RS512 | RSA | SHA-512 | Signature | N/A | Requires an RSA Private Key with 2048 bits or more |
|
||||
| ES256 | ECDSA P-256 | SHA-256 | Signature | ECDSA Private Key with the P-256 curve | |
|
||||
| ES384 | ECDSA P-384 | SHA-384 | Signature | ECDSA Private Key with the P-384 curve | |
|
||||
| ES512 | ECDSA P-521 | SHA-512 | Signature | ECDSA Private Key with the P-521 curve | Requires an ECDSA Private Key with 2048 bits or more |
|
||||
| PS256 | RSA (MGF1) | SHA-256 | Signature | N/A | Requires an RSA Private Key with 2048 bits or more |
|
||||
| PS384 | RSA (MGF1) | SHA-384 | Signature | N/A | Requires an RSA Private Key with 2048 bits or more |
|
||||
| PS512 | RSA (MGF1) | SHA-512 | Signature | N/A | Requires an RSA Private Key with 2048 bits or more |
|
||||
|
||||
### Request Object
|
||||
|
||||
|
||||
| Algorithm | Key Type | Hashing Algorithm | Use | Notes |
|
||||
|:---------:|:------------------:|:-----------------:|:---------:|:--------------------------------------------------:|
|
||||
| none | None | None | N/A | N/A |
|
||||
| HS256 | HMAC Shared Secret | SHA-256 | Signature | [Client Authentication Method] `client_secret_jwt` |
|
||||
| HS384 | HMAC Shared Secret | SHA-384 | Signature | [Client Authentication Method] `client_secret_jwt` |
|
||||
| HS512 | HMAC Shared Secret | SHA-512 | Signature | [Client Authentication Method] `client_secret_jwt` |
|
||||
|
||||
[Client Authentication Method]: #client-authentication-method
|
||||
|
||||
## Parameters
|
||||
|
||||
The following section describes advanced parameters which can be used in various endpoints as well as their related
|
||||
|
|
|
@ -162,9 +162,7 @@ func (ctx *CmdCtx) LoadProviders() (warns, errs []error) {
|
|||
ctx.providers.Notifier = notification.NewFileNotifier(*ctx.config.Notifier.FileSystem)
|
||||
}
|
||||
|
||||
if ctx.providers.OpenIDConnect, err = oidc.NewOpenIDConnectProvider(ctx.config.IdentityProviders.OIDC, ctx.providers.StorageProvider, ctx.providers.Templates); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
ctx.providers.OpenIDConnect = oidc.NewOpenIDConnectProvider(ctx.config.IdentityProviders.OIDC, ctx.providers.StorageProvider, ctx.providers.Templates)
|
||||
|
||||
if ctx.config.Telemetry.Metrics.Enabled {
|
||||
ctx.providers.Metrics = metrics.NewPrometheus()
|
||||
|
|
|
@ -335,7 +335,7 @@ func (ctx *CmdCtx) CryptoCertificateRequestRunE(cmd *cobra.Command, _ []string)
|
|||
return err
|
||||
}
|
||||
|
||||
if err = utils.WriteCertificateBytesToPEM(csrPath, true, csr); err != nil {
|
||||
if err = utils.WriteCertificateBytesAsPEMToPath(csrPath, true, csr); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -430,7 +430,7 @@ func (ctx *CmdCtx) CryptoCertificateGenerateRunE(cmd *cobra.Command, _ []string,
|
|||
return err
|
||||
}
|
||||
|
||||
if err = utils.WriteCertificateBytesToPEM(certificatePath, false, certificate); err != nil {
|
||||
if err = utils.WriteCertificateBytesAsPEMToPath(certificatePath, false, certificate); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
|
@ -198,7 +198,7 @@ func cryptoGenerateCertificateBundlesFromCmd(cmd *cobra.Command, b *strings.Buil
|
|||
|
||||
b.WriteString(fmt.Sprintf("\tCertificate (chain): %s\n", pathPEM))
|
||||
|
||||
if err = utils.WritePEM(pathPEM, blocks...); err != nil {
|
||||
if err = utils.WritePEMBlocksToPath(pathPEM, blocks...); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
@ -220,7 +220,7 @@ func cryptoGenerateCertificateBundlesFromCmd(cmd *cobra.Command, b *strings.Buil
|
|||
|
||||
b.WriteString(fmt.Sprintf("\tCertificate (priv-chain): %s\n", pathPEM))
|
||||
|
||||
if err = utils.WritePEM(pathPEM, blocks...); err != nil {
|
||||
if err = utils.WritePEMBlocksToPath(pathPEM, blocks...); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1521,6 +1521,10 @@ notifier:
|
|||
## the 'client_secret_jwt' token_endpoint_auth_method.
|
||||
# token_endpoint_auth_signing_alg: HS256
|
||||
|
||||
## The permitted client authentication signing algorithm for the Token Endpoint for this client when using
|
||||
## the 'client_secret_jwt' token_endpoint_auth_method.
|
||||
# token_endpoint_auth_signing_alg: HS256
|
||||
|
||||
## The policy to require for this client; one_factor or two_factor.
|
||||
# authorization_policy: 'two_factor'
|
||||
|
||||
|
|
|
@ -513,6 +513,30 @@ func StringToCryptoPrivateKeyHookFunc() mapstructure.DecodeHookFuncType {
|
|||
}
|
||||
}
|
||||
|
||||
// StringToCryptographicKeyHookFunc decodes strings to schema.CryptographicKey's.
|
||||
func StringToCryptographicKeyHookFunc() mapstructure.DecodeHookFuncType {
|
||||
return func(f reflect.Type, t reflect.Type, data any) (value any, err error) {
|
||||
if f.Kind() != reflect.String {
|
||||
return data, nil
|
||||
}
|
||||
|
||||
field, _ := reflect.TypeOf(schema.JWK{}).FieldByName("Key")
|
||||
expectedType := field.Type
|
||||
|
||||
if t != expectedType {
|
||||
return data, nil
|
||||
}
|
||||
|
||||
dataStr := data.(string)
|
||||
|
||||
if value, err = utils.ParseX509FromPEM([]byte(dataStr)); err != nil {
|
||||
return nil, fmt.Errorf(errFmtDecodeHookCouldNotParseBasic, "", expectedType, err)
|
||||
}
|
||||
|
||||
return value, nil
|
||||
}
|
||||
}
|
||||
|
||||
// StringToPrivateKeyHookFunc decodes strings to rsa.PrivateKey's.
|
||||
func StringToPrivateKeyHookFunc() mapstructure.DecodeHookFuncType {
|
||||
return func(f reflect.Type, t reflect.Type, data any) (value any, err error) {
|
||||
|
|
|
@ -21,6 +21,7 @@ func TestKoanfEnvironmentCallback(t *testing.T) {
|
|||
keyMap := map[string]string{
|
||||
DefaultEnvPrefix + "KEY_EXAMPLE_UNDERSCORE": "key.example_underscore",
|
||||
}
|
||||
|
||||
ignoredKeys := []string{DefaultEnvPrefix + "SOME_SECRET"}
|
||||
|
||||
callback := koanfEnvironmentCallback(keyMap, ignoredKeys, DefaultEnvPrefix, DefaultEnvDelimiter)
|
||||
|
|
|
@ -65,6 +65,7 @@ func unmarshal(ko *koanf.Koanf, val *schema.StructValidator, path string, o any)
|
|||
StringToX509CertificateChainHookFunc(),
|
||||
StringToPrivateKeyHookFunc(),
|
||||
StringToCryptoPrivateKeyHookFunc(),
|
||||
StringToCryptographicKeyHookFunc(),
|
||||
StringToTLSVersionHookFunc(),
|
||||
StringToPasswordDigestHookFunc(),
|
||||
ToTimeDurationHookFunc(),
|
||||
|
|
|
@ -245,6 +245,37 @@ func TestShouldLoadURLList(t *testing.T) {
|
|||
assert.Equal(t, "https://example.com", config.IdentityProviders.OIDC.CORS.AllowedOrigins[1].String())
|
||||
}
|
||||
|
||||
/*
|
||||
func TestShouldLoadNewOIDCConfig(t *testing.T) {
|
||||
val := schema.NewStructValidator()
|
||||
_, config, err := Load(val, NewDefaultSources([]string{"./test_resources/config_oidc_modern.yml"}, DefaultEnvPrefix, DefaultEnvDelimiter)...)
|
||||
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Len(t, val.Errors(), 0)
|
||||
assert.Len(t, val.Warnings(), 0)
|
||||
|
||||
val.Clear()
|
||||
|
||||
validator.ValidateIdentityProviders(&config.IdentityProviders, val)
|
||||
|
||||
assert.Len(t, val.Errors(), 0)
|
||||
|
||||
assert.Len(t, config.IdentityProviders.OIDC.IssuerJWKS.Keys, 2)
|
||||
assert.Equal(t, "keya", config.IdentityProviders.OIDC.IssuerJWKS.DefaultKeyID)
|
||||
|
||||
assert.Equal(t, oidc.KeyUseSignature, config.IdentityProviders.OIDC.IssuerJWKS.Keys["keya"].Use)
|
||||
assert.Equal(t, oidc.SigningAlgRSAUsingSHA256, config.IdentityProviders.OIDC.IssuerJWKS.Keys["keya"].Algorithm)
|
||||
|
||||
assert.Equal(t, oidc.KeyUseSignature, config.IdentityProviders.OIDC.IssuerJWKS.Keys["ec521"].Use)
|
||||
assert.Equal(t, oidc.SigningAlgECDSAUsingP521AndSHA512, config.IdentityProviders.OIDC.IssuerJWKS.Keys["ec521"].Algorithm)
|
||||
|
||||
assert.Contains(t, config.IdentityProviders.OIDC.Discovery.RegisteredJWKSigningAlgs, oidc.SigningAlgRSAUsingSHA256)
|
||||
assert.Contains(t, config.IdentityProviders.OIDC.Discovery.RegisteredJWKSigningAlgs, oidc.SigningAlgECDSAUsingP521AndSHA512)
|
||||
}.
|
||||
|
||||
*/
|
||||
|
||||
func TestShouldConfigureConsent(t *testing.T) {
|
||||
val := schema.NewStructValidator()
|
||||
keys, config, err := Load(val, NewDefaultSources([]string{"./test_resources/config_oidc.yml"}, DefaultEnvPrefix, DefaultEnvDelimiter)...)
|
||||
|
|
|
@ -17,6 +17,8 @@ type OpenIDConnectConfiguration struct {
|
|||
IssuerCertificateChain X509CertificateChain `koanf:"issuer_certificate_chain"`
|
||||
IssuerPrivateKey *rsa.PrivateKey `koanf:"issuer_private_key"`
|
||||
|
||||
IssuerJWKS []JWK `koanf:"issuer_jwks"`
|
||||
|
||||
AccessTokenLifespan time.Duration `koanf:"access_token_lifespan"`
|
||||
AuthorizeCodeLifespan time.Duration `koanf:"authorize_code_lifespan"`
|
||||
IDTokenLifespan time.Duration `koanf:"id_token_lifespan"`
|
||||
|
@ -32,6 +34,13 @@ type OpenIDConnectConfiguration struct {
|
|||
PAR OpenIDConnectPARConfiguration `koanf:"pushed_authorizations"`
|
||||
|
||||
Clients []OpenIDConnectClientConfiguration `koanf:"clients"`
|
||||
|
||||
Discovery OpenIDConnectDiscovery // MetaData value. Not configurable by users.
|
||||
}
|
||||
|
||||
type OpenIDConnectDiscovery struct {
|
||||
DefaultKeyID string
|
||||
RegisteredJWKSigningAlgs []string
|
||||
}
|
||||
|
||||
// OpenIDConnectPARConfiguration represents an OpenID Connect PAR config.
|
||||
|
@ -67,13 +76,15 @@ type OpenIDConnectClientConfiguration struct {
|
|||
TokenEndpointAuthMethod string `koanf:"token_endpoint_auth_method"`
|
||||
TokenEndpointAuthSigningAlg string `koanf:"token_endpoint_auth_signing_alg"`
|
||||
|
||||
IDTokenSigningAlg string `koanf:"id_token_signing_alg"`
|
||||
|
||||
Policy string `koanf:"authorization_policy"`
|
||||
|
||||
EnforcePAR bool `koanf:"enforce_par"`
|
||||
EnforcePKCE bool `koanf:"enforce_pkce"`
|
||||
|
||||
PKCEChallengeMethod string `koanf:"pkce_challenge_method"`
|
||||
UserinfoSigningAlgorithm string `koanf:"userinfo_signing_algorithm"`
|
||||
PKCEChallengeMethod string `koanf:"pkce_challenge_method"`
|
||||
UserinfoSigningAlg string `koanf:"userinfo_signing_algorithm"`
|
||||
|
||||
ConsentMode string `koanf:"consent_mode"`
|
||||
ConsentPreConfiguredDuration *time.Duration `koanf:"pre_configured_consent_duration"`
|
||||
|
@ -97,7 +108,8 @@ var DefaultOpenIDConnectClientConfiguration = OpenIDConnectClientConfiguration{
|
|||
ResponseTypes: []string{"code"},
|
||||
ResponseModes: []string{"form_post"},
|
||||
|
||||
UserinfoSigningAlgorithm: "none",
|
||||
IDTokenSigningAlg: "RS256",
|
||||
UserinfoSigningAlg: "none",
|
||||
ConsentMode: "auto",
|
||||
ConsentPreConfiguredDuration: &defaultOIDCClientConsentPreConfiguredDuration,
|
||||
}
|
||||
|
|
|
@ -20,6 +20,12 @@ var Keys = []string{
|
|||
"identity_providers.oidc.hmac_secret",
|
||||
"identity_providers.oidc.issuer_certificate_chain",
|
||||
"identity_providers.oidc.issuer_private_key",
|
||||
"identity_providers.oidc.issuer_jwks",
|
||||
"identity_providers.oidc.issuer_jwks[].key_id",
|
||||
"identity_providers.oidc.issuer_jwks[]",
|
||||
"identity_providers.oidc.issuer_jwks[].algorithm",
|
||||
"identity_providers.oidc.issuer_jwks[].key",
|
||||
"identity_providers.oidc.issuer_jwks[].certificate_chain",
|
||||
"identity_providers.oidc.access_token_lifespan",
|
||||
"identity_providers.oidc.authorize_code_lifespan",
|
||||
"identity_providers.oidc.id_token_lifespan",
|
||||
|
@ -47,6 +53,7 @@ var Keys = []string{
|
|||
"identity_providers.oidc.clients[].response_modes",
|
||||
"identity_providers.oidc.clients[].token_endpoint_auth_method",
|
||||
"identity_providers.oidc.clients[].token_endpoint_auth_signing_alg",
|
||||
"identity_providers.oidc.clients[].id_token_signing_alg",
|
||||
"identity_providers.oidc.clients[].authorization_policy",
|
||||
"identity_providers.oidc.clients[].enforce_par",
|
||||
"identity_providers.oidc.clients[].enforce_pkce",
|
||||
|
@ -54,6 +61,7 @@ var Keys = []string{
|
|||
"identity_providers.oidc.clients[].userinfo_signing_algorithm",
|
||||
"identity_providers.oidc.clients[].consent_mode",
|
||||
"identity_providers.oidc.clients[].pre_configured_consent_duration",
|
||||
"identity_providers.oidc",
|
||||
"authentication_backend.password_reset.disable",
|
||||
"authentication_backend.password_reset.custom_url",
|
||||
"authentication_backend.refresh_interval",
|
||||
|
|
|
@ -34,3 +34,12 @@ type ServerBuffers struct {
|
|||
Read int `koanf:"read"`
|
||||
Write int `koanf:"write"`
|
||||
}
|
||||
|
||||
// JWK represents a JWK.
|
||||
type JWK struct {
|
||||
KeyID string `koanf:"key_id"`
|
||||
Use string
|
||||
Algorithm string `koanf:"algorithm"`
|
||||
Key CryptographicKey `koanf:"key"`
|
||||
CertificateChain X509CertificateChain `koanf:"certificate_chain"`
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package schema
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/ed25519"
|
||||
|
@ -101,6 +102,10 @@ func NewX509CertificateChain(in string) (chain *X509CertificateChain, err error)
|
|||
return chain, nil
|
||||
}
|
||||
|
||||
func NewX509CertificateChainFromCerts(in []*x509.Certificate) (chain X509CertificateChain) {
|
||||
return X509CertificateChain{certs: in}
|
||||
}
|
||||
|
||||
// NewTLSVersion returns a new TLSVersion given a string.
|
||||
func NewTLSVersion(input string) (version *TLSVersion, err error) {
|
||||
switch strings.ReplaceAll(strings.ToUpper(input), " ", "") {
|
||||
|
@ -166,6 +171,9 @@ type CryptographicPrivateKey interface {
|
|||
Equal(x crypto.PrivateKey) bool
|
||||
}
|
||||
|
||||
// CryptographicKey represents an artificial cryptographic public or private key.
|
||||
type CryptographicKey any
|
||||
|
||||
// X509CertificateChain is a helper struct that holds a list of *x509.Certificate's.
|
||||
type X509CertificateChain struct {
|
||||
certs []*x509.Certificate
|
||||
|
@ -277,6 +285,24 @@ func (c *X509CertificateChain) Leaf() (leaf *x509.Certificate) {
|
|||
return c.certs[0]
|
||||
}
|
||||
|
||||
// EncodePEM encodes the entire chain as PEM bytes.
|
||||
func (c *X509CertificateChain) EncodePEM() (encoded []byte, err error) {
|
||||
buf := &bytes.Buffer{}
|
||||
|
||||
for _, cert := range c.certs {
|
||||
block := pem.Block{
|
||||
Type: blockCERTIFICATE,
|
||||
Bytes: cert.Raw,
|
||||
}
|
||||
|
||||
if err = pem.Encode(buf, &block); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
// Validate the X509CertificateChain ensuring the certificates were provided in the correct order
|
||||
// (with nth being signed by the nth+1), and that all of the certificates are valid based on the current time.
|
||||
func (c *X509CertificateChain) Validate() (err error) {
|
||||
|
|
|
@ -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 (
|
||||
errFmtOIDCNoClientsConfigured = "identity_providers: oidc: option 'clients' must have one or " +
|
||||
"more clients configured"
|
||||
errFmtOIDCNoPrivateKey = "identity_providers: oidc: option 'issuer_private_key' is required"
|
||||
errFmtOIDCNoPrivateKey = "identity_providers: oidc: option 'issuer_private_key' or `issuer_jwks` is required"
|
||||
errFmtOIDCInvalidPrivateKeyBitSize = "identity_providers: oidc: option 'issuer_private_key' must be an RSA private key with %d bits or more but it only has %d bits"
|
||||
errFmtOIDCInvalidPrivateKeyMalformedMissingPublicKey = "identity_providers: oidc: option 'issuer_private_key' must be a valid RSA private key but the provided data is missing the public key bits"
|
||||
errFmtOIDCCertificateMismatch = "identity_providers: oidc: option 'issuer_private_key' does not appear to be the private key the certificate provided by option 'issuer_certificate_chain'"
|
||||
|
@ -409,6 +409,7 @@ const (
|
|||
attrOIDCRedirectURIs = "redirect_uris"
|
||||
attrOIDCTokenAuthMethod = "token_endpoint_auth_method"
|
||||
attrOIDCUsrSigAlg = "userinfo_signing_algorithm"
|
||||
attrOIDCIDTokenSigAlg = "id_token_signing_alg"
|
||||
attrOIDCPKCEChallengeMethod = "pkce_challenge_method"
|
||||
)
|
||||
|
||||
|
@ -416,7 +417,6 @@ var (
|
|||
validOIDCCORSEndpoints = []string{oidc.EndpointAuthorization, oidc.EndpointPushedAuthorizationRequest, oidc.EndpointToken, oidc.EndpointIntrospection, oidc.EndpointRevocation, oidc.EndpointUserinfo}
|
||||
|
||||
validOIDCClientScopes = []string{oidc.ScopeOpenID, oidc.ScopeEmail, oidc.ScopeProfile, oidc.ScopeGroups, oidc.ScopeOfflineAccess}
|
||||
validOIDCClientUserinfoAlgorithms = []string{oidc.SigningAlgNone, oidc.SigningAlgRSAUsingSHA256}
|
||||
validOIDCClientConsentModes = []string{auto, oidc.ClientConsentModeImplicit.String(), oidc.ClientConsentModeExplicit.String(), oidc.ClientConsentModePreConfigured.String()}
|
||||
validOIDCClientResponseModes = []string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery, oidc.ResponseModeFragment}
|
||||
validOIDCClientResponseTypes = []string{oidc.ResponseTypeAuthorizationCodeFlow, oidc.ResponseTypeImplicitFlowIDToken, oidc.ResponseTypeImplicitFlowToken, oidc.ResponseTypeImplicitFlowBoth, oidc.ResponseTypeHybridFlowIDToken, oidc.ResponseTypeHybridFlowToken, oidc.ResponseTypeHybridFlowBoth}
|
||||
|
@ -428,6 +428,7 @@ var (
|
|||
validOIDCClientTokenEndpointAuthMethods = []string{oidc.ClientAuthMethodNone, oidc.ClientAuthMethodClientSecretPost, oidc.ClientAuthMethodClientSecretBasic, oidc.ClientAuthMethodClientSecretJWT}
|
||||
validOIDCClientTokenEndpointAuthMethodsConfidential = []string{oidc.ClientAuthMethodClientSecretPost, oidc.ClientAuthMethodClientSecretBasic}
|
||||
validOIDCClientTokenEndpointAuthSigAlgs = []string{oidc.SigningAlgHMACUsingSHA256, oidc.SigningAlgHMACUsingSHA384, oidc.SigningAlgHMACUsingSHA512}
|
||||
validOIDCIssuerJWKSigningAlgs = []string{oidc.SigningAlgRSAUsingSHA256, oidc.SigningAlgRSAPSSUsingSHA256, oidc.SigningAlgECDSAUsingP256AndSHA256, oidc.SigningAlgRSAUsingSHA384, oidc.SigningAlgRSAPSSUsingSHA384, oidc.SigningAlgECDSAUsingP384AndSHA384, oidc.SigningAlgRSAUsingSHA512, oidc.SigningAlgRSAPSSUsingSHA512, oidc.SigningAlgECDSAUsingP521AndSHA512}
|
||||
)
|
||||
|
||||
var (
|
||||
|
|
|
@ -1,12 +1,19 @@
|
|||
package validator
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/ed25519"
|
||||
"crypto/rsa"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"gopkg.in/square/go-jose.v2"
|
||||
|
||||
"github.com/authelia/authelia/v4/internal/configuration/schema"
|
||||
"github.com/authelia/authelia/v4/internal/oidc"
|
||||
"github.com/authelia/authelia/v4/internal/utils"
|
||||
|
@ -24,26 +31,9 @@ func validateOIDC(config *schema.OpenIDConnectConfiguration, val *schema.StructV
|
|||
|
||||
setOIDCDefaults(config)
|
||||
|
||||
switch {
|
||||
case config.IssuerPrivateKey == nil:
|
||||
val.Push(fmt.Errorf(errFmtOIDCNoPrivateKey))
|
||||
default:
|
||||
if config.IssuerCertificateChain.HasCertificates() {
|
||||
if !config.IssuerCertificateChain.EqualKey(config.IssuerPrivateKey) {
|
||||
val.Push(fmt.Errorf(errFmtOIDCCertificateMismatch))
|
||||
}
|
||||
validateOIDCIssuer(config, val)
|
||||
|
||||
if err := config.IssuerCertificateChain.Validate(); err != nil {
|
||||
val.Push(fmt.Errorf(errFmtOIDCCertificateChain, err))
|
||||
}
|
||||
}
|
||||
|
||||
if config.IssuerPrivateKey.PublicKey.N == nil {
|
||||
val.Push(fmt.Errorf(errFmtOIDCInvalidPrivateKeyMalformedMissingPublicKey))
|
||||
} else if config.IssuerPrivateKey.Size()*8 < 2048 {
|
||||
val.Push(fmt.Errorf(errFmtOIDCInvalidPrivateKeyBitSize, 2048, config.IssuerPrivateKey.Size()*8))
|
||||
}
|
||||
}
|
||||
sort.Sort(oidc.SortedSigningAlgs(config.Discovery.RegisteredJWKSigningAlgs))
|
||||
|
||||
if config.MinimumParameterEntropy != 0 && config.MinimumParameterEntropy < 8 {
|
||||
val.PushWarning(fmt.Errorf(errFmtOIDCServerInsecureParameterEntropy, config.MinimumParameterEntropy))
|
||||
|
@ -62,6 +52,163 @@ func validateOIDC(config *schema.OpenIDConnectConfiguration, val *schema.StructV
|
|||
}
|
||||
}
|
||||
|
||||
func validateOIDCIssuer(config *schema.OpenIDConnectConfiguration, val *schema.StructValidator) {
|
||||
switch {
|
||||
case config.IssuerPrivateKey != nil:
|
||||
validateOIDCIssuerLegacy(config, val)
|
||||
|
||||
fallthrough
|
||||
case len(config.IssuerJWKS) != 0:
|
||||
validateOIDCIssuerModern(config, val)
|
||||
default:
|
||||
val.Push(fmt.Errorf(errFmtOIDCNoPrivateKey))
|
||||
}
|
||||
}
|
||||
|
||||
func validateOIDCIssuerLegacy(config *schema.OpenIDConnectConfiguration, val *schema.StructValidator) {
|
||||
j := &jose.JSONWebKey{Key: &config.IssuerPrivateKey.PublicKey}
|
||||
|
||||
thumbprint, err := j.Thumbprint(crypto.SHA1)
|
||||
if err != nil {
|
||||
val.Push(fmt.Errorf("identity_providers: oidc: option 'issuer_private_key' failed to calculate thumbprint to configure key id value: %w", err))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
config.IssuerJWKS = append(config.IssuerJWKS, schema.JWK{
|
||||
KeyID: fmt.Sprintf("%x", thumbprint)[:6],
|
||||
Algorithm: oidc.SigningAlgRSAUsingSHA256,
|
||||
Use: oidc.KeyUseSignature,
|
||||
Key: config.IssuerPrivateKey,
|
||||
CertificateChain: config.IssuerCertificateChain,
|
||||
})
|
||||
}
|
||||
|
||||
//nolint:gocyclo // Refactor time permitting.
|
||||
func validateOIDCIssuerModern(config *schema.OpenIDConnectConfiguration, val *schema.StructValidator) {
|
||||
var (
|
||||
props *JWKProperties
|
||||
err error
|
||||
)
|
||||
|
||||
kids := make([]string, len(config.IssuerJWKS))
|
||||
|
||||
for i := 0; i < len(config.IssuerJWKS); i++ {
|
||||
if key, ok := config.IssuerJWKS[i].Key.(*rsa.PrivateKey); ok && key.PublicKey.N == nil {
|
||||
val.Push(fmt.Errorf("identity_providers: oidc: issuer_jwks: key #%d: option 'key' must be a valid RSA private key but the provided data is malformed as it's missing the public key bits", i+1))
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
switch n := len(config.IssuerJWKS[i].KeyID); {
|
||||
case n == 0:
|
||||
j := jose.JSONWebKey{}
|
||||
|
||||
switch key := config.IssuerJWKS[i].Key.(type) {
|
||||
case schema.CryptographicPrivateKey:
|
||||
j.Key = key.Public()
|
||||
case *rsa.PublicKey, *ecdsa.PublicKey, ed25519.PublicKey:
|
||||
j.Key = key
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
if j.Key == nil {
|
||||
break
|
||||
}
|
||||
|
||||
var thumbprint []byte
|
||||
|
||||
if thumbprint, err = j.Thumbprint(crypto.SHA1); err != nil {
|
||||
val.Push(fmt.Errorf("identity_providers: oidc: issuer_jwks: key #%d: option 'key' failed to calculate thumbprint to configure key id value: %w", i+1, err))
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
config.IssuerJWKS[i].KeyID = fmt.Sprintf("%x", thumbprint)[:6]
|
||||
case n > 7:
|
||||
val.Push(fmt.Errorf("identity_providers: oidc: issuer_jwks: key #%d with key id '%s': option `key_id`` must be 7 characters or less", i+1, config.IssuerJWKS[i].KeyID))
|
||||
}
|
||||
|
||||
if config.IssuerJWKS[i].KeyID != "" && utils.IsStringInSlice(config.IssuerJWKS[i].KeyID, kids) {
|
||||
val.Push(fmt.Errorf("identity_providers: oidc: issuer_jwks: key #%d with key id '%s': option 'key_id' must be unique", i+1, config.IssuerJWKS[i].KeyID))
|
||||
}
|
||||
|
||||
kids[i] = config.IssuerJWKS[i].KeyID
|
||||
|
||||
if !utils.IsStringAlphaNumeric(config.IssuerJWKS[i].KeyID) {
|
||||
val.Push(fmt.Errorf("identity_providers: oidc: issuer_jwks: key #%d with key id '%s': option 'key_id' must only have alphanumeric characters", i+1, config.IssuerJWKS[i].KeyID))
|
||||
}
|
||||
|
||||
if props, err = schemaJWKGetProperties(config.IssuerJWKS[i]); err != nil {
|
||||
val.Push(fmt.Errorf("identity_providers: oidc: issuer_jwks: key #%d with key id '%s': option 'key' failed to get key properties: %w", i+1, config.IssuerJWKS[i].KeyID, err))
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
switch config.IssuerJWKS[i].Use {
|
||||
case "":
|
||||
config.IssuerJWKS[i].Use = props.Use
|
||||
case oidc.KeyUseSignature:
|
||||
break
|
||||
default:
|
||||
val.Push(fmt.Errorf("identity_providers: oidc: issuer_jwks: key #%d with key id '%s': option '%s' must be one of %s but it's configured as '%s'", i+1, config.IssuerJWKS[i].KeyID, "use", strJoinOr([]string{oidc.KeyUseSignature}), config.IssuerJWKS[i].Use))
|
||||
}
|
||||
|
||||
switch {
|
||||
case config.IssuerJWKS[i].Algorithm == "":
|
||||
config.IssuerJWKS[i].Algorithm = props.Algorithm
|
||||
case utils.IsStringInSlice(config.IssuerJWKS[i].Algorithm, validOIDCIssuerJWKSigningAlgs):
|
||||
break
|
||||
default:
|
||||
val.Push(fmt.Errorf("identity_providers: oidc: issuer_jwks: key #%d with key id '%s': option '%s' must be one of %s but it's configured as '%s'", i+1, config.IssuerJWKS[i].KeyID, "algorithm", strJoinOr(validOIDCIssuerJWKSigningAlgs), config.IssuerJWKS[i].Algorithm))
|
||||
}
|
||||
|
||||
if config.IssuerJWKS[i].Algorithm != "" {
|
||||
if utils.IsStringInSlice(config.IssuerJWKS[i].Algorithm, config.Discovery.RegisteredJWKSigningAlgs) {
|
||||
val.Push(fmt.Errorf("identity_providers: oidc: issuer_jwks: key #%d with key id '%s': option 'algorithm' must be unique but another key is using it", i+1, config.IssuerJWKS[i].KeyID))
|
||||
} else {
|
||||
config.Discovery.RegisteredJWKSigningAlgs = append(config.Discovery.RegisteredJWKSigningAlgs, config.IssuerJWKS[i].Algorithm)
|
||||
}
|
||||
}
|
||||
|
||||
if config.IssuerJWKS[i].Algorithm == oidc.SigningAlgRSAUsingSHA256 && config.Discovery.DefaultKeyID == "" {
|
||||
config.Discovery.DefaultKeyID = config.IssuerJWKS[i].KeyID
|
||||
}
|
||||
|
||||
var checkEqualKey bool
|
||||
|
||||
switch key := config.IssuerJWKS[i].Key.(type) {
|
||||
case *rsa.PrivateKey:
|
||||
checkEqualKey = true
|
||||
|
||||
if key.Size() < 256 {
|
||||
checkEqualKey = false
|
||||
|
||||
val.Push(fmt.Errorf("identity_providers: oidc: issuer_jwks: key #%d with key id '%s': option 'key' is an RSA %d bit private key but it must be a RSA 2048 bit private key", i+1, config.IssuerJWKS[i].KeyID, key.Size()*8))
|
||||
}
|
||||
case *ecdsa.PrivateKey:
|
||||
checkEqualKey = true
|
||||
default:
|
||||
val.Push(fmt.Errorf("identity_providers: oidc: issuer_jwks: key #%d with key id '%s': option 'key' must be a *rsa.PrivateKey or *ecdsa.PrivateKey but it's a %T", i+1, config.IssuerJWKS[i].KeyID, key))
|
||||
}
|
||||
|
||||
if config.IssuerJWKS[i].CertificateChain.HasCertificates() {
|
||||
if checkEqualKey && !config.IssuerJWKS[i].CertificateChain.EqualKey(config.IssuerJWKS[i].Key) {
|
||||
val.Push(fmt.Errorf("identity_providers: oidc: issuer_jwks: key #%d with key id '%s': option 'key' does not appear to be the private key the certificate provided by option 'certificate_chain'", i+1, config.IssuerJWKS[i].KeyID))
|
||||
}
|
||||
|
||||
if err = config.IssuerJWKS[i].CertificateChain.Validate(); err != nil {
|
||||
val.Push(fmt.Errorf("identity_providers: oidc: issuer_jwks: key #%d with key id '%s': option 'certificate_chain' produced an error during validation of the chain: %w", i+1, config.IssuerJWKS[i].KeyID, err))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(config.Discovery.RegisteredJWKSigningAlgs) != 0 && !utils.IsStringInSlice(oidc.SigningAlgRSAUsingSHA256, config.Discovery.RegisteredJWKSigningAlgs) {
|
||||
val.Push(fmt.Errorf("identity_providers: oidc: issuer_jwks: keys: must at least have one key supporting the '%s' algorithm but only has %s", oidc.SigningAlgRSAUsingSHA256, strJoinAnd(config.Discovery.RegisteredJWKSigningAlgs)))
|
||||
}
|
||||
}
|
||||
|
||||
func setOIDCDefaults(config *schema.OpenIDConnectConfiguration) {
|
||||
if config.AccessTokenLifespan == time.Duration(0) {
|
||||
config.AccessTokenLifespan = schema.DefaultOpenIDConnectConfiguration.AccessTokenLifespan
|
||||
|
@ -228,7 +375,7 @@ func validateOIDCClient(c int, config *schema.OpenIDConnectConfiguration, val *s
|
|||
validateOIDCClientRedirectURIs(c, config, val, errDeprecatedFunc)
|
||||
|
||||
validateOIDCClientTokenEndpointAuth(c, config, val)
|
||||
validateOIDDClientUserinfoAlgorithm(c, config, val)
|
||||
validateOIDDClientSigningAlgs(c, config, val)
|
||||
|
||||
validateOIDCClientSectorIdentifier(c, config, val)
|
||||
}
|
||||
|
@ -516,13 +663,18 @@ func validateOIDCClientTokenEndpointAuth(c int, config *schema.OpenIDConnectConf
|
|||
}
|
||||
}
|
||||
|
||||
func validateOIDDClientUserinfoAlgorithm(c int, config *schema.OpenIDConnectConfiguration, val *schema.StructValidator) {
|
||||
if config.Clients[c].UserinfoSigningAlgorithm == "" {
|
||||
config.Clients[c].UserinfoSigningAlgorithm = schema.DefaultOpenIDConnectClientConfiguration.UserinfoSigningAlgorithm
|
||||
func validateOIDDClientSigningAlgs(c int, config *schema.OpenIDConnectConfiguration, val *schema.StructValidator) {
|
||||
if config.Clients[c].UserinfoSigningAlg == "" {
|
||||
config.Clients[c].UserinfoSigningAlg = schema.DefaultOpenIDConnectClientConfiguration.UserinfoSigningAlg
|
||||
} else if config.Clients[c].UserinfoSigningAlg != oidc.SigningAlgNone && !utils.IsStringInSlice(config.Clients[c].UserinfoSigningAlg, config.Discovery.RegisteredJWKSigningAlgs) {
|
||||
val.Push(fmt.Errorf(errFmtOIDCClientInvalidValue,
|
||||
config.Clients[c].ID, attrOIDCUsrSigAlg, strJoinOr(append(config.Discovery.RegisteredJWKSigningAlgs, oidc.SigningAlgNone)), config.Clients[c].UserinfoSigningAlg))
|
||||
}
|
||||
|
||||
if !utils.IsStringInSlice(config.Clients[c].UserinfoSigningAlgorithm, validOIDCClientUserinfoAlgorithms) {
|
||||
if config.Clients[c].IDTokenSigningAlg == "" {
|
||||
config.Clients[c].IDTokenSigningAlg = schema.DefaultOpenIDConnectClientConfiguration.IDTokenSigningAlg
|
||||
} else if !utils.IsStringInSlice(config.Clients[c].IDTokenSigningAlg, config.Discovery.RegisteredJWKSigningAlgs) {
|
||||
val.Push(fmt.Errorf(errFmtOIDCClientInvalidValue,
|
||||
config.Clients[c].ID, attrOIDCUsrSigAlg, strJoinOr(validOIDCClientUserinfoAlgorithms), config.Clients[c].UserinfoSigningAlgorithm))
|
||||
config.Clients[c].ID, attrOIDCIDTokenSigAlg, strJoinOr(config.Discovery.RegisteredJWKSigningAlgs), config.Clients[c].IDTokenSigningAlg))
|
||||
}
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,10 +1,17 @@
|
|||
package validator
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"crypto/ed25519"
|
||||
"crypto/elliptic"
|
||||
"crypto/rsa"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/net/publicsuffix"
|
||||
|
||||
"github.com/authelia/authelia/v4/internal/configuration/schema"
|
||||
"github.com/authelia/authelia/v4/internal/oidc"
|
||||
"github.com/authelia/authelia/v4/internal/utils"
|
||||
)
|
||||
|
||||
|
@ -107,3 +114,47 @@ func validateList(values, valid []string, chkDuplicate bool) (invalid, duplicate
|
|||
|
||||
return
|
||||
}
|
||||
|
||||
type JWKProperties struct {
|
||||
Use string
|
||||
Algorithm string
|
||||
Bits int
|
||||
Curve elliptic.Curve
|
||||
}
|
||||
|
||||
func schemaJWKGetProperties(jwk schema.JWK) (properties *JWKProperties, err error) {
|
||||
switch key := jwk.Key.(type) {
|
||||
case nil:
|
||||
return nil, fmt.Errorf("private key is nil")
|
||||
case ed25519.PrivateKey, ed25519.PublicKey:
|
||||
return &JWKProperties{}, nil
|
||||
case *rsa.PrivateKey:
|
||||
return &JWKProperties{oidc.KeyUseSignature, oidc.SigningAlgRSAUsingSHA256, key.Size(), nil}, nil
|
||||
case *rsa.PublicKey:
|
||||
return &JWKProperties{oidc.KeyUseSignature, oidc.SigningAlgRSAUsingSHA256, key.Size(), nil}, nil
|
||||
case *ecdsa.PublicKey:
|
||||
switch key.Curve {
|
||||
case elliptic.P256():
|
||||
return &JWKProperties{oidc.KeyUseSignature, oidc.SigningAlgECDSAUsingP256AndSHA256, -1, key.Curve}, nil
|
||||
case elliptic.P384():
|
||||
return &JWKProperties{oidc.KeyUseSignature, oidc.SigningAlgECDSAUsingP384AndSHA384, -1, key.Curve}, nil
|
||||
case elliptic.P521():
|
||||
return &JWKProperties{oidc.KeyUseSignature, oidc.SigningAlgECDSAUsingP521AndSHA512, -1, key.Curve}, nil
|
||||
default:
|
||||
return &JWKProperties{oidc.KeyUseSignature, "", -1, key.Curve}, nil
|
||||
}
|
||||
case *ecdsa.PrivateKey:
|
||||
switch key.Curve {
|
||||
case elliptic.P256():
|
||||
return &JWKProperties{oidc.KeyUseSignature, oidc.SigningAlgECDSAUsingP256AndSHA256, -1, key.Curve}, nil
|
||||
case elliptic.P384():
|
||||
return &JWKProperties{oidc.KeyUseSignature, oidc.SigningAlgECDSAUsingP384AndSHA384, -1, key.Curve}, nil
|
||||
case elliptic.P521():
|
||||
return &JWKProperties{oidc.KeyUseSignature, oidc.SigningAlgECDSAUsingP521AndSHA512, -1, key.Curve}, nil
|
||||
default:
|
||||
return &JWKProperties{oidc.KeyUseSignature, "", -1, key.Curve}, nil
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("the key type '%T' is unknown or not valid for the configuration", key)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,9 +8,9 @@ import (
|
|||
|
||||
// JSONWebKeySetGET returns the JSON Web Key Set. Used in OAuth 2.0 and OpenID Connect 1.0.
|
||||
func JSONWebKeySetGET(ctx *middlewares.AutheliaCtx) {
|
||||
ctx.SetContentType("application/json")
|
||||
ctx.SetContentTypeApplicationJSON()
|
||||
|
||||
if err := json.NewEncoder(ctx).Encode(ctx.Providers.OpenIDConnect.KeyManager.GetKeySet()); err != nil {
|
||||
if err := json.NewEncoder(ctx).Encode(ctx.Providers.OpenIDConnect.KeyManager.Set(ctx)); err != nil {
|
||||
ctx.Error(err, "failed to serve json web key set")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -127,7 +127,7 @@ func OpenIDConnectAuthorization(ctx *middlewares.AutheliaCtx, rw http.ResponseWr
|
|||
|
||||
ctx.Logger.Debugf("Authorization Request with id '%s' on client with id '%s' was successfully processed, proceeding to build Authorization Response", requester.GetID(), clientID)
|
||||
|
||||
session := oidc.NewSessionWithAuthorizeRequest(issuer, ctx.Providers.OpenIDConnect.KeyManager.GetActiveKeyID(),
|
||||
session := oidc.NewSessionWithAuthorizeRequest(issuer, ctx.Providers.OpenIDConnect.KeyManager.GetKIDFromAlg(ctx, client.GetIDTokenSigningAlg()),
|
||||
userSession.Username, userSession.AuthenticationMethodRefs.MarshalRFC8176(), extraClaims, authTime, consent, requester)
|
||||
|
||||
ctx.Logger.Tracef("Authorization Request with id '%s' on client with id '%s' creating session for Authorization Response for subject '%s' with username '%s' with claims: %+v",
|
||||
|
|
|
@ -145,11 +145,10 @@ func (s *ClientAuthenticationStrategySuite) SetupTest() {
|
|||
s.ctrl = gomock.NewController(s.T())
|
||||
s.store = mocks.NewMockStorage(s.ctrl)
|
||||
|
||||
var err error
|
||||
|
||||
secret := MustDecodeSecret("$plaintext$client-secret")
|
||||
|
||||
s.provider, err = oidc.NewOpenIDConnectProvider(&schema.OpenIDConnectConfiguration{
|
||||
s.provider = oidc.NewOpenIDConnectProvider(&schema.OpenIDConnectConfiguration{
|
||||
IssuerJWKS: []schema.JWK{},
|
||||
IssuerCertificateChain: schema.X509CertificateChain{},
|
||||
IssuerPrivateKey: MustParseRSAPrivateKey(exampleRSAPrivateKey),
|
||||
HMACSecret: "abc123",
|
||||
|
@ -370,8 +369,6 @@ func (s *ClientAuthenticationStrategySuite) SetupTest() {
|
|||
},
|
||||
},
|
||||
}, s.store, nil)
|
||||
|
||||
s.Require().NoError(err)
|
||||
}
|
||||
|
||||
func (s *ClientAuthenticationStrategySuite) TestShouldValidateAssertionHS256() {
|
||||
|
|
|
@ -99,8 +99,18 @@ func OpenIDConnectUserinfo(ctx *middlewares.AutheliaCtx, rw http.ResponseWriter,
|
|||
|
||||
ctx.Logger.Tracef("UserInfo Response with id '%s' on client with id '%s' is being sent with the following claims: %+v", requester.GetID(), clientID, claims)
|
||||
|
||||
switch client.GetUserinfoSigningAlgorithm() {
|
||||
case oidc.SigningAlgRSAUsingSHA256:
|
||||
switch alg := client.GetUserinfoSigningAlg(); alg {
|
||||
case oidc.SigningAlgNone, "":
|
||||
ctx.Providers.OpenIDConnect.Write(rw, req, claims)
|
||||
default:
|
||||
var jwk *oidc.JWK
|
||||
|
||||
if jwk = ctx.Providers.OpenIDConnect.KeyManager.GetByAlg(ctx, alg); jwk == nil {
|
||||
ctx.Providers.OpenIDConnect.WriteError(rw, req, errors.WithStack(fosite.ErrServerError.WithHintf("Unsupported UserInfo signing algorithm '%s'.", alg)))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
var jti uuid.UUID
|
||||
|
||||
if jti, err = uuid.NewRandom(); err != nil {
|
||||
|
@ -114,11 +124,11 @@ func OpenIDConnectUserinfo(ctx *middlewares.AutheliaCtx, rw http.ResponseWriter,
|
|||
|
||||
headers := &jwt.Headers{
|
||||
Extra: map[string]any{
|
||||
oidc.JWTHeaderKeyIdentifier: ctx.Providers.OpenIDConnect.KeyManager.GetActiveKeyID(),
|
||||
oidc.JWTHeaderKeyIdentifier: jwk.KeyID(),
|
||||
},
|
||||
}
|
||||
|
||||
if token, _, err = ctx.Providers.OpenIDConnect.KeyManager.Strategy().Generate(req.Context(), claims, headers); err != nil {
|
||||
if token, _, err = jwk.Strategy().Generate(req.Context(), claims, headers); err != nil {
|
||||
ctx.Providers.OpenIDConnect.WriteError(rw, req, err)
|
||||
|
||||
return
|
||||
|
@ -126,9 +136,5 @@ func OpenIDConnectUserinfo(ctx *middlewares.AutheliaCtx, rw http.ResponseWriter,
|
|||
|
||||
rw.Header().Set(fasthttp.HeaderContentType, "application/jwt")
|
||||
_, _ = rw.Write([]byte(token))
|
||||
case oidc.SigningAlgNone, "":
|
||||
ctx.Providers.OpenIDConnect.Write(rw, req, claims)
|
||||
default:
|
||||
ctx.Providers.OpenIDConnect.WriteError(rw, req, errors.WithStack(fosite.ErrServerError.WithHintf("Unsupported UserInfo signing algorithm '%s'.", client.GetUserinfoSigningAlgorithm())))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,7 +34,8 @@ func NewClient(config schema.OpenIDConnectClientConfiguration) (client Client) {
|
|||
|
||||
EnforcePAR: config.EnforcePAR,
|
||||
|
||||
UserinfoSigningAlgorithm: config.UserinfoSigningAlgorithm,
|
||||
IDTokenSigningAlg: config.IDTokenSigningAlg,
|
||||
UserinfoSigningAlg: config.UserinfoSigningAlg,
|
||||
|
||||
Policy: authorization.NewLevel(config.Policy),
|
||||
|
||||
|
@ -131,13 +132,22 @@ func (c *BaseClient) GetResponseModes() []fosite.ResponseModeType {
|
|||
return c.ResponseModes
|
||||
}
|
||||
|
||||
// GetUserinfoSigningAlgorithm returns the UserinfoSigningAlgorithm.
|
||||
func (c *BaseClient) GetUserinfoSigningAlgorithm() string {
|
||||
if c.UserinfoSigningAlgorithm == "" {
|
||||
c.UserinfoSigningAlgorithm = SigningAlgNone
|
||||
// GetIDTokenSigningAlg returns the IDTokenSigningAlg.
|
||||
func (c *BaseClient) GetIDTokenSigningAlg() (alg string) {
|
||||
if c.IDTokenSigningAlg == "" {
|
||||
c.IDTokenSigningAlg = SigningAlgRSAUsingSHA256
|
||||
}
|
||||
|
||||
return c.UserinfoSigningAlgorithm
|
||||
return c.IDTokenSigningAlg
|
||||
}
|
||||
|
||||
// GetUserinfoSigningAlg returns the UserinfoSigningAlg.
|
||||
func (c *BaseClient) GetUserinfoSigningAlg() string {
|
||||
if c.UserinfoSigningAlg == "" {
|
||||
c.UserinfoSigningAlg = SigningAlgNone
|
||||
}
|
||||
|
||||
return c.UserinfoSigningAlg
|
||||
}
|
||||
|
||||
// GetPAREnforcement returns EnforcePAR.
|
||||
|
@ -295,7 +305,7 @@ func (c *FullClient) GetTokenEndpointAuthMethod() string {
|
|||
if c.Public {
|
||||
c.TokenEndpointAuthMethod = ClientAuthMethodNone
|
||||
} else {
|
||||
c.TokenEndpointAuthMethod = ClientAuthMethodClientSecretPost
|
||||
c.TokenEndpointAuthMethod = ClientAuthMethodClientSecretBasic
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ package oidc
|
|||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/ory/fosite"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
@ -26,8 +27,8 @@ func TestNewClient(t *testing.T) {
|
|||
|
||||
bclient, ok := client.(*BaseClient)
|
||||
require.True(t, ok)
|
||||
assert.Equal(t, "", bclient.UserinfoSigningAlgorithm)
|
||||
assert.Equal(t, SigningAlgNone, client.GetUserinfoSigningAlgorithm())
|
||||
assert.Equal(t, "", bclient.UserinfoSigningAlg)
|
||||
assert.Equal(t, SigningAlgNone, client.GetUserinfoSigningAlg())
|
||||
|
||||
_, ok = client.(*FullClient)
|
||||
assert.False(t, ok)
|
||||
|
@ -51,7 +52,7 @@ func TestNewClient(t *testing.T) {
|
|||
assert.Equal(t, authorization.TwoFactor, client.GetAuthorizationPolicy())
|
||||
|
||||
config = schema.OpenIDConnectClientConfiguration{
|
||||
TokenEndpointAuthMethod: ClientAuthMethodClientSecretBasic,
|
||||
TokenEndpointAuthMethod: ClientAuthMethodClientSecretPost,
|
||||
}
|
||||
|
||||
client = NewClient(config)
|
||||
|
@ -61,18 +62,51 @@ func TestNewClient(t *testing.T) {
|
|||
var niljwks *jose.JSONWebKeySet
|
||||
|
||||
require.True(t, ok)
|
||||
assert.Equal(t, "", fclient.UserinfoSigningAlgorithm)
|
||||
assert.Equal(t, ClientAuthMethodClientSecretBasic, fclient.TokenEndpointAuthMethod)
|
||||
assert.Equal(t, ClientAuthMethodClientSecretBasic, fclient.GetTokenEndpointAuthMethod())
|
||||
assert.Equal(t, SigningAlgNone, client.GetUserinfoSigningAlgorithm())
|
||||
|
||||
assert.Equal(t, "", fclient.UserinfoSigningAlg)
|
||||
assert.Equal(t, SigningAlgNone, client.GetUserinfoSigningAlg())
|
||||
assert.Equal(t, SigningAlgNone, fclient.UserinfoSigningAlg)
|
||||
|
||||
assert.Equal(t, "", fclient.IDTokenSigningAlg)
|
||||
assert.Equal(t, SigningAlgRSAUsingSHA256, client.GetIDTokenSigningAlg())
|
||||
assert.Equal(t, SigningAlgRSAUsingSHA256, fclient.IDTokenSigningAlg)
|
||||
|
||||
assert.Equal(t, ClientAuthMethodClientSecretPost, fclient.TokenEndpointAuthMethod)
|
||||
assert.Equal(t, ClientAuthMethodClientSecretPost, fclient.GetTokenEndpointAuthMethod())
|
||||
|
||||
assert.Equal(t, "", fclient.TokenEndpointAuthSigningAlgorithm)
|
||||
assert.Equal(t, SigningAlgRSAUsingSHA256, fclient.GetTokenEndpointAuthSigningAlgorithm())
|
||||
assert.Equal(t, SigningAlgRSAUsingSHA256, fclient.TokenEndpointAuthSigningAlgorithm)
|
||||
|
||||
assert.Equal(t, "", fclient.RequestObjectSigningAlgorithm)
|
||||
assert.Equal(t, "", fclient.GetRequestObjectSigningAlgorithm())
|
||||
|
||||
fclient.RequestObjectSigningAlgorithm = SigningAlgRSAUsingSHA256
|
||||
assert.Equal(t, SigningAlgRSAUsingSHA256, fclient.GetRequestObjectSigningAlgorithm())
|
||||
|
||||
assert.Equal(t, "", fclient.JSONWebKeysURI)
|
||||
assert.Equal(t, "", fclient.GetJSONWebKeysURI())
|
||||
|
||||
fclient.JSONWebKeysURI = "https://example.com"
|
||||
assert.Equal(t, "https://example.com", fclient.GetJSONWebKeysURI())
|
||||
|
||||
assert.Equal(t, niljwks, fclient.JSONWebKeys)
|
||||
assert.Equal(t, niljwks, fclient.GetJSONWebKeys())
|
||||
|
||||
assert.Equal(t, ClientConsentMode(0), fclient.Consent.Mode)
|
||||
assert.Equal(t, time.Second*0, fclient.Consent.Duration)
|
||||
assert.Equal(t, ClientConsent{Mode: ClientConsentModeExplicit}, fclient.GetConsentPolicy())
|
||||
|
||||
fclient.TokenEndpointAuthMethod = ""
|
||||
fclient.Public = false
|
||||
assert.Equal(t, ClientAuthMethodClientSecretBasic, fclient.GetTokenEndpointAuthMethod())
|
||||
assert.Equal(t, ClientAuthMethodClientSecretBasic, fclient.TokenEndpointAuthMethod)
|
||||
|
||||
fclient.TokenEndpointAuthMethod = ""
|
||||
fclient.Public = true
|
||||
assert.Equal(t, ClientAuthMethodNone, fclient.GetTokenEndpointAuthMethod())
|
||||
assert.Equal(t, ClientAuthMethodNone, fclient.TokenEndpointAuthMethod)
|
||||
|
||||
assert.Equal(t, []string(nil), fclient.RequestURIs)
|
||||
assert.Equal(t, []string(nil), fclient.GetRequestURIs())
|
||||
}
|
||||
|
|
|
@ -1,24 +1,29 @@
|
|||
package oidc
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"crypto/ed25519"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/authelia/authelia/v4/internal/configuration/schema"
|
||||
"github.com/authelia/authelia/v4/internal/utils"
|
||||
)
|
||||
|
||||
const (
|
||||
myclient = "myclient"
|
||||
myclientdesc = "My Client"
|
||||
onefactor = "one_factor"
|
||||
twofactor = "two_factor"
|
||||
examplecom = "https://example.com"
|
||||
examplecomsid = "example.com"
|
||||
badsecret = "$plaintext$a_bad_secret"
|
||||
badhmac = "asbdhaaskmdlkamdklasmdlkams"
|
||||
exampleIssuerPrivateKey = "-----BEGIN RSA PRIVATE KEY-----\nMIIEpAIBAAKCAQEAvcMVMB2vEbqI6PlSNJ4HmUyMxBDJ5iY7FS+zDDAHOZBg9S3S\nKcAn1CZcnyL0VvJ7wcdhR6oTnOwR94eKvzUyJZ+GL2hTMm27dubEYsNdhoCl6N3X\nyEEohNfoxiiCYraVauX8X3M9jFzbEz9+pacaDbHB2syaJ1qFmMNR+HSu2jPzOo7M\nlqKIOgUzA0741MaYNt47AEVg4XU5ORLdolbAkItmYg1QbyFndg9H5IvwKkYaXTGE\nlgDBcPUC0yVjAC15Mguquq+jZeQay+6PSbHTD8PQMOkLjyChI2xEhVNbdCXe676R\ncMW2R/gjrcK23zmtmTWRfdC1iZLSlHO+bJj9vQIDAQABAoIBAEZvkP/JJOCJwqPn\nV3IcbmmilmV4bdi1vByDFgyiDyx4wOSA24+PubjvfFW9XcCgRPuKjDtTj/AhWBHv\nB7stfa2lZuNV7/u562mZArA+IAr62Zp0LdIxDV8x3T8gbjVB3HhPYbv0RJZDKTYd\nzV6jhfIrVu9mHpoY6ZnodhapCPYIyk/d49KBIHZuAc25CUjMXgTeaVtf0c996036\nUxW6ef33wAOJAvW0RCvbXAJfmBeEq2qQlkjTIlpYx71fhZWexHifi8Ouv3Zonc+1\n/P2Adq5uzYVBT92f9RKHg9QxxNzVrLjSMaxyvUtWQCAQfW0tFIRdqBGsHYsQrFtI\nF4yzv8ECgYEA7ntpyN9HD9Z9lYQzPCR73sFCLM+ID99aVij0wHuxK97bkSyyvkLd\n7MyTaym3lg1UEqWNWBCLvFULZx7F0Ah6qCzD4ymm3Bj/ADpWWPgljBI0AFml+HHs\nhcATmXUrj5QbLyhiP2gmJjajp1o/rgATx6ED66seSynD6JOH8wUhhZUCgYEAy7OA\n06PF8GfseNsTqlDjNF0K7lOqd21S0prdwrsJLiVzUlfMM25MLE0XLDUutCnRheeh\nIlcuDoBsVTxz6rkvFGD74N+pgXlN4CicsBq5ofK060PbqCQhSII3fmHobrZ9Cr75\nHmBjAxHx998SKaAAGbBbcYGUAp521i1pH5CEPYkCgYEAkUd1Zf0+2RMdZhwm6hh/\nrW+l1I6IoMK70YkZsLipccRNld7Y9LbfYwYtODcts6di9AkOVfueZJiaXbONZfIE\nZrb+jkAteh9wGL9xIrnohbABJcV3Kiaco84jInUSmGDtPokncOENfHIEuEpuSJ2b\nbx1TuhmAVuGWivR0+ULC7RECgYEAgS0cDRpWc9Xzh9Cl7+PLsXEvdWNpPsL9OsEq\n0Ep7z9+/+f/jZtoTRCS/BTHUpDvAuwHglT5j3p5iFMt5VuiIiovWLwynGYwrbnNS\nqfrIrYKUaH1n1oDS+oBZYLQGCe9/7EifAjxtjYzbvSyg//SPG7tSwfBCREbpZXj2\nqSWkNsECgYA/mCDzCTlrrWPuiepo6kTmN+4TnFA+hJI6NccDVQ+jvbqEdoJ4SW4L\nzqfZSZRFJMNpSgIqkQNRPJqMP0jQ5KRtJrjMWBnYxktwKz9fDg2R2MxdFgMF2LH2\nHEMMhFHlv8NDjVOXh1KwRoltNGVWYsSrD9wKU9GhRCEfmNCGrvBcEg==\n-----END RSA PRIVATE KEY-----"
|
||||
pathCrypto = "../configuration/test_resources/crypto/%s.%s"
|
||||
myclient = "myclient"
|
||||
myclientdesc = "My Client"
|
||||
onefactor = "one_factor"
|
||||
twofactor = "two_factor"
|
||||
examplecom = "https://example.com"
|
||||
examplecomsid = "example.com"
|
||||
badsecret = "$plaintext$a_bad_secret"
|
||||
badhmac = "asbdhaaskmdlkamdklasmdlkams"
|
||||
)
|
||||
|
||||
func MustDecodeSecret(value string) *schema.PasswordDigest {
|
||||
|
@ -37,20 +42,126 @@ func MustParseRequestURI(input string) *url.URL {
|
|||
}
|
||||
}
|
||||
|
||||
func MustParseRSAPrivateKey(data string) *rsa.PrivateKey {
|
||||
block, _ := pem.Decode([]byte(data))
|
||||
if block == nil || block.Bytes == nil || len(block.Bytes) == 0 {
|
||||
panic("not pem encoded")
|
||||
func MustLoadCrypto(alg, mod, ext string, extra ...string) any {
|
||||
fparts := []string{alg, mod}
|
||||
if len(extra) != 0 {
|
||||
fparts = append(fparts, extra...)
|
||||
}
|
||||
|
||||
if block.Type != "RSA PRIVATE KEY" {
|
||||
panic("not private key")
|
||||
}
|
||||
var (
|
||||
data []byte
|
||||
decoded any
|
||||
err error
|
||||
)
|
||||
|
||||
key, err := x509.ParsePKCS1PrivateKey(block.Bytes)
|
||||
if err != nil {
|
||||
if data, err = os.ReadFile(fmt.Sprintf(pathCrypto, strings.Join(fparts, "_"), ext)); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if decoded, err = utils.ParseX509FromPEMRecursive(data); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return decoded
|
||||
}
|
||||
|
||||
func MustLoadCertificateChain(alg, op string) schema.X509CertificateChain {
|
||||
decoded := MustLoadCrypto(alg, op, "crt")
|
||||
|
||||
switch cert := decoded.(type) {
|
||||
case *x509.Certificate:
|
||||
return schema.NewX509CertificateChainFromCerts([]*x509.Certificate{cert})
|
||||
case []*x509.Certificate:
|
||||
return schema.NewX509CertificateChainFromCerts(cert)
|
||||
default:
|
||||
panic(fmt.Errorf("the key was not a *x509.Certificate or []*x509.Certificate, it's a %T", cert))
|
||||
}
|
||||
}
|
||||
|
||||
func MustLoadCertificate(alg, op string) *x509.Certificate {
|
||||
decoded := MustLoadCrypto(alg, op, "crt")
|
||||
|
||||
cert, ok := decoded.(*x509.Certificate)
|
||||
if !ok {
|
||||
panic(fmt.Errorf("the key was not a *x509.Certificate, it's a %T", cert))
|
||||
}
|
||||
|
||||
return cert
|
||||
}
|
||||
|
||||
func MustLoadEd15519PrivateKey(curve string, extra ...string) ed25519.PrivateKey {
|
||||
decoded := MustLoadCrypto("ED25519", curve, "pem", extra...)
|
||||
|
||||
key, ok := decoded.(ed25519.PrivateKey)
|
||||
if !ok {
|
||||
panic(fmt.Errorf("the key was not a ed25519.PrivateKey, it's a %T", key))
|
||||
}
|
||||
|
||||
return key
|
||||
}
|
||||
|
||||
func MustLoadECDSAPrivateKey(curve string, extra ...string) *ecdsa.PrivateKey {
|
||||
decoded := MustLoadCrypto("ECDSA", curve, "pem", extra...)
|
||||
|
||||
key, ok := decoded.(*ecdsa.PrivateKey)
|
||||
if !ok {
|
||||
panic(fmt.Errorf("the key was not a *ecdsa.PrivateKey, it's a %T", key))
|
||||
}
|
||||
|
||||
return key
|
||||
}
|
||||
|
||||
func MustLoadRSAPublicKey(bits string, extra ...string) *rsa.PublicKey {
|
||||
decoded := MustLoadCrypto("RSA", bits, "pem", extra...)
|
||||
|
||||
key, ok := decoded.(*rsa.PublicKey)
|
||||
if !ok {
|
||||
panic(fmt.Errorf("the key was not a *rsa.PublicKey, it's a %T", key))
|
||||
}
|
||||
|
||||
return key
|
||||
}
|
||||
|
||||
func MustLoadRSAPrivateKey(bits string, extra ...string) *rsa.PrivateKey {
|
||||
decoded := MustLoadCrypto("RSA", bits, "pem", extra...)
|
||||
|
||||
key, ok := decoded.(*rsa.PrivateKey)
|
||||
if !ok {
|
||||
panic(fmt.Errorf("the key was not a *rsa.PrivateKey, it's a %T", key))
|
||||
}
|
||||
|
||||
return key
|
||||
}
|
||||
|
||||
var (
|
||||
tOpenIDConnectPBKDF2ClientSecret, tOpenIDConnectPlainTextClientSecret *schema.PasswordDigest
|
||||
|
||||
// Standard RSA key / certificate pairs.
|
||||
keyRSA1024, keyRSA2048, keyRSA4096 *rsa.PrivateKey
|
||||
certRSA1024, certRSA2048, certRSA4096 schema.X509CertificateChain
|
||||
|
||||
// Standard ECDSA key / certificate pairs.
|
||||
keyECDSAP224, keyECDSAP256, keyECDSAP384, keyECDSAP521 *ecdsa.PrivateKey
|
||||
certECDSAP224, certECDSAP256, certECDSAP384, certECDSAP521 schema.X509CertificateChain
|
||||
)
|
||||
|
||||
func init() {
|
||||
tOpenIDConnectPBKDF2ClientSecret = MustDecodeSecret("$pbkdf2-sha512$310000$c8p78n7pUMln0jzvd4aK4Q$JNRBzwAo0ek5qKn50cFzzvE9RXV88h1wJn5KGiHrD0YKtZaR/nCb2CJPOsKaPK0hjf.9yHxzQGZziziccp6Yng")
|
||||
tOpenIDConnectPlainTextClientSecret = MustDecodeSecret("$plaintext$example")
|
||||
|
||||
keyRSA1024 = MustLoadRSAPrivateKey("1024")
|
||||
keyRSA2048 = MustLoadRSAPrivateKey("2048")
|
||||
keyRSA4096 = MustLoadRSAPrivateKey("4096")
|
||||
keyECDSAP224 = MustLoadECDSAPrivateKey("P224")
|
||||
keyECDSAP256 = MustLoadECDSAPrivateKey("P256")
|
||||
keyECDSAP384 = MustLoadECDSAPrivateKey("P384")
|
||||
keyECDSAP521 = MustLoadECDSAPrivateKey("P521")
|
||||
|
||||
certRSA1024 = MustLoadCertificateChain("RSA", "1024")
|
||||
certRSA2048 = MustLoadCertificateChain("RSA", "2048")
|
||||
certRSA4096 = MustLoadCertificateChain("RSA", "4096")
|
||||
certECDSAP224 = MustLoadCertificateChain("ECDSA", "P224")
|
||||
certECDSAP256 = MustLoadCertificateChain("ECDSA", "P256")
|
||||
certECDSAP384 = MustLoadCertificateChain("ECDSA", "P384")
|
||||
certECDSAP521 = MustLoadCertificateChain("ECDSA", "P521")
|
||||
}
|
||||
|
|
|
@ -1,13 +1,93 @@
|
|||
package oidc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/ory/fosite"
|
||||
"github.com/ory/fosite/token/hmac"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestHMACStrategy(t *testing.T) {
|
||||
goodsecret := []byte("R7VCSUfnKc7Y5zE84q6GstYqfMGjL4wM")
|
||||
secreta := []byte("a")
|
||||
|
||||
config := &Config{
|
||||
TokenEntropy: 10,
|
||||
GlobalSecret: secreta,
|
||||
Lifespans: LifespanConfig{
|
||||
AccessToken: time.Hour,
|
||||
RefreshToken: time.Hour,
|
||||
AuthorizeCode: time.Minute,
|
||||
},
|
||||
}
|
||||
|
||||
strategy := &HMACCoreStrategy{
|
||||
Enigma: &hmac.HMACStrategy{Config: config},
|
||||
Config: config,
|
||||
}
|
||||
|
||||
var (
|
||||
token, signature string
|
||||
err error
|
||||
)
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
token, signature, err = strategy.GenerateAuthorizeCode(ctx, &fosite.Request{})
|
||||
assert.EqualError(t, err, "secret for signing HMAC-SHA512/256 is expected to be 32 byte long, got 1 byte")
|
||||
assert.Empty(t, token)
|
||||
assert.Empty(t, signature)
|
||||
|
||||
config.GlobalSecret = goodsecret
|
||||
|
||||
token, signature, err = strategy.GenerateAuthorizeCode(ctx, &fosite.Request{})
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.NotEmpty(t, token)
|
||||
assert.NotEmpty(t, signature)
|
||||
assert.Equal(t, signature, strategy.AuthorizeCodeSignature(ctx, token))
|
||||
assert.Regexp(t, regexp.MustCompile(`^authelia_ac_`), token)
|
||||
|
||||
assert.NoError(t, strategy.ValidateAuthorizeCode(ctx, &fosite.Request{RequestedAt: time.Now(), Session: &fosite.DefaultSession{}}, token))
|
||||
assert.NoError(t, strategy.ValidateAuthorizeCode(ctx, &fosite.Request{RequestedAt: time.Now(), Session: &fosite.DefaultSession{}}, token))
|
||||
assert.EqualError(t, strategy.ValidateAuthorizeCode(ctx, &fosite.Request{RequestedAt: time.Now().Add(time.Hour * -2400), Session: &fosite.DefaultSession{}}, token), "invalid_token")
|
||||
assert.NoError(t, strategy.ValidateAuthorizeCode(ctx, &fosite.Request{RequestedAt: time.Now().Add(time.Hour * -2400), Session: &fosite.DefaultSession{ExpiresAt: map[fosite.TokenType]time.Time{fosite.AuthorizeCode: time.Now().Add(100 * time.Hour)}}}, token))
|
||||
assert.EqualError(t, strategy.ValidateAuthorizeCode(ctx, &fosite.Request{RequestedAt: time.Now(), Session: &fosite.DefaultSession{ExpiresAt: map[fosite.TokenType]time.Time{fosite.AuthorizeCode: time.Now().Add(-100 * time.Second)}}}, token), "invalid_token")
|
||||
|
||||
token, signature, err = strategy.GenerateRefreshToken(ctx, &fosite.Request{})
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.NotEmpty(t, token)
|
||||
assert.NotEmpty(t, signature)
|
||||
assert.Equal(t, signature, strategy.RefreshTokenSignature(ctx, token))
|
||||
assert.Regexp(t, regexp.MustCompile(`^authelia_rt_`), token)
|
||||
|
||||
assert.NoError(t, strategy.ValidateRefreshToken(ctx, &fosite.Request{RequestedAt: time.Now(), Session: &fosite.DefaultSession{}}, token))
|
||||
assert.NoError(t, strategy.ValidateRefreshToken(ctx, &fosite.Request{RequestedAt: time.Now(), Session: &fosite.DefaultSession{}}, token))
|
||||
assert.NoError(t, strategy.ValidateRefreshToken(ctx, &fosite.Request{RequestedAt: time.Now().Add(time.Hour * -2400), Session: &fosite.DefaultSession{ExpiresAt: map[fosite.TokenType]time.Time{fosite.RefreshToken: time.Now().Add(100 * time.Hour)}}}, token))
|
||||
assert.EqualError(t, strategy.ValidateRefreshToken(ctx, &fosite.Request{RequestedAt: time.Now(), Session: &fosite.DefaultSession{ExpiresAt: map[fosite.TokenType]time.Time{fosite.RefreshToken: time.Now().Add(-100 * time.Second)}}}, token), "invalid_token")
|
||||
|
||||
token, signature, err = strategy.GenerateAccessToken(ctx, &fosite.Request{})
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.NotEmpty(t, token)
|
||||
assert.NotEmpty(t, signature)
|
||||
assert.Equal(t, signature, strategy.AccessTokenSignature(ctx, token))
|
||||
assert.Regexp(t, regexp.MustCompile(`^authelia_at_`), token)
|
||||
|
||||
assert.NoError(t, strategy.ValidateAccessToken(ctx, &fosite.Request{RequestedAt: time.Now(), Session: &fosite.DefaultSession{}}, token))
|
||||
assert.NoError(t, strategy.ValidateAccessToken(ctx, &fosite.Request{RequestedAt: time.Now(), Session: &fosite.DefaultSession{}}, token))
|
||||
assert.EqualError(t, strategy.ValidateAccessToken(ctx, &fosite.Request{RequestedAt: time.Now().Add(time.Hour * -2400), Session: &fosite.DefaultSession{}}, token), "invalid_token")
|
||||
assert.NoError(t, strategy.ValidateAccessToken(ctx, &fosite.Request{RequestedAt: time.Now().Add(time.Hour * -2400), Session: &fosite.DefaultSession{ExpiresAt: map[fosite.TokenType]time.Time{fosite.AccessToken: time.Now().Add(100 * time.Hour)}}}, token))
|
||||
assert.EqualError(t, strategy.ValidateAccessToken(ctx, &fosite.Request{RequestedAt: time.Now(), Session: &fosite.DefaultSession{ExpiresAt: map[fosite.TokenType]time.Time{fosite.AccessToken: time.Now().Add(-100 * time.Second)}}}, token), "invalid_token")
|
||||
}
|
||||
|
||||
func TestHMACCoreStrategy_TrimPrefix(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
package oidc
|
||||
|
||||
import (
|
||||
"sort"
|
||||
|
||||
"github.com/authelia/authelia/v4/internal/configuration/schema"
|
||||
"github.com/authelia/authelia/v4/internal/utils"
|
||||
)
|
||||
|
||||
// NewOpenIDConnectWellKnownConfiguration generates a new OpenIDConnectWellKnownConfiguration.
|
||||
|
@ -75,6 +78,21 @@ func NewOpenIDConnectWellKnownConfiguration(c *schema.OpenIDConnectConfiguration
|
|||
CodeChallengeMethodsSupported: []string{
|
||||
PKCEChallengeMethodSHA256,
|
||||
},
|
||||
RevocationEndpointAuthMethodsSupported: []string{
|
||||
ClientAuthMethodClientSecretBasic,
|
||||
ClientAuthMethodClientSecretPost,
|
||||
ClientAuthMethodClientSecretJWT,
|
||||
ClientAuthMethodNone,
|
||||
},
|
||||
RevocationEndpointAuthSigningAlgValuesSupported: []string{
|
||||
SigningAlgHMACUsingSHA256,
|
||||
SigningAlgHMACUsingSHA384,
|
||||
SigningAlgHMACUsingSHA512,
|
||||
},
|
||||
IntrospectionEndpointAuthMethodsSupported: []string{
|
||||
ClientAuthMethodClientSecretBasic,
|
||||
ClientAuthMethodNone,
|
||||
},
|
||||
},
|
||||
OAuth2PushedAuthorizationDiscoveryOptions: &OAuth2PushedAuthorizationDiscoveryOptions{
|
||||
RequirePushedAuthorizationRequests: c.PAR.Enforce,
|
||||
|
@ -89,6 +107,10 @@ func NewOpenIDConnectWellKnownConfiguration(c *schema.OpenIDConnectConfiguration
|
|||
SigningAlgNone,
|
||||
SigningAlgRSAUsingSHA256,
|
||||
},
|
||||
RequestObjectSigningAlgValuesSupported: []string{
|
||||
SigningAlgNone,
|
||||
SigningAlgRSAUsingSHA256,
|
||||
},
|
||||
},
|
||||
OpenIDConnectFrontChannelLogoutDiscoveryOptions: &OpenIDConnectFrontChannelLogoutDiscoveryOptions{},
|
||||
OpenIDConnectBackChannelLogoutDiscoveryOptions: &OpenIDConnectBackChannelLogoutDiscoveryOptions{},
|
||||
|
@ -100,6 +122,26 @@ func NewOpenIDConnectWellKnownConfiguration(c *schema.OpenIDConnectConfiguration
|
|||
},
|
||||
}
|
||||
|
||||
algs := make([]string, len(c.Discovery.RegisteredJWKSigningAlgs))
|
||||
|
||||
copy(algs, c.Discovery.RegisteredJWKSigningAlgs)
|
||||
|
||||
for _, alg := range algs {
|
||||
if !utils.IsStringInSlice(alg, config.IDTokenSigningAlgValuesSupported) {
|
||||
config.IDTokenSigningAlgValuesSupported = append(config.IDTokenSigningAlgValuesSupported, alg)
|
||||
}
|
||||
|
||||
if !utils.IsStringInSlice(alg, config.UserinfoSigningAlgValuesSupported) {
|
||||
config.UserinfoSigningAlgValuesSupported = append(config.UserinfoSigningAlgValuesSupported, alg)
|
||||
}
|
||||
}
|
||||
|
||||
sort.Sort(SortedSigningAlgs(config.IDTokenSigningAlgValuesSupported))
|
||||
sort.Sort(SortedSigningAlgs(config.UserinfoSigningAlgValuesSupported))
|
||||
sort.Sort(SortedSigningAlgs(config.RequestObjectSigningAlgValuesSupported))
|
||||
sort.Sort(SortedSigningAlgs(config.RevocationEndpointAuthSigningAlgValuesSupported))
|
||||
sort.Sort(SortedSigningAlgs(config.TokenEndpointAuthSigningAlgValuesSupported))
|
||||
|
||||
if c.EnablePKCEPlainChallenge {
|
||||
config.CodeChallengeMethodsSupported = append(config.CodeChallengeMethodsSupported, PKCEChallengeMethodPlain)
|
||||
}
|
||||
|
|
|
@ -14,8 +14,9 @@ func TestNewOpenIDConnectWellKnownConfiguration(t *testing.T) {
|
|||
pkcePlainChallenge bool
|
||||
enforcePAR bool
|
||||
clients map[string]Client
|
||||
discovery schema.OpenIDConnectDiscovery
|
||||
|
||||
expectCodeChallengeMethodsSupported, expectSubjectTypesSupported []string
|
||||
expectCodeChallengeMethodsSupported, expectSubjectTypesSupported, expectedIDTokenSigAlgsSupported, expectedUserInfoSigAlgsSupported []string
|
||||
}{
|
||||
{
|
||||
desc: "ShouldHaveChallengeMethodsS256ANDSubjectTypesSupportedPublic",
|
||||
|
@ -23,6 +24,20 @@ func TestNewOpenIDConnectWellKnownConfiguration(t *testing.T) {
|
|||
clients: map[string]Client{"a": &BaseClient{}},
|
||||
expectCodeChallengeMethodsSupported: []string{PKCEChallengeMethodSHA256},
|
||||
expectSubjectTypesSupported: []string{SubjectTypePublic, SubjectTypePairwise},
|
||||
expectedIDTokenSigAlgsSupported: []string{SigningAlgRSAUsingSHA256},
|
||||
expectedUserInfoSigAlgsSupported: []string{SigningAlgRSAUsingSHA256, SigningAlgNone},
|
||||
},
|
||||
{
|
||||
desc: "ShouldIncludDiscoveryInfo",
|
||||
pkcePlainChallenge: false,
|
||||
clients: map[string]Client{"a": &BaseClient{}},
|
||||
discovery: schema.OpenIDConnectDiscovery{
|
||||
RegisteredJWKSigningAlgs: []string{SigningAlgECDSAUsingP521AndSHA512},
|
||||
},
|
||||
expectCodeChallengeMethodsSupported: []string{PKCEChallengeMethodSHA256},
|
||||
expectSubjectTypesSupported: []string{SubjectTypePublic, SubjectTypePairwise},
|
||||
expectedIDTokenSigAlgsSupported: []string{SigningAlgRSAUsingSHA256, SigningAlgECDSAUsingP521AndSHA512},
|
||||
expectedUserInfoSigAlgsSupported: []string{SigningAlgRSAUsingSHA256, SigningAlgECDSAUsingP521AndSHA512, SigningAlgNone},
|
||||
},
|
||||
{
|
||||
desc: "ShouldHaveChallengeMethodsS256PlainANDSubjectTypesSupportedPublic",
|
||||
|
@ -30,6 +45,8 @@ func TestNewOpenIDConnectWellKnownConfiguration(t *testing.T) {
|
|||
clients: map[string]Client{"a": &BaseClient{}},
|
||||
expectCodeChallengeMethodsSupported: []string{PKCEChallengeMethodSHA256, PKCEChallengeMethodPlain},
|
||||
expectSubjectTypesSupported: []string{SubjectTypePublic, SubjectTypePairwise},
|
||||
expectedIDTokenSigAlgsSupported: []string{SigningAlgRSAUsingSHA256},
|
||||
expectedUserInfoSigAlgsSupported: []string{SigningAlgRSAUsingSHA256, SigningAlgNone},
|
||||
},
|
||||
{
|
||||
desc: "ShouldHaveChallengeMethodsS256ANDSubjectTypesSupportedPublicPairwise",
|
||||
|
@ -37,6 +54,8 @@ func TestNewOpenIDConnectWellKnownConfiguration(t *testing.T) {
|
|||
clients: map[string]Client{"a": &BaseClient{SectorIdentifier: "yes"}},
|
||||
expectCodeChallengeMethodsSupported: []string{PKCEChallengeMethodSHA256},
|
||||
expectSubjectTypesSupported: []string{SubjectTypePublic, SubjectTypePairwise},
|
||||
expectedIDTokenSigAlgsSupported: []string{SigningAlgRSAUsingSHA256},
|
||||
expectedUserInfoSigAlgsSupported: []string{SigningAlgRSAUsingSHA256, SigningAlgNone},
|
||||
},
|
||||
{
|
||||
desc: "ShouldHaveChallengeMethodsS256PlainANDSubjectTypesSupportedPublicPairwise",
|
||||
|
@ -44,6 +63,8 @@ func TestNewOpenIDConnectWellKnownConfiguration(t *testing.T) {
|
|||
clients: map[string]Client{"a": &BaseClient{SectorIdentifier: "yes"}},
|
||||
expectCodeChallengeMethodsSupported: []string{PKCEChallengeMethodSHA256, PKCEChallengeMethodPlain},
|
||||
expectSubjectTypesSupported: []string{SubjectTypePublic, SubjectTypePairwise},
|
||||
expectedIDTokenSigAlgsSupported: []string{SigningAlgRSAUsingSHA256},
|
||||
expectedUserInfoSigAlgsSupported: []string{SigningAlgRSAUsingSHA256, SigningAlgNone},
|
||||
},
|
||||
{
|
||||
desc: "ShouldHaveTokenAuthMethodsNone",
|
||||
|
@ -51,6 +72,8 @@ func TestNewOpenIDConnectWellKnownConfiguration(t *testing.T) {
|
|||
clients: map[string]Client{"a": &BaseClient{SectorIdentifier: "yes"}},
|
||||
expectCodeChallengeMethodsSupported: []string{PKCEChallengeMethodSHA256, PKCEChallengeMethodPlain},
|
||||
expectSubjectTypesSupported: []string{SubjectTypePublic, SubjectTypePairwise},
|
||||
expectedIDTokenSigAlgsSupported: []string{SigningAlgRSAUsingSHA256},
|
||||
expectedUserInfoSigAlgsSupported: []string{SigningAlgRSAUsingSHA256, SigningAlgNone},
|
||||
},
|
||||
{
|
||||
desc: "ShouldHaveTokenAuthMethodsNone",
|
||||
|
@ -61,6 +84,20 @@ func TestNewOpenIDConnectWellKnownConfiguration(t *testing.T) {
|
|||
},
|
||||
expectCodeChallengeMethodsSupported: []string{PKCEChallengeMethodSHA256, PKCEChallengeMethodPlain},
|
||||
expectSubjectTypesSupported: []string{SubjectTypePublic, SubjectTypePairwise},
|
||||
expectedIDTokenSigAlgsSupported: []string{SigningAlgRSAUsingSHA256},
|
||||
expectedUserInfoSigAlgsSupported: []string{SigningAlgRSAUsingSHA256, SigningAlgNone},
|
||||
},
|
||||
{
|
||||
desc: "ShouldHaveTokenAuthMethodsNone",
|
||||
pkcePlainChallenge: true,
|
||||
clients: map[string]Client{
|
||||
"a": &BaseClient{SectorIdentifier: "yes"},
|
||||
"b": &BaseClient{SectorIdentifier: "yes"},
|
||||
},
|
||||
expectCodeChallengeMethodsSupported: []string{PKCEChallengeMethodSHA256, PKCEChallengeMethodPlain},
|
||||
expectSubjectTypesSupported: []string{SubjectTypePublic, SubjectTypePairwise},
|
||||
expectedIDTokenSigAlgsSupported: []string{SigningAlgRSAUsingSHA256},
|
||||
expectedUserInfoSigAlgsSupported: []string{SigningAlgRSAUsingSHA256, SigningAlgNone},
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -71,6 +108,7 @@ func TestNewOpenIDConnectWellKnownConfiguration(t *testing.T) {
|
|||
PAR: schema.OpenIDConnectPARConfiguration{
|
||||
Enforce: tc.enforcePAR,
|
||||
},
|
||||
Discovery: tc.discovery,
|
||||
}
|
||||
|
||||
actual := NewOpenIDConnectWellKnownConfiguration(&c)
|
||||
|
@ -89,6 +127,9 @@ func TestNewOpenIDConnectWellKnownConfiguration(t *testing.T) {
|
|||
for _, subjectType := range actual.SubjectTypesSupported {
|
||||
assert.Contains(t, tc.expectSubjectTypesSupported, subjectType)
|
||||
}
|
||||
|
||||
assert.Equal(t, tc.expectedUserInfoSigAlgsSupported, actual.UserinfoSigningAlgValuesSupported)
|
||||
assert.Equal(t, tc.expectedIDTokenSigAlgsSupported, actual.IDTokenSigningAlgValuesSupported)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,181 +3,398 @@ package oidc
|
|||
import (
|
||||
"context"
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/rsa"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/ory/fosite/token/jwt"
|
||||
"github.com/golang-jwt/jwt/v4"
|
||||
fjwt "github.com/ory/fosite/token/jwt"
|
||||
"github.com/ory/x/errorsx"
|
||||
|
||||
"gopkg.in/square/go-jose.v2"
|
||||
|
||||
"github.com/authelia/authelia/v4/internal/configuration/schema"
|
||||
)
|
||||
|
||||
// NewKeyManagerWithConfiguration when provided a schema.OpenIDConnectConfiguration creates a new KeyManager and adds an
|
||||
// initial key to the manager.
|
||||
func NewKeyManagerWithConfiguration(config *schema.OpenIDConnectConfiguration) (manager *KeyManager, err error) {
|
||||
manager = NewKeyManager()
|
||||
// NewKeyManager news up a KeyManager.
|
||||
func NewKeyManager(config *schema.OpenIDConnectConfiguration) (manager *KeyManager) {
|
||||
manager = &KeyManager{
|
||||
kids: map[string]*JWK{},
|
||||
algs: map[string]*JWK{},
|
||||
}
|
||||
|
||||
if _, err = manager.AddActiveJWK(config.IssuerCertificateChain, config.IssuerPrivateKey); err != nil {
|
||||
for _, sjwk := range config.IssuerJWKS {
|
||||
jwk := NewJWK(sjwk)
|
||||
|
||||
manager.kids[sjwk.KeyID] = jwk
|
||||
manager.algs[jwk.alg.Alg()] = jwk
|
||||
|
||||
if jwk.kid == config.Discovery.DefaultKeyID {
|
||||
manager.kid = jwk.kid
|
||||
}
|
||||
}
|
||||
|
||||
return manager
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
func (m *KeyManager) GetByKID(ctx context.Context, kid string) *JWK {
|
||||
if kid == "" {
|
||||
return m.kids[m.kid]
|
||||
}
|
||||
|
||||
if jwk, ok := m.kids[kid]; ok {
|
||||
return jwk
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *KeyManager) GetByHeader(ctx context.Context, header fjwt.Mapper) (jwk *JWK, err error) {
|
||||
var (
|
||||
kid string
|
||||
ok bool
|
||||
)
|
||||
|
||||
if header == nil {
|
||||
return nil, fmt.Errorf("jwt header was nil")
|
||||
}
|
||||
|
||||
if kid, ok = header.Get(JWTHeaderKeyIdentifier).(string); !ok {
|
||||
return nil, fmt.Errorf("jwt header did not have a kid")
|
||||
}
|
||||
|
||||
if jwk, ok = m.kids[kid]; !ok {
|
||||
return nil, fmt.Errorf("jwt header '%s' with value '%s' does not match a managed jwk", JWTHeaderKeyIdentifier, kid)
|
||||
}
|
||||
|
||||
return jwk, nil
|
||||
}
|
||||
|
||||
func (m *KeyManager) GetByTokenString(ctx context.Context, tokenString string) (jwk *JWK, err error) {
|
||||
var (
|
||||
token *jwt.Token
|
||||
)
|
||||
|
||||
if token, _, err = jwt.NewParser().ParseUnverified(tokenString, jwt.MapClaims{}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return manager, nil
|
||||
return m.GetByHeader(ctx, &fjwt.Headers{Extra: token.Header})
|
||||
}
|
||||
|
||||
// NewKeyManager creates a new empty KeyManager.
|
||||
func NewKeyManager() (manager *KeyManager) {
|
||||
return &KeyManager{
|
||||
jwks: &jose.JSONWebKeySet{},
|
||||
func (m *KeyManager) Set(ctx context.Context) *jose.JSONWebKeySet {
|
||||
keys := make([]jose.JSONWebKey, 0, len(m.kids))
|
||||
|
||||
for _, jwk := range m.kids {
|
||||
keys = append(keys, jwk.JWK())
|
||||
}
|
||||
|
||||
sort.Sort(SortedJSONWebKey(keys))
|
||||
|
||||
return &jose.JSONWebKeySet{
|
||||
Keys: keys,
|
||||
}
|
||||
}
|
||||
|
||||
// Strategy returns the fosite jwt.JWTStrategy.
|
||||
func (m *KeyManager) Strategy() (strategy jwt.Signer) {
|
||||
if m.jwk == nil {
|
||||
return nil
|
||||
func (m *KeyManager) Generate(ctx context.Context, claims fjwt.MapClaims, header fjwt.Mapper) (tokenString string, sig string, err error) {
|
||||
var jwk *JWK
|
||||
|
||||
if jwk, err = m.GetByHeader(ctx, header); err != nil {
|
||||
return "", "", fmt.Errorf("error getting jwk from header: %w", err)
|
||||
}
|
||||
|
||||
return m.jwk.Strategy()
|
||||
return jwk.Strategy().Generate(ctx, claims, header)
|
||||
}
|
||||
|
||||
// GetKeySet returns the joseJSONWebKeySet containing the rsa.PublicKey types.
|
||||
func (m *KeyManager) GetKeySet() (jwks *jose.JSONWebKeySet) {
|
||||
return m.jwks
|
||||
func (m *KeyManager) Validate(ctx context.Context, tokenString string) (sig string, err error) {
|
||||
var jwk *JWK
|
||||
|
||||
if jwk, err = m.GetByTokenString(ctx, tokenString); err != nil {
|
||||
return "", fmt.Errorf("error getting jwk from token string: %w", err)
|
||||
}
|
||||
|
||||
return jwk.Strategy().Validate(ctx, tokenString)
|
||||
}
|
||||
|
||||
// GetActiveJWK obtains the currently active jose.JSONWebKey.
|
||||
func (m *KeyManager) GetActiveJWK() (jwk *jose.JSONWebKey, err error) {
|
||||
if m.jwks == nil || m.jwk == nil {
|
||||
return nil, errors.New("could not obtain the active JWK from an improperly configured key manager")
|
||||
}
|
||||
|
||||
jwks := m.jwks.Key(m.jwk.id)
|
||||
|
||||
if len(jwks) == 1 {
|
||||
return &jwks[0], nil
|
||||
}
|
||||
|
||||
if len(jwks) == 0 {
|
||||
return nil, errors.New("could not find a key with the active key id")
|
||||
}
|
||||
|
||||
return nil, errors.New("multiple keys with the same key id")
|
||||
func (m *KeyManager) Hash(ctx context.Context, in []byte) (sum []byte, err error) {
|
||||
return m.GetByKID(ctx, "").Strategy().Hash(ctx, in)
|
||||
}
|
||||
|
||||
// GetActiveKeyID returns the key id of the currently active key.
|
||||
func (m *KeyManager) GetActiveKeyID() (keyID string) {
|
||||
if m.jwk == nil {
|
||||
return ""
|
||||
func (m *KeyManager) Decode(ctx context.Context, tokenString string) (token *fjwt.Token, err error) {
|
||||
var jwk *JWK
|
||||
|
||||
if jwk, err = m.GetByTokenString(ctx, tokenString); err != nil {
|
||||
return nil, fmt.Errorf("error getting jwk from token string: %w", err)
|
||||
}
|
||||
|
||||
return m.jwk.id
|
||||
return jwk.Strategy().Decode(ctx, tokenString)
|
||||
}
|
||||
|
||||
// GetActivePrivateKey returns the rsa.PrivateKey of the currently active key.
|
||||
func (m *KeyManager) GetActivePrivateKey() (key *rsa.PrivateKey, err error) {
|
||||
if m.jwk == nil {
|
||||
return nil, errors.New("failed to retrieve active private key")
|
||||
func (m *KeyManager) GetSignature(ctx context.Context, tokenString string) (sig string, err error) {
|
||||
return getTokenSignature(tokenString)
|
||||
}
|
||||
|
||||
func (m *KeyManager) GetSigningMethodLength(ctx context.Context) (size int) {
|
||||
return m.GetByKID(ctx, "").Strategy().GetSigningMethodLength(ctx)
|
||||
}
|
||||
|
||||
func NewJWK(s schema.JWK) (jwk *JWK) {
|
||||
jwk = &JWK{
|
||||
kid: s.KeyID,
|
||||
use: s.Use,
|
||||
alg: jwt.GetSigningMethod(s.Algorithm),
|
||||
key: s.Key.(schema.CryptographicPrivateKey),
|
||||
|
||||
chain: s.CertificateChain,
|
||||
thumbprint: s.CertificateChain.Thumbprint(crypto.SHA256),
|
||||
thumbprintsha1: s.CertificateChain.Thumbprint(crypto.SHA1),
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
m.jwks.Keys = append(m.jwks.Keys, *m.jwk.JSONWebKey())
|
||||
|
||||
return m.jwk, nil
|
||||
}
|
||||
|
||||
// JWTStrategy is a decorator struct for the fosite jwt.JWTStrategy.
|
||||
type JWTStrategy struct {
|
||||
jwt.Signer
|
||||
|
||||
id string
|
||||
}
|
||||
|
||||
// KeyID returns the key id.
|
||||
func (s *JWTStrategy) KeyID() (id string) {
|
||||
return s.id
|
||||
}
|
||||
|
||||
// GetPublicKeyID is a decorator func for the underlying fosite RS256JWTStrategy.
|
||||
func (s *JWTStrategy) GetPublicKeyID(_ context.Context) (string, error) {
|
||||
return s.id, nil
|
||||
}
|
||||
|
||||
// NewJWK creates a new JWK.
|
||||
func NewJWK(chain schema.X509CertificateChain, key *rsa.PrivateKey) (j *JWK, err error) {
|
||||
if key == nil {
|
||||
return nil, fmt.Errorf("JWK is not properly initialized: missing key")
|
||||
}
|
||||
|
||||
j = &JWK{
|
||||
key: key,
|
||||
chain: chain,
|
||||
}
|
||||
|
||||
jwk := &jose.JSONWebKey{
|
||||
Algorithm: SigningAlgRSAUsingSHA256,
|
||||
Use: KeyUseSignature,
|
||||
Key: &key.PublicKey,
|
||||
}
|
||||
|
||||
var thumbprint []byte
|
||||
|
||||
if thumbprint, err = jwk.Thumbprint(crypto.SHA1); err != nil {
|
||||
return nil, fmt.Errorf("failed to calculate SHA1 thumbprint for certificate: %w", err)
|
||||
}
|
||||
|
||||
j.id = strings.ToLower(fmt.Sprintf("%x", thumbprint))
|
||||
|
||||
if len(j.id) >= 7 {
|
||||
j.id = j.id[:6]
|
||||
}
|
||||
|
||||
if len(j.id) >= 7 {
|
||||
j.id = j.id[:6]
|
||||
}
|
||||
|
||||
return j, nil
|
||||
}
|
||||
|
||||
// JWK is a utility wrapper for JSON Web Key's.
|
||||
type JWK struct {
|
||||
id string
|
||||
key *rsa.PrivateKey
|
||||
chain schema.X509CertificateChain
|
||||
}
|
||||
|
||||
// Strategy returns the relevant jwt.JWTStrategy for this JWT.
|
||||
func (j *JWK) Strategy() (strategy jwt.Signer) {
|
||||
return &JWTStrategy{id: j.id, Signer: &jwt.DefaultSigner{GetPrivateKey: j.GetPrivateKey}}
|
||||
}
|
||||
|
||||
func (j *JWK) GetPrivateKey(ctx context.Context) (key any, err error) {
|
||||
return j.key, nil
|
||||
}
|
||||
|
||||
// JSONWebKey returns the relevant *jose.JSONWebKey for this JWT.
|
||||
func (j *JWK) JSONWebKey() (jwk *jose.JSONWebKey) {
|
||||
jwk = &jose.JSONWebKey{
|
||||
Key: &j.key.PublicKey,
|
||||
KeyID: j.id,
|
||||
Algorithm: SigningAlgRSAUsingSHA256,
|
||||
Use: KeyUseSignature,
|
||||
Certificates: j.chain.Certificates(),
|
||||
}
|
||||
|
||||
if len(jwk.Certificates) != 0 {
|
||||
jwk.CertificateThumbprintSHA1, jwk.CertificateThumbprintSHA256 = j.chain.Thumbprint(crypto.SHA1), j.chain.Thumbprint(crypto.SHA256)
|
||||
switch jwk.alg {
|
||||
case jwt.SigningMethodRS256, jwt.SigningMethodPS256, jwt.SigningMethodES256:
|
||||
jwk.hash = crypto.SHA256
|
||||
case jwt.SigningMethodRS384, jwt.SigningMethodPS384, jwt.SigningMethodES384:
|
||||
jwk.hash = crypto.SHA384
|
||||
case jwt.SigningMethodRS512, jwt.SigningMethodPS512, jwt.SigningMethodES512:
|
||||
jwk.hash = crypto.SHA512
|
||||
default:
|
||||
jwk.hash = crypto.SHA256
|
||||
}
|
||||
|
||||
return jwk
|
||||
}
|
||||
|
||||
type JWK struct {
|
||||
kid string
|
||||
use string
|
||||
alg jwt.SigningMethod
|
||||
hash crypto.Hash
|
||||
|
||||
key schema.CryptographicPrivateKey
|
||||
chain schema.X509CertificateChain
|
||||
thumbprintsha1 []byte
|
||||
thumbprint []byte
|
||||
}
|
||||
|
||||
func (j *JWK) GetPrivateKey(ctx context.Context) (any, error) {
|
||||
return j.PrivateJWK(), nil
|
||||
}
|
||||
|
||||
func (j *JWK) KeyID() string {
|
||||
return j.kid
|
||||
}
|
||||
|
||||
func (j *JWK) PrivateJWK() (jwk *jose.JSONWebKey) {
|
||||
return &jose.JSONWebKey{
|
||||
Key: j.key,
|
||||
KeyID: j.kid,
|
||||
Algorithm: j.alg.Alg(),
|
||||
Use: j.use,
|
||||
Certificates: j.chain.Certificates(),
|
||||
CertificateThumbprintSHA1: j.thumbprintsha1,
|
||||
CertificateThumbprintSHA256: j.thumbprint,
|
||||
}
|
||||
}
|
||||
|
||||
func (j *JWK) JWK() (jwk jose.JSONWebKey) {
|
||||
return j.PrivateJWK().Public()
|
||||
}
|
||||
|
||||
func (j *JWK) Strategy() (strategy fjwt.Signer) {
|
||||
return &Signer{
|
||||
hash: j.hash,
|
||||
alg: j.alg,
|
||||
GetPrivateKey: j.GetPrivateKey,
|
||||
}
|
||||
}
|
||||
|
||||
// Signer is responsible for generating and validating JWT challenges.
|
||||
type Signer struct {
|
||||
hash crypto.Hash
|
||||
alg jwt.SigningMethod
|
||||
|
||||
GetPrivateKey fjwt.GetPrivateKeyFunc
|
||||
}
|
||||
|
||||
func (j *Signer) GetPublicKey(ctx context.Context) (key crypto.PublicKey, err error) {
|
||||
var k any
|
||||
|
||||
if k, err = j.GetPrivateKey(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch t := k.(type) {
|
||||
case *jose.JSONWebKey:
|
||||
return t.Public().Key, nil
|
||||
case jose.OpaqueSigner:
|
||||
return t.Public().Key, nil
|
||||
case schema.CryptographicPrivateKey:
|
||||
return t.Public(), nil
|
||||
default:
|
||||
return nil, errors.New("invalid private key type")
|
||||
}
|
||||
}
|
||||
|
||||
// Generate generates a new authorize code or returns an error. set secret.
|
||||
func (j *Signer) Generate(ctx context.Context, claims fjwt.MapClaims, header fjwt.Mapper) (tokenString string, sig string, err error) {
|
||||
var key any
|
||||
|
||||
if key, err = j.GetPrivateKey(ctx); err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
switch t := key.(type) {
|
||||
case *jose.JSONWebKey:
|
||||
return generateToken(claims, header, j.alg, t.Key)
|
||||
case jose.JSONWebKey:
|
||||
return generateToken(claims, header, j.alg, t.Key)
|
||||
case *rsa.PrivateKey, *ecdsa.PrivateKey:
|
||||
return generateToken(claims, header, j.alg, t)
|
||||
case jose.OpaqueSigner:
|
||||
switch tt := t.Public().Key.(type) {
|
||||
case *rsa.PrivateKey, *ecdsa.PrivateKey:
|
||||
return generateToken(claims, header, j.alg, t)
|
||||
default:
|
||||
return "", "", fmt.Errorf("unsupported private / public key pairs: %T, %T", t, tt)
|
||||
}
|
||||
default:
|
||||
return "", "", fmt.Errorf("unsupported private key type: %T", t)
|
||||
}
|
||||
}
|
||||
|
||||
// Validate validates a token and returns its signature or an error if the token is not valid.
|
||||
func (j *Signer) Validate(ctx context.Context, tokenString string) (sig string, err error) {
|
||||
var (
|
||||
key crypto.PublicKey
|
||||
)
|
||||
|
||||
if key, err = j.GetPublicKey(ctx); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return validateToken(tokenString, key)
|
||||
}
|
||||
|
||||
// Decode will decode a JWT token.
|
||||
func (j *Signer) Decode(ctx context.Context, tokenString string) (token *fjwt.Token, err error) {
|
||||
var (
|
||||
key crypto.PublicKey
|
||||
)
|
||||
|
||||
if key, err = j.GetPublicKey(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return decodeToken(tokenString, key)
|
||||
}
|
||||
|
||||
// GetSignature will return the signature of a token.
|
||||
func (j *Signer) GetSignature(ctx context.Context, tokenString string) (sig string, err error) {
|
||||
return getTokenSignature(tokenString)
|
||||
}
|
||||
|
||||
// Hash will return a given hash based on the byte input or an error upon fail.
|
||||
func (j *Signer) Hash(ctx context.Context, in []byte) (sum []byte, err error) {
|
||||
hash := j.hash.New()
|
||||
|
||||
if _, err = hash.Write(in); err != nil {
|
||||
return []byte{}, errorsx.WithStack(err)
|
||||
}
|
||||
|
||||
return hash.Sum([]byte{}), nil
|
||||
}
|
||||
|
||||
// GetSigningMethodLength will return the length of the signing method.
|
||||
func (j *Signer) GetSigningMethodLength(ctx context.Context) (size int) {
|
||||
return j.hash.Size()
|
||||
}
|
||||
|
||||
func generateToken(claims fjwt.MapClaims, header fjwt.Mapper, signingMethod jwt.SigningMethod, key any) (rawToken string, sig string, err error) {
|
||||
if header == nil || claims == nil {
|
||||
return "", "", errors.New("either claims or header is nil")
|
||||
}
|
||||
|
||||
token := jwt.NewWithClaims(signingMethod, claims)
|
||||
|
||||
token.Header = assign(token.Header, header.ToMap())
|
||||
|
||||
if rawToken, err = token.SignedString(key); err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
if sig, err = getTokenSignature(rawToken); err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
return rawToken, sig, nil
|
||||
}
|
||||
|
||||
func decodeToken(tokenString string, key any) (token *fjwt.Token, err error) {
|
||||
return fjwt.ParseWithClaims(tokenString, fjwt.MapClaims{}, func(*fjwt.Token) (any, error) {
|
||||
return key, nil
|
||||
})
|
||||
}
|
||||
|
||||
func validateToken(tokenString string, key any) (sig string, err error) {
|
||||
if _, err = decodeToken(tokenString, key); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return getTokenSignature(tokenString)
|
||||
}
|
||||
|
||||
func getTokenSignature(tokenString string) (sig string, err error) {
|
||||
parts := strings.Split(tokenString, ".")
|
||||
|
||||
if len(parts) != 3 {
|
||||
return "", errors.New("header, body and signature must all be set")
|
||||
}
|
||||
|
||||
return parts[2], nil
|
||||
}
|
||||
|
||||
func assign(a, b map[string]any) map[string]any {
|
||||
for k, w := range b {
|
||||
if _, ok := a[k]; ok {
|
||||
continue
|
||||
}
|
||||
|
||||
a[k] = w
|
||||
}
|
||||
|
||||
return a
|
||||
}
|
||||
|
|
|
@ -1,48 +1,450 @@
|
|||
package oidc
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
fjwt "github.com/ory/fosite/token/jwt"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"gopkg.in/square/go-jose.v2"
|
||||
|
||||
"github.com/authelia/authelia/v4/internal/configuration/schema"
|
||||
)
|
||||
|
||||
func TestKeyManager_AddActiveJWK(t *testing.T) {
|
||||
manager := NewKeyManager()
|
||||
assert.Nil(t, manager.jwk)
|
||||
assert.Nil(t, manager.Strategy())
|
||||
func TestKeyManager(t *testing.T) {
|
||||
config := &schema.OpenIDConnectConfiguration{
|
||||
Discovery: schema.OpenIDConnectDiscovery{
|
||||
DefaultKeyID: "kid-RS256-sig",
|
||||
},
|
||||
IssuerJWKS: []schema.JWK{
|
||||
{
|
||||
Use: KeyUseSignature,
|
||||
Algorithm: SigningAlgRSAUsingSHA256,
|
||||
Key: keyRSA2048,
|
||||
CertificateChain: certRSA2048,
|
||||
},
|
||||
{
|
||||
Use: KeyUseSignature,
|
||||
Algorithm: SigningAlgRSAUsingSHA384,
|
||||
Key: keyRSA2048,
|
||||
CertificateChain: certRSA2048,
|
||||
},
|
||||
{
|
||||
Use: KeyUseSignature,
|
||||
Algorithm: SigningAlgRSAUsingSHA512,
|
||||
Key: keyRSA4096,
|
||||
CertificateChain: certRSA4096,
|
||||
},
|
||||
{
|
||||
Use: KeyUseSignature,
|
||||
Algorithm: SigningAlgRSAPSSUsingSHA256,
|
||||
Key: keyRSA2048,
|
||||
CertificateChain: certRSA2048,
|
||||
},
|
||||
{
|
||||
Use: KeyUseSignature,
|
||||
Algorithm: SigningAlgRSAPSSUsingSHA384,
|
||||
Key: keyRSA2048,
|
||||
CertificateChain: certRSA2048,
|
||||
},
|
||||
{
|
||||
Use: KeyUseSignature,
|
||||
Algorithm: SigningAlgRSAPSSUsingSHA512,
|
||||
Key: keyRSA4096,
|
||||
CertificateChain: certRSA4096,
|
||||
},
|
||||
{
|
||||
Use: KeyUseSignature,
|
||||
Algorithm: SigningAlgECDSAUsingP256AndSHA256,
|
||||
Key: keyECDSAP256,
|
||||
CertificateChain: certECDSAP256,
|
||||
},
|
||||
{
|
||||
Use: KeyUseSignature,
|
||||
Algorithm: SigningAlgECDSAUsingP384AndSHA384,
|
||||
Key: keyECDSAP384,
|
||||
CertificateChain: certECDSAP384,
|
||||
},
|
||||
{
|
||||
Use: KeyUseSignature,
|
||||
Algorithm: SigningAlgECDSAUsingP521AndSHA512,
|
||||
Key: keyECDSAP521,
|
||||
CertificateChain: certECDSAP521,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
j, err := manager.AddActiveJWK(schema.X509CertificateChain{}, MustParseRSAPrivateKey(exampleIssuerPrivateKey))
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, j)
|
||||
require.NotNil(t, manager.jwk)
|
||||
require.NotNil(t, manager.Strategy())
|
||||
for i, key := range config.IssuerJWKS {
|
||||
config.IssuerJWKS[i].KeyID = fmt.Sprintf("kid-%s-%s", key.Algorithm, key.Use)
|
||||
}
|
||||
|
||||
thumbprint, err := j.JSONWebKey().Thumbprint(crypto.SHA1)
|
||||
manager := NewKeyManager(config)
|
||||
|
||||
assert.NotNil(t, manager)
|
||||
|
||||
assert.Len(t, manager.kids, len(config.IssuerJWKS))
|
||||
assert.Len(t, manager.algs, len(config.IssuerJWKS))
|
||||
|
||||
assert.Equal(t, "kid-RS256-sig", manager.kid)
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
var (
|
||||
jwk *JWK
|
||||
err error
|
||||
)
|
||||
|
||||
jwk = manager.GetByAlg(ctx, "notalg")
|
||||
assert.Nil(t, jwk)
|
||||
|
||||
jwk = manager.GetByKID(ctx, "notalg")
|
||||
assert.Nil(t, jwk)
|
||||
|
||||
jwk = manager.GetByKID(ctx, "")
|
||||
assert.NotNil(t, jwk)
|
||||
assert.Equal(t, config.Discovery.DefaultKeyID, jwk.KeyID())
|
||||
|
||||
jwk, err = manager.GetByHeader(ctx, &fjwt.Headers{Extra: map[string]any{JWTHeaderKeyIdentifier: "notalg"}})
|
||||
assert.EqualError(t, err, "jwt header 'kid' with value 'notalg' does not match a managed jwk")
|
||||
assert.Nil(t, jwk)
|
||||
|
||||
jwk, err = manager.GetByHeader(ctx, &fjwt.Headers{Extra: map[string]any{}})
|
||||
assert.EqualError(t, err, "jwt header did not have a kid")
|
||||
assert.Nil(t, jwk)
|
||||
|
||||
jwk, err = manager.GetByHeader(ctx, nil)
|
||||
assert.EqualError(t, err, "jwt header was nil")
|
||||
assert.Nil(t, jwk)
|
||||
|
||||
kid, err := manager.GetKIDFromAlgStrict(ctx, "notalg")
|
||||
assert.EqualError(t, err, "alg not found")
|
||||
assert.Equal(t, "", kid)
|
||||
|
||||
kid = manager.GetKIDFromAlg(ctx, "notalg")
|
||||
assert.Equal(t, config.Discovery.DefaultKeyID, kid)
|
||||
|
||||
set := manager.Set(ctx)
|
||||
|
||||
assert.NotNil(t, set)
|
||||
assert.Len(t, set.Keys, len(config.IssuerJWKS))
|
||||
|
||||
data, err := json.Marshal(&set)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, data)
|
||||
|
||||
kid := strings.ToLower(fmt.Sprintf("%x", thumbprint)[:6])
|
||||
assert.Equal(t, manager.jwk.id, kid)
|
||||
assert.Equal(t, kid, j.JSONWebKey().KeyID)
|
||||
assert.Len(t, manager.jwks.Keys, 1)
|
||||
out := jose.JSONWebKeySet{}
|
||||
assert.NoError(t, json.Unmarshal(data, &out))
|
||||
assert.Equal(t, *set, out)
|
||||
|
||||
keys := manager.jwks.Key(kid)
|
||||
assert.Equal(t, keys[0].KeyID, kid)
|
||||
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)
|
||||
|
||||
privKey, err := manager.GetActivePrivateKey()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, privKey)
|
||||
t.Run("ShouldGetCorrectKey", func(t *testing.T) {
|
||||
jwk = manager.GetByKID(ctx, expectedKID)
|
||||
assert.NotNil(t, jwk)
|
||||
assert.Equal(t, expectedKID, jwk.KeyID())
|
||||
|
||||
webKey, err := manager.GetActiveJWK()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, webKey)
|
||||
jwk = manager.GetByAlg(ctx, alg)
|
||||
assert.NotNil(t, jwk)
|
||||
|
||||
keySet := manager.GetKeySet()
|
||||
assert.NotNil(t, keySet)
|
||||
assert.Equal(t, kid, manager.GetActiveKeyID())
|
||||
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.
|
||||
func NewOpenIDConnectProvider(config *schema.OpenIDConnectConfiguration, store storage.Provider, templates *templates.Provider) (provider *OpenIDConnectProvider, err error) {
|
||||
func NewOpenIDConnectProvider(config *schema.OpenIDConnectConfiguration, store storage.Provider, templates *templates.Provider) (provider *OpenIDConnectProvider) {
|
||||
if config == nil {
|
||||
return nil, nil
|
||||
return nil
|
||||
}
|
||||
|
||||
provider = &OpenIDConnectProvider{
|
||||
JSONWriter: herodot.NewJSONWriter(nil),
|
||||
Store: NewStore(config, store),
|
||||
KeyManager: NewKeyManager(config),
|
||||
Config: NewConfig(config, templates),
|
||||
}
|
||||
|
||||
provider.OAuth2Provider = fosite.NewOAuth2Provider(provider.Store, provider.Config)
|
||||
|
||||
if provider.KeyManager, err = NewKeyManagerWithConfiguration(config); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
provider.Config.Strategy.OpenID = &openid.DefaultStrategy{
|
||||
Signer: provider.KeyManager.Strategy(),
|
||||
Signer: provider.KeyManager,
|
||||
Config: provider.Config,
|
||||
}
|
||||
|
||||
provider.Config.LoadHandlers(provider.Store, provider.KeyManager.Strategy())
|
||||
provider.Config.LoadHandlers(provider.Store, provider.KeyManager)
|
||||
provider.Config.Strategy.ClientAuthentication = provider.DefaultClientAuthenticationStrategy
|
||||
|
||||
provider.discovery = NewOpenIDConnectWellKnownConfiguration(config)
|
||||
|
||||
return provider, nil
|
||||
return provider
|
||||
}
|
||||
|
||||
// GetOAuth2WellKnownConfiguration returns the discovery document for the OAuth Configuration.
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package oidc
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/url"
|
||||
"testing"
|
||||
|
||||
|
@ -11,16 +12,15 @@ import (
|
|||
)
|
||||
|
||||
func TestOpenIDConnectProvider_NewOpenIDConnectProvider_NotConfigured(t *testing.T) {
|
||||
provider, err := NewOpenIDConnectProvider(nil, nil, nil)
|
||||
provider := NewOpenIDConnectProvider(nil, nil, nil)
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Nil(t, provider)
|
||||
}
|
||||
|
||||
func TestNewOpenIDConnectProvider_ShouldEnableOptionalDiscoveryValues(t *testing.T) {
|
||||
provider, err := NewOpenIDConnectProvider(&schema.OpenIDConnectConfiguration{
|
||||
provider := NewOpenIDConnectProvider(&schema.OpenIDConnectConfiguration{
|
||||
IssuerCertificateChain: schema.X509CertificateChain{},
|
||||
IssuerPrivateKey: MustParseRSAPrivateKey(exampleIssuerPrivateKey),
|
||||
IssuerPrivateKey: keyRSA2048,
|
||||
EnablePKCEPlainChallenge: true,
|
||||
HMACSecret: badhmac,
|
||||
Clients: []schema.OpenIDConnectClientConfiguration{
|
||||
|
@ -36,7 +36,7 @@ func TestNewOpenIDConnectProvider_ShouldEnableOptionalDiscoveryValues(t *testing
|
|||
},
|
||||
}, nil, nil)
|
||||
|
||||
assert.NoError(t, err)
|
||||
require.NotNil(t, provider)
|
||||
|
||||
disco := provider.GetOpenIDConnectWellKnownConfiguration(examplecom)
|
||||
|
||||
|
@ -50,9 +50,9 @@ func TestNewOpenIDConnectProvider_ShouldEnableOptionalDiscoveryValues(t *testing
|
|||
}
|
||||
|
||||
func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GoodConfiguration(t *testing.T) {
|
||||
provider, err := NewOpenIDConnectProvider(&schema.OpenIDConnectConfiguration{
|
||||
provider := NewOpenIDConnectProvider(&schema.OpenIDConnectConfiguration{
|
||||
IssuerCertificateChain: schema.X509CertificateChain{},
|
||||
IssuerPrivateKey: MustParseRSAPrivateKey(exampleIssuerPrivateKey),
|
||||
IssuerPrivateKey: keyRSA2048,
|
||||
HMACSecret: badhmac,
|
||||
Clients: []schema.OpenIDConnectClientConfiguration{
|
||||
{
|
||||
|
@ -86,13 +86,12 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GoodConfiguration(t *tes
|
|||
}, nil, nil)
|
||||
|
||||
assert.NotNil(t, provider)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOpenIDConnectWellKnownConfiguration(t *testing.T) {
|
||||
provider, err := NewOpenIDConnectProvider(&schema.OpenIDConnectConfiguration{
|
||||
provider := NewOpenIDConnectProvider(&schema.OpenIDConnectConfiguration{
|
||||
IssuerCertificateChain: schema.X509CertificateChain{},
|
||||
IssuerPrivateKey: MustParseRSAPrivateKey(exampleIssuerPrivateKey),
|
||||
IssuerPrivateKey: keyRSA2048,
|
||||
HMACSecret: "asbdhaaskmdlkamdklasmdlkams",
|
||||
Clients: []schema.OpenIDConnectClientConfiguration{
|
||||
{
|
||||
|
@ -106,7 +105,7 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOpenIDConnectWellKnow
|
|||
},
|
||||
}, nil, nil)
|
||||
|
||||
assert.NoError(t, err)
|
||||
require.NotNil(t, provider)
|
||||
|
||||
disco := provider.GetOpenIDConnectWellKnownConfiguration(examplecom)
|
||||
|
||||
|
@ -153,19 +152,41 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOpenIDConnectWellKnow
|
|||
assert.Contains(t, disco.TokenEndpointAuthMethodsSupported, ClientAuthMethodClientSecretJWT)
|
||||
assert.Contains(t, disco.TokenEndpointAuthMethodsSupported, ClientAuthMethodNone)
|
||||
|
||||
assert.Len(t, disco.RevocationEndpointAuthMethodsSupported, 4)
|
||||
assert.Contains(t, disco.RevocationEndpointAuthMethodsSupported, ClientAuthMethodClientSecretBasic)
|
||||
assert.Contains(t, disco.RevocationEndpointAuthMethodsSupported, ClientAuthMethodClientSecretPost)
|
||||
assert.Contains(t, disco.RevocationEndpointAuthMethodsSupported, ClientAuthMethodClientSecretJWT)
|
||||
assert.Contains(t, disco.RevocationEndpointAuthMethodsSupported, ClientAuthMethodNone)
|
||||
|
||||
assert.Len(t, disco.IntrospectionEndpointAuthMethodsSupported, 2)
|
||||
assert.Contains(t, disco.IntrospectionEndpointAuthMethodsSupported, ClientAuthMethodClientSecretBasic)
|
||||
assert.Contains(t, disco.IntrospectionEndpointAuthMethodsSupported, ClientAuthMethodNone)
|
||||
|
||||
assert.Len(t, disco.GrantTypesSupported, 3)
|
||||
assert.Contains(t, disco.GrantTypesSupported, GrantTypeAuthorizationCode)
|
||||
assert.Contains(t, disco.GrantTypesSupported, GrantTypeRefreshToken)
|
||||
assert.Contains(t, disco.GrantTypesSupported, GrantTypeImplicit)
|
||||
|
||||
assert.Len(t, disco.RevocationEndpointAuthSigningAlgValuesSupported, 3)
|
||||
assert.Equal(t, disco.RevocationEndpointAuthSigningAlgValuesSupported[0], SigningAlgHMACUsingSHA256)
|
||||
assert.Equal(t, disco.RevocationEndpointAuthSigningAlgValuesSupported[1], SigningAlgHMACUsingSHA384)
|
||||
assert.Equal(t, disco.RevocationEndpointAuthSigningAlgValuesSupported[2], SigningAlgHMACUsingSHA512)
|
||||
|
||||
assert.Len(t, disco.TokenEndpointAuthSigningAlgValuesSupported, 3)
|
||||
assert.Equal(t, disco.TokenEndpointAuthSigningAlgValuesSupported[0], SigningAlgHMACUsingSHA256)
|
||||
assert.Equal(t, disco.TokenEndpointAuthSigningAlgValuesSupported[1], SigningAlgHMACUsingSHA384)
|
||||
assert.Equal(t, disco.TokenEndpointAuthSigningAlgValuesSupported[2], SigningAlgHMACUsingSHA512)
|
||||
|
||||
assert.Len(t, disco.IDTokenSigningAlgValuesSupported, 1)
|
||||
assert.Contains(t, disco.IDTokenSigningAlgValuesSupported, SigningAlgRSAUsingSHA256)
|
||||
|
||||
assert.Len(t, disco.UserinfoSigningAlgValuesSupported, 2)
|
||||
assert.Contains(t, disco.UserinfoSigningAlgValuesSupported, SigningAlgRSAUsingSHA256)
|
||||
assert.Contains(t, disco.UserinfoSigningAlgValuesSupported, SigningAlgNone)
|
||||
assert.Equal(t, disco.UserinfoSigningAlgValuesSupported[0], SigningAlgRSAUsingSHA256)
|
||||
assert.Equal(t, disco.UserinfoSigningAlgValuesSupported[1], SigningAlgNone)
|
||||
|
||||
assert.Len(t, disco.RequestObjectSigningAlgValuesSupported, 0)
|
||||
require.Len(t, disco.RequestObjectSigningAlgValuesSupported, 2)
|
||||
assert.Equal(t, SigningAlgRSAUsingSHA256, disco.RequestObjectSigningAlgValuesSupported[0])
|
||||
assert.Equal(t, SigningAlgNone, disco.RequestObjectSigningAlgValuesSupported[1])
|
||||
|
||||
assert.Len(t, disco.ClaimsSupported, 18)
|
||||
assert.Contains(t, disco.ClaimsSupported, ClaimAuthenticationMethodsReference)
|
||||
|
@ -186,12 +207,16 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOpenIDConnectWellKnow
|
|||
assert.Contains(t, disco.ClaimsSupported, ClaimGroups)
|
||||
assert.Contains(t, disco.ClaimsSupported, ClaimPreferredUsername)
|
||||
assert.Contains(t, disco.ClaimsSupported, ClaimFullName)
|
||||
|
||||
assert.Len(t, disco.PromptValuesSupported, 2)
|
||||
assert.Contains(t, disco.PromptValuesSupported, PromptConsent)
|
||||
assert.Contains(t, disco.PromptValuesSupported, PromptNone)
|
||||
}
|
||||
|
||||
func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOAuth2WellKnownConfiguration(t *testing.T) {
|
||||
provider, err := NewOpenIDConnectProvider(&schema.OpenIDConnectConfiguration{
|
||||
provider := NewOpenIDConnectProvider(&schema.OpenIDConnectConfiguration{
|
||||
IssuerCertificateChain: schema.X509CertificateChain{},
|
||||
IssuerPrivateKey: MustParseRSAPrivateKey(exampleIssuerPrivateKey),
|
||||
IssuerPrivateKey: keyRSA2048,
|
||||
HMACSecret: "asbdhaaskmdlkamdklasmdlkams",
|
||||
Clients: []schema.OpenIDConnectClientConfiguration{
|
||||
{
|
||||
|
@ -205,7 +230,7 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOAuth2WellKnownConfig
|
|||
},
|
||||
}, nil, nil)
|
||||
|
||||
assert.NoError(t, err)
|
||||
require.NotNil(t, provider)
|
||||
|
||||
disco := provider.GetOAuth2WellKnownConfiguration(examplecom)
|
||||
|
||||
|
@ -278,9 +303,9 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOAuth2WellKnownConfig
|
|||
}
|
||||
|
||||
func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOpenIDConnectWellKnownConfigurationWithPlainPKCE(t *testing.T) {
|
||||
provider, err := NewOpenIDConnectProvider(&schema.OpenIDConnectConfiguration{
|
||||
provider := NewOpenIDConnectProvider(&schema.OpenIDConnectConfiguration{
|
||||
IssuerCertificateChain: schema.X509CertificateChain{},
|
||||
IssuerPrivateKey: MustParseRSAPrivateKey(exampleIssuerPrivateKey),
|
||||
IssuerPrivateKey: keyRSA2048,
|
||||
HMACSecret: "asbdhaaskmdlkamdklasmdlkams",
|
||||
EnablePKCEPlainChallenge: true,
|
||||
Clients: []schema.OpenIDConnectClientConfiguration{
|
||||
|
@ -295,7 +320,7 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOpenIDConnectWellKnow
|
|||
},
|
||||
}, nil, nil)
|
||||
|
||||
assert.NoError(t, err)
|
||||
require.NotNil(t, provider)
|
||||
|
||||
disco := provider.GetOpenIDConnectWellKnownConfiguration(examplecom)
|
||||
|
||||
|
@ -303,3 +328,44 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOpenIDConnectWellKnow
|
|||
assert.Equal(t, PKCEChallengeMethodSHA256, disco.CodeChallengeMethodsSupported[0])
|
||||
assert.Equal(t, PKCEChallengeMethodPlain, disco.CodeChallengeMethodsSupported[1])
|
||||
}
|
||||
|
||||
func TestNewOpenIDConnectProviderDiscovery(t *testing.T) {
|
||||
provider := NewOpenIDConnectProvider(&schema.OpenIDConnectConfiguration{
|
||||
IssuerCertificateChain: schema.X509CertificateChain{},
|
||||
IssuerPrivateKey: keyRSA2048,
|
||||
HMACSecret: "asbdhaaskmdlkamdklasmdlkams",
|
||||
EnablePKCEPlainChallenge: true,
|
||||
Clients: []schema.OpenIDConnectClientConfiguration{
|
||||
{
|
||||
ID: "a-client",
|
||||
Secret: MustDecodeSecret("$plaintext$a-client-secret"),
|
||||
Policy: onefactor,
|
||||
RedirectURIs: []string{
|
||||
"https://google.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
}, nil, nil)
|
||||
|
||||
a := provider.GetOpenIDConnectWellKnownConfiguration("https://auth.example.com")
|
||||
|
||||
data, err := json.Marshal(&a)
|
||||
assert.NoError(t, err)
|
||||
|
||||
b := OpenIDConnectWellKnownConfiguration{}
|
||||
|
||||
assert.NoError(t, json.Unmarshal(data, &b))
|
||||
|
||||
assert.Equal(t, a, b)
|
||||
|
||||
y := provider.GetOAuth2WellKnownConfiguration("https://auth.example.com")
|
||||
|
||||
data, err = json.Marshal(&y)
|
||||
assert.NoError(t, err)
|
||||
|
||||
z := OAuth2WellKnownConfiguration{}
|
||||
|
||||
assert.NoError(t, json.Unmarshal(data, &z))
|
||||
|
||||
assert.Equal(t, y, z)
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@ import (
|
|||
func TestOpenIDConnectStore_GetClientPolicy(t *testing.T) {
|
||||
s := NewStore(&schema.OpenIDConnectConfiguration{
|
||||
IssuerCertificateChain: schema.X509CertificateChain{},
|
||||
IssuerPrivateKey: MustParseRSAPrivateKey(exampleIssuerPrivateKey),
|
||||
IssuerPrivateKey: keyRSA2048,
|
||||
Clients: []schema.OpenIDConnectClientConfiguration{
|
||||
{
|
||||
ID: myclient,
|
||||
|
@ -47,7 +47,7 @@ func TestOpenIDConnectStore_GetClientPolicy(t *testing.T) {
|
|||
func TestOpenIDConnectStore_GetInternalClient(t *testing.T) {
|
||||
s := NewStore(&schema.OpenIDConnectConfiguration{
|
||||
IssuerCertificateChain: schema.X509CertificateChain{},
|
||||
IssuerPrivateKey: MustParseRSAPrivateKey(exampleIssuerPrivateKey),
|
||||
IssuerPrivateKey: keyRSA2048,
|
||||
Clients: []schema.OpenIDConnectClientConfiguration{
|
||||
{
|
||||
ID: myclient,
|
||||
|
@ -82,7 +82,7 @@ func TestOpenIDConnectStore_GetInternalClient_ValidClient(t *testing.T) {
|
|||
|
||||
s := NewStore(&schema.OpenIDConnectConfiguration{
|
||||
IssuerCertificateChain: schema.X509CertificateChain{},
|
||||
IssuerPrivateKey: MustParseRSAPrivateKey(exampleIssuerPrivateKey),
|
||||
IssuerPrivateKey: keyRSA2048,
|
||||
Clients: []schema.OpenIDConnectClientConfiguration{c1},
|
||||
}, nil)
|
||||
|
||||
|
@ -110,7 +110,7 @@ func TestOpenIDConnectStore_GetInternalClient_InvalidClient(t *testing.T) {
|
|||
|
||||
s := NewStore(&schema.OpenIDConnectConfiguration{
|
||||
IssuerCertificateChain: schema.X509CertificateChain{},
|
||||
IssuerPrivateKey: MustParseRSAPrivateKey(exampleIssuerPrivateKey),
|
||||
IssuerPrivateKey: keyRSA2048,
|
||||
Clients: []schema.OpenIDConnectClientConfiguration{c1},
|
||||
}, nil)
|
||||
|
||||
|
@ -122,7 +122,7 @@ func TestOpenIDConnectStore_GetInternalClient_InvalidClient(t *testing.T) {
|
|||
func TestOpenIDConnectStore_IsValidClientID(t *testing.T) {
|
||||
s := NewStore(&schema.OpenIDConnectConfiguration{
|
||||
IssuerCertificateChain: schema.X509CertificateChain{},
|
||||
IssuerPrivateKey: MustParseRSAPrivateKey(exampleIssuerPrivateKey),
|
||||
IssuerPrivateKey: keyRSA2048,
|
||||
Clients: []schema.OpenIDConnectClientConfiguration{
|
||||
{
|
||||
ID: myclient,
|
||||
|
|
|
@ -122,7 +122,8 @@ type BaseClient struct {
|
|||
ResponseTypes []string
|
||||
ResponseModes []fosite.ResponseModeType
|
||||
|
||||
UserinfoSigningAlgorithm string
|
||||
IDTokenSigningAlg string
|
||||
UserinfoSigningAlg string
|
||||
|
||||
Policy authorization.Level
|
||||
|
||||
|
@ -150,7 +151,9 @@ type Client interface {
|
|||
GetSecret() algorithm.Digest
|
||||
GetSectorIdentifier() string
|
||||
GetConsentResponseBody(consent *model.OAuth2ConsentSession) ConsentGetResponseBody
|
||||
GetUserinfoSigningAlgorithm() string
|
||||
|
||||
GetUserinfoSigningAlg() string
|
||||
GetIDTokenSigningAlg() string
|
||||
|
||||
GetPAREnforcement() bool
|
||||
GetPKCEEnforcement() bool
|
||||
|
@ -222,13 +225,6 @@ func (c ClientConsentMode) String() string {
|
|||
}
|
||||
}
|
||||
|
||||
// KeyManager keeps track of all of the active/inactive rsa keys and provides them to services requiring them.
|
||||
// It additionally allows us to add keys for the purpose of key rotation in the future.
|
||||
type KeyManager struct {
|
||||
jwk *JWK
|
||||
jwks *jose.JSONWebKeySet
|
||||
}
|
||||
|
||||
// ConsentGetResponseBody schema of the response body of the consent GET endpoint.
|
||||
type ConsentGetResponseBody struct {
|
||||
ClientID string `json:"client_id"`
|
||||
|
|
|
@ -4,9 +4,101 @@ import (
|
|||
"strings"
|
||||
|
||||
"github.com/ory/fosite"
|
||||
"gopkg.in/square/go-jose.v2"
|
||||
)
|
||||
|
||||
// IsPushedAuthorizedRequest returns true if the requester has a PushedAuthorizationRequest redirect_uri value.
|
||||
func IsPushedAuthorizedRequest(r fosite.Requester, prefix string) bool {
|
||||
return strings.HasPrefix(r.GetRequestForm().Get(FormParameterRequestURI), prefix)
|
||||
}
|
||||
|
||||
// SortedSigningAlgs is a sorting type which allows the use of sort.Sort to order a list of OAuth 2.0 Signing Algs.
|
||||
// Sorting occurs in the order of from within the RFC's.
|
||||
type SortedSigningAlgs []string
|
||||
|
||||
func (algs SortedSigningAlgs) Len() int {
|
||||
return len(algs)
|
||||
}
|
||||
|
||||
func (algs SortedSigningAlgs) Less(i, j int) bool {
|
||||
return isSigningAlgLess(algs[i], algs[j])
|
||||
}
|
||||
|
||||
func (algs SortedSigningAlgs) Swap(i, j int) {
|
||||
algs[i], algs[j] = algs[j], algs[i]
|
||||
}
|
||||
|
||||
type SortedJSONWebKey []jose.JSONWebKey
|
||||
|
||||
func (jwks SortedJSONWebKey) Len() int {
|
||||
return len(jwks)
|
||||
}
|
||||
|
||||
func (jwks SortedJSONWebKey) Less(i, j int) bool {
|
||||
if jwks[i].Algorithm == jwks[j].Algorithm {
|
||||
return jwks[i].KeyID < jwks[j].KeyID
|
||||
}
|
||||
|
||||
return isSigningAlgLess(jwks[i].Algorithm, jwks[j].Algorithm)
|
||||
}
|
||||
|
||||
func (jwks SortedJSONWebKey) Swap(i, j int) {
|
||||
jwks[i], jwks[j] = jwks[j], jwks[i]
|
||||
}
|
||||
|
||||
//nolint:gocyclo // Low importance func.
|
||||
func isSigningAlgLess(i, j string) bool {
|
||||
switch {
|
||||
case i == j:
|
||||
return false
|
||||
case i == SigningAlgNone:
|
||||
return false
|
||||
case j == SigningAlgNone:
|
||||
return true
|
||||
default:
|
||||
var (
|
||||
ip, jp string
|
||||
it, jt bool
|
||||
)
|
||||
|
||||
if len(i) > 2 {
|
||||
it = true
|
||||
ip = i[:2]
|
||||
}
|
||||
|
||||
if len(j) > 2 {
|
||||
jt = true
|
||||
jp = j[:2]
|
||||
}
|
||||
|
||||
switch {
|
||||
case it && jt && ip == jp:
|
||||
return i < j
|
||||
case ip == SigningAlgPrefixHMAC:
|
||||
return true
|
||||
case jp == SigningAlgPrefixHMAC:
|
||||
return false
|
||||
case ip == SigningAlgPrefixRSAPSS:
|
||||
return false
|
||||
case jp == SigningAlgPrefixRSAPSS:
|
||||
return true
|
||||
case ip == SigningAlgPrefixRSA:
|
||||
return true
|
||||
case jp == SigningAlgPrefixRSA:
|
||||
return false
|
||||
case ip == SigningAlgPrefixECDSA:
|
||||
return true
|
||||
case jp == SigningAlgPrefixECDSA:
|
||||
return false
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
SigningAlgPrefixRSA = "RS"
|
||||
SigningAlgPrefixHMAC = "HS"
|
||||
SigningAlgPrefixRSAPSS = "PS"
|
||||
SigningAlgPrefixECDSA = "ES"
|
||||
)
|
||||
|
|
|
@ -31,6 +31,7 @@ const (
|
|||
BlockTypePKIXPublicKey = "PUBLIC KEY"
|
||||
BlockTypeCertificate = "CERTIFICATE"
|
||||
BlockTypeCertificateRequest = "CERTIFICATE REQUEST"
|
||||
BlockTypeX509CRL = "X509 CRL"
|
||||
|
||||
KeyAlgorithmRSA = "RSA"
|
||||
KeyAlgorithmECDSA = "ECDSA"
|
||||
|
|
|
@ -13,6 +13,7 @@ import (
|
|||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/big"
|
||||
"net"
|
||||
"os"
|
||||
|
@ -188,28 +189,95 @@ func ParseX509FromPEM(data []byte) (key any, err error) {
|
|||
return nil, errors.New("failed to parse PEM block containing the key")
|
||||
}
|
||||
|
||||
return ParsePEMBlock(block)
|
||||
}
|
||||
|
||||
// ParseX509FromPEMRecursive allows returning the appropriate key type given some PEM encoded input.
|
||||
// For Keys this is a single value of one of *rsa.PrivateKey, *rsa.PublicKey, *ecdsa.PrivateKey, *ecdsa.PublicKey,
|
||||
// ed25519.PrivateKey, or ed25519.PublicKey. For certificates this is
|
||||
// either a *X509.Certificate, or a []*X509.Certificate.
|
||||
func ParseX509FromPEMRecursive(data []byte) (decoded any, err error) {
|
||||
var (
|
||||
block *pem.Block
|
||||
multi bool
|
||||
certificates []*x509.Certificate
|
||||
)
|
||||
|
||||
for i := 0; true; i++ {
|
||||
block, data = pem.Decode(data)
|
||||
|
||||
n := len(data)
|
||||
|
||||
switch {
|
||||
case block == nil:
|
||||
return nil, fmt.Errorf("failed to parse PEM blocks: data does not appear to be PEM encoded")
|
||||
case multi || n != 0:
|
||||
switch block.Type {
|
||||
case BlockTypeCertificate:
|
||||
var certificate *x509.Certificate
|
||||
|
||||
if certificate, err = x509.ParseCertificate(block.Bytes); err != nil {
|
||||
return nil, fmt.Errorf("failed to parse PEM blocks: data contains multiple blocks but #%d had an error during parsing: %w", i, err)
|
||||
}
|
||||
|
||||
certificates = append(certificates, certificate)
|
||||
default:
|
||||
return nil, fmt.Errorf("failed to parse PEM blocks: data contains multiple blocks but #%d has a '%s' block type and should have a '%s' block type", i, block.Type, BlockTypeCertificate)
|
||||
}
|
||||
|
||||
multi = true
|
||||
default:
|
||||
if decoded, err = ParsePEMBlock(block); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if n == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
switch {
|
||||
case multi:
|
||||
return certificates, nil
|
||||
default:
|
||||
return decoded, nil
|
||||
}
|
||||
}
|
||||
|
||||
// ParsePEMBlock parses a single PEM block into the relevant X509 data struct.
|
||||
func ParsePEMBlock(block *pem.Block) (key any, err error) {
|
||||
if block == nil {
|
||||
return nil, errors.New("failed to parse PEM block as it was empty")
|
||||
}
|
||||
|
||||
switch block.Type {
|
||||
case BlockTypeRSAPrivateKey:
|
||||
key, err = x509.ParsePKCS1PrivateKey(block.Bytes)
|
||||
return x509.ParsePKCS1PrivateKey(block.Bytes)
|
||||
case BlockTypeECDSAPrivateKey:
|
||||
key, err = x509.ParseECPrivateKey(block.Bytes)
|
||||
return x509.ParseECPrivateKey(block.Bytes)
|
||||
case BlockTypePKCS8PrivateKey:
|
||||
key, err = x509.ParsePKCS8PrivateKey(block.Bytes)
|
||||
return x509.ParsePKCS8PrivateKey(block.Bytes)
|
||||
case BlockTypeRSAPublicKey:
|
||||
key, err = x509.ParsePKCS1PublicKey(block.Bytes)
|
||||
return x509.ParsePKCS1PublicKey(block.Bytes)
|
||||
case BlockTypePKIXPublicKey:
|
||||
key, err = x509.ParsePKIXPublicKey(block.Bytes)
|
||||
return x509.ParsePKIXPublicKey(block.Bytes)
|
||||
case BlockTypeCertificate:
|
||||
key, err = x509.ParseCertificate(block.Bytes)
|
||||
return x509.ParseCertificate(block.Bytes)
|
||||
case BlockTypeCertificateRequest:
|
||||
return x509.ParseCertificateRequest(block.Bytes)
|
||||
case BlockTypeX509CRL:
|
||||
return x509.ParseRevocationList(block.Bytes)
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown block type: %s", block.Type)
|
||||
switch {
|
||||
case strings.Contains(block.Type, "PRIVATE KEY"):
|
||||
return x509.ParsePKCS8PrivateKey(block.Bytes)
|
||||
case strings.Contains(block.Type, "PUBLIC KEY"):
|
||||
return x509.ParsePKIXPublicKey(block.Bytes)
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown block type: %s", block.Type)
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return key, nil
|
||||
}
|
||||
|
||||
// CastX509AsCertificate converts an interface to an *x509.Certificate.
|
||||
|
@ -306,8 +374,25 @@ func NewX509CertPool(directory string) (certPool *x509.CertPool, warnings []erro
|
|||
return certPool, warnings, errors
|
||||
}
|
||||
|
||||
// WriteCertificateBytesToPEM writes a certificate/csr to a file in the PEM format.
|
||||
func WriteCertificateBytesToPEM(path string, csr bool, certs ...[]byte) (err error) {
|
||||
// WriteCertificateBytesAsPEMToPath writes a certificate/csr to a file in the PEM format.
|
||||
func WriteCertificateBytesAsPEMToPath(path string, csr bool, certs ...[]byte) (err error) {
|
||||
var out *os.File
|
||||
|
||||
if out, err = os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = WriteCertificateBytesAsPEMToWriter(out, csr, certs...); err != nil {
|
||||
_ = out.Close()
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// WriteCertificateBytesAsPEMToWriter writes a certificate/csr to a io.Writer in the PEM format.
|
||||
func WriteCertificateBytesAsPEMToWriter(wr io.Writer, csr bool, certs ...[]byte) (err error) {
|
||||
blockType := BlockTypeCertificate
|
||||
if csr {
|
||||
blockType = BlockTypeCertificateRequest
|
||||
|
@ -319,26 +404,34 @@ func WriteCertificateBytesToPEM(path string, csr bool, certs ...[]byte) (err err
|
|||
blocks[i] = &pem.Block{Type: blockType, Bytes: cert}
|
||||
}
|
||||
|
||||
return WritePEM(path, blocks...)
|
||||
return WritePEMBlocksToWriter(wr, blocks...)
|
||||
}
|
||||
|
||||
// WritePEM writes a set of *pem.Blocks to a file.
|
||||
func WritePEM(path string, blocks ...*pem.Block) (err error) {
|
||||
// WritePEMBlocksToPath writes a set of *pem.Blocks to a file.
|
||||
func WritePEMBlocksToPath(path string, blocks ...*pem.Block) (err error) {
|
||||
var out *os.File
|
||||
|
||||
if out, err = os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, block := range blocks {
|
||||
if err = pem.Encode(out, block); err != nil {
|
||||
_ = out.Close()
|
||||
if err = WritePEMBlocksToWriter(out, blocks...); err != nil {
|
||||
_ = out.Close()
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
return out.Close()
|
||||
}
|
||||
|
||||
func WritePEMBlocksToWriter(wr io.Writer, blocks ...*pem.Block) (err error) {
|
||||
for _, block := range blocks {
|
||||
if err = pem.Encode(wr, block); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return out.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
// WriteKeyToPEM writes a key that can be encoded as a PEM to a file in the PEM format.
|
||||
|
@ -348,7 +441,7 @@ func WriteKeyToPEM(key any, path string, pkcs8 bool) (err error) {
|
|||
return err
|
||||
}
|
||||
|
||||
return WritePEM(path, block)
|
||||
return WritePEMBlocksToPath(path, block)
|
||||
}
|
||||
|
||||
// PEMBlockFromX509Key turns a PublicKey or PrivateKey into a pem.Block.
|
||||
|
|
Loading…
Reference in New Issue