feat(oidc): issuer jwk certificates (#3989)

This allows for JWKs to include certificate information, either signed via Global PKI, Enterprise PKI, or self-signed.
pull/3855/head
James Elliott 2022-10-02 13:07:40 +11:00 committed by GitHub
parent 66ea374227
commit 6810c91d34
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 1912 additions and 360 deletions

View File

@ -815,35 +815,78 @@ notifier:
## HMAC Secret can also be set using a secret: https://www.authelia.com/c/secrets ## HMAC Secret can also be set using a secret: https://www.authelia.com/c/secrets
# hmac_secret: this_is_a_secret_abc123abc123abc # hmac_secret: this_is_a_secret_abc123abc123abc
## The issuer_certificate_chain is an optional PEM encoded certificate chain. It's used in conjunction with the
## issuer_private_key to sign JWT's. All certificates in the chain must be within the validity period, and every
## certificate included must be signed by the certificate immediately after it if provided.
# issuer_certificate_chain: |
# -----BEGIN CERTIFICATE-----
# MIIC5jCCAc6gAwIBAgIRAK4Sj7FiN6PXo/urPfO4E7owDQYJKoZIhvcNAQELBQAw
# EzERMA8GA1UEChMIQXV0aGVsaWEwHhcNNzAwMTAxMDAwMDAwWhcNNzEwMTAxMDAw
# MDAwWjATMREwDwYDVQQKEwhBdXRoZWxpYTCCASIwDQYJKoZIhvcNAQEBBQADggEP
# ADCCAQoCggEBAPKv3pSyP4ozGEiVLJ14dIWFCEGEgq7WUMI0SZZqQA2ID0L59U/Q
# /Usyy7uC9gfMUzODTpANtkOjFQcQAsxlR1FOjVBrX5QgjSvXwbQn3DtwMA7XWSl6
# LuYx2rBYSlMSN5UZQm/RxMtXfLK2b51WgEEYDFi+nECSqKzR4R54eOPkBEWRfvuY
# 91AMjlhpivg8e4JWkq4LVQUKbmiFYwIdK8XQiN4blY9WwXwJFYs5sQ/UYMwBFi0H
# kWOh7GEjfxgoUOPauIueZSMSlQp7zqAH39N0ZSYb6cS0Npj57QoWZSY3ak87ebcR
# Nf4rCvZLby7LoN7qYCKxmCaDD3x2+NYpWH8CAwEAAaM1MDMwDgYDVR0PAQH/BAQD
# AgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcN
# AQELBQADggEBAHSITqIQSNzonFl3DzxHPEzr2hp6peo45buAAtu8FZHoA+U7Icfh
# /ZXjPg7Xz+hgFwM/DTNGXkMWacQA/PaNWvZspgRJf2AXvNbMSs2UQODr7Tbv+Fb4
# lyblmMUNYFMCFVAMU0eIxXAFq2qcwv8UMcQFT0Z/35s6PVOakYnAGGQjTfp5Ljuq
# wsdc/xWmM0cHWube6sdRRUD7SY20KU/kWzl8iFO0VbSSrDf1AlEhnLEkp1SPaxXg
# OdBnl98MeoramNiJ7NT6Jnyb3zZ578fjaWfThiBpagItI8GZmG4s4Ovh2JbheN8i
# ZsjNr9jqHTjhyLVbDRlmJzcqoj4JhbKs6/I^invalid DO NOT USE=
# -----END CERTIFICATE-----
# -----BEGIN CERTIFICATE-----
# MIIDBDCCAeygAwIBAgIRALJsPg21kA0zY4F1wUCIuoMwDQYJKoZIhvcNAQELBQAw
# EzERMA8GA1UEChMIQXV0aGVsaWEwHhcNNzAwMTAxMDAwMDAwWhcNNzEwMTAxMDAw
# MDAwWjATMREwDwYDVQQKEwhBdXRoZWxpYTCCASIwDQYJKoZIhvcNAQEBBQADggEP
# ADCCAQoCggEBAMXHBvVxUzYk0u34/DINMSF+uiOekKOAjOrC6Mi9Ww8ytPVO7t2S
# zfTvM+XnEJqkFQFgimERfG/eGhjF9XIEY6LtnXe8ATvOK4nTwdufzBaoeQu3Gd50
# 5VXr6OHRo//ErrGvFXwP3g8xLePABsi/fkH3oDN+ztewOBMDzpd+KgTrk8ysv2ou
# kNRMKFZZqASvCgv0LD5KWvUCnL6wgf1oTXG7aztduA4oSkUP321GpOmBC5+5ElU7
# ysoRzvD12o9QJ/IfEaulIX06w9yVMo60C/h6A3U6GdkT1SiyTIqR7v7KU/IWd/Qi
# Lfftcj91VhCmJ73Meff2e2S2PrpjdXbG5FMCAwEAAaNTMFEwDgYDVR0PAQH/BAQD
# AgKkMA8GA1UdJQQIMAYGBFUdJQAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU
# Z7AtA3mzFc0InSBA5fiMfeLXA3owDQYJKoZIhvcNAQELBQADggEBAEE5hm1mtlk/
# kviCoHH4evbpw7rxPxDftIQlqYTtvMM4eWY/6icFoSZ4fUHEWYyps8SsPu/8f2tf
# 71LGgZn0FdHi1QU2H8m0HHK7TFw+5Q6RLrLdSyk0PItJ71s9en7r8pX820nAFEHZ
# HkOSfJZ7B5hFgUDkMtVM6bardXAhoqcMk4YCU96e9d4PB4eI+xGc+mNuYvov3RbB
# D0s8ICyojeyPVLerz4wHjZu68Z5frAzhZ68YbzNs8j2fIBKKHkHyLG1iQyF+LJVj
# 2PjCP+auJsj6fQQpMGoyGtpLcSDh+ptcTngUD8JsWipzTCjmaNqdPHAOYmcgtf4b
# qocikt3WAdU^invalid DO NOT USE=
# -----END CERTIFICATE-----
## The issuer_private_key is used to sign the JWT forged by OpenID Connect. ## The issuer_private_key is used to sign the JWT forged by OpenID Connect.
## Issuer Private Key can also be set using a secret: https://www.authelia.com/c/secrets ## Issuer Private Key can also be set using a secret: https://www.authelia.com/c/secrets
# issuer_private_key: | # issuer_private_key: |
# -----BEGIN RSA PRIVATE KEY----- # -----BEGIN RSA PRIVATE KEY-----
# MXIEogIB$AKCAQEAxZVJP3WF//PG2fLQoEC9DtdiFG/+00vqlbVzz47nyxKONIPI # MIIEpAIBAAKCAQEA8q/elLI/ijMYSJUsnXh0hYUIQYSCrtZQwjRJlmpADYgPQvn1
# lmL3UdmqpGTKMe/5Brqse4ZAKlQHiDbwzK9ypnfigtHuvh/JO0S7ChP70RC67ed1 # T9D9SzLLu4L2B8xTM4NOkA22Q6MVBxACzGVHUU6NUGtflCCNK9fBtCfcO3AwDtdZ
# HV1nyfz5eW3llbtGJPrlYLqITNgctHp6zmRUFtSzPj9qFvozI93LJi492yL1+vu8 # KXou5jHasFhKUxI3lRlCb9HEy1d8srZvnVaAQRgMWL6cQJKorNHhHnh44+QERZF+
# Un3Dm8+Qq6XM2tPdEcldB/dtBwOWoF+8eOOVsu0TDuB5bwlhBVGJuSAuzBPRS2bF # +5j3UAyOWGmK+Dx7glaSrgtVBQpuaIVjAh0rxdCI3huVj1bBfAkVizmxD9RgzAEW
# Ga4uk0JDdkDOMCEQxC5uWDFxgfERSMFyfLVWD47woDbuWEBq10c0z+dpWPMp7Ain # LQeRY6HsYSN/GChQ49q4i55lIxKVCnvOoAff03RlJhvpxLQ2mPntChZlJjdqTzt5
# YnnkqicwCN88Z0zid6MmMQ65F4+9Hc+qC/p6xwIDAQABAoIBAGlhaAHKor+Su3o/ # txE1/isK9ktvLsug3upgIrGYJoMPfHb41ilYfwIDAQABAoIBAQDTOdFf2JjHH1um
# AXqXTL5/rbYMzbLQiLt0XeJT69jpeqMTroZXHmWvXE3128mqnf0yzw/K2Ko6yxGh # aPgRAvNf9v7Nj5jytaRKs5nM6iNf46ls4QPreXnMhqSeSwj6lpNgBYxOgzC9Q+cc
# i+j/onya8FqpsVYCCgfsbn2/js1AyRJeIp6Y1ORsYnqbXJnxmkXa80AV/OBPW2/+ # Y4ob/paJJPaIJTxmP8K/gyWcOQlNToL1l+eJ20eQoZm23NGr5fIsunSBwLEpTrdB
# 60TtSdQrebY3iFPc+i2k+9bPTvpyyDLKlz8UwdZG+k5uyYNIyQTccz+PjwsIvDij # ENqqtcwhW937K8Pxy/Q1nuLyU2bc6Tn/ivLozc8n27dpQWWKh8537VY7ancIaACr
# 7tKYamhhLN3QXt3/aZTFpjTgezP4WyriZxjWrddHowc47q2rwNS95ND39JcysJAc # LJJLYxKqhQpjtBWAyCDvZQirnAOm9KnvIHaGXIswCZ4Xbsu0Y9NL+woARPyRVQvG
# 0Pcbu8A5lVa7Fx33uOtzDfKWIW7xVEN+OtPgN+FbTjXcXk5IZedl+pW5lU5P++G/ # jfxy4EmO9s1s6y7OObSukwKDSNihAKHx/VIbvVWx8g2Lv5fGOa+J2Y7o9Qurs8t5
# ZPvz+WECgYEA9g6HwdODW3e68bOqsFoKg35+vfUFMzlyMF8HFylNVfnLpTEDr637 # BQwMTt0BAoGBAPUw5Z32EszNepAeV3E2mPFUc5CLiqAxagZJuNDO2pKtyN29ETTR
# owzMFvcUxVd71b+gV5nnnbI+riUFIgyR8vhCjhy4moopDPahC4/KwN4NG6uz+i1h # Ma4O1cWtGb6RqcNNN/Iukfkdk27Q5nC9VJSUUPYelOLc1WYOoUf6oKRzE72dkMQV
# AB6D5+zn2BjnO/5xMMFGlApWtRNmJVGYlNDj3bXKh2VXzzy03VNeD8kCgYEAzZFL # R4bf6TkjD+OVR17fAfkswkGahZ5XA7j48KIQ+YC4jbnYKSxZTYyKPjH/AoGBAP1i
# OlzoRB1HKpTWIECcuvxofMxLOLb3zs0k2t/FYNYIpovmGWCCAULz13y53e5+/+5m # tqXt36OVlP+y84wWqZSjMelBIVa9phDVGJmmhz3i1cMni8eLpJzWecA3pfnG6Tm9
# 7I9VUZJFaIhaZ36qVBApCKdru69pZMkWCcQO9jELFcx51Ez7OgJWzu7GS1QJCPKC # ze5M4whASleEt+M00gEvNaU9ND+z0wBfi+/DwJYIbv8PQdGrBiZFrPhTPjGQUldR
# fEDxI0rZK21j93/Sl/nUnEir7CYpQ+wvCaGuHg8CgYAXgbncfY1+DokwkB6NbHy2 # lXccV2meeLZv7TagVxSi3DO6dSJfSEHyemd5j9mBAoGAX8Hv+0gOQZQCSOTAq8Nx
# pT4Mfbz6cNGE538w6kQ2I4AeDvmwLentYMqaow478CinegAiflSPTzkHwAemghbr # 6dZcp9gHlNaXnMsP9eTDckOSzh636JPGvj6m+GPJSSbkURUIQ3oyokMNwFqvlNos
# ZGZPV1UXhn13fJRUG2+eT1hnPVcbXnx223N0k8Bud6qXo65CnyRT/kzcTbcjd5Eh # fTaLhAOfjBZI9WnDTTQxpugWjphJ4HqbC67JC/qIiw5S6FdaEvGLEEoD4zoChywZ
# Hne2daicmMTzynPo9Q72aQKBgBmobO9X8VWvIdbaxO85oVZlctVA2pK1o7CYQmVf # 9oGAn+fz2d/0/JAH/FpFPgsCgYEAp/ipZgPzziiZ9ov1wbdAQcWRj7RaWnssPFpX
# UM+JZ4MCKzI3rYJizPS0iK5+ujNPmmEkcs2/qBIoEsCgOrpLWhPOcc/3UPxXbPzD # jXwEiXT3CgEMO4MJ4+KWIWOChrti3qFBg6i6lDyyS6Qyls7sLFbUdC7HlTcrOEMe
# D+sCrBOIdhxdj23qJNOnUfDNCGOpgUfpAzAYg4q8GKInvi1h7XukRnEvQi9MJ4LY # rBoTcCI1GqZNlqWOVQ65ZIEiaI7o1vPBZo2GMQEZuq8mDKFsOMThvvTrM5cAep84
# P1dZAoGASGcGnTMkmeSXP8ux+dvQJAiJskn/sJIgBZ5uq5GRCeLBUosRSVxM75UK # n6HJR4ECgYABWcbsSnr0MKvVth/inxjbKapbZnp2HUCuw87Ie5zK2Of/tbC20wwk
# vAh/c/RBj+pYXVKuPuHGZCQJxsdcRXzXNGouUtgbaYML5Me/Hagt20QzDRBfuGBg # yKw3vrGoE3O1t1g2m2tn8UGGASeZ842jZWjIODdSi5+icysQGuULKt86h/woz2SQ
# qeZBJaXhjElvw6PUWtg4x+LYRCBpq/bS3LK3ozZrSTukVkKDegw= # 27GoE2i5mh6Yez6VAYbUuns3FcwIsMyWLq043Tu2DNkx9ijOOAuQzw^invalid..
# DO NOT USE==
# -----END RSA PRIVATE KEY----- # -----END RSA PRIVATE KEY-----
## The lifespans configure the expiration for these token types. ## The lifespans configure the expiration for these token types.

View File

@ -33,33 +33,72 @@ The following snippet provides a sample-configuration for the OIDC identity prov
identity_providers: identity_providers:
oidc: oidc:
hmac_secret: this_is_a_secret_abc123abc123abc hmac_secret: this_is_a_secret_abc123abc123abc
issuer_certificate_chain: |
-----BEGIN CERTIFICATE-----
MIIC5jCCAc6gAwIBAgIRAK4Sj7FiN6PXo/urPfO4E7owDQYJKoZIhvcNAQELBQAw
EzERMA8GA1UEChMIQXV0aGVsaWEwHhcNNzAwMTAxMDAwMDAwWhcNNzEwMTAxMDAw
MDAwWjATMREwDwYDVQQKEwhBdXRoZWxpYTCCASIwDQYJKoZIhvcNAQEBBQADggEP
ADCCAQoCggEBAPKv3pSyP4ozGEiVLJ14dIWFCEGEgq7WUMI0SZZqQA2ID0L59U/Q
/Usyy7uC9gfMUzODTpANtkOjFQcQAsxlR1FOjVBrX5QgjSvXwbQn3DtwMA7XWSl6
LuYx2rBYSlMSN5UZQm/RxMtXfLK2b51WgEEYDFi+nECSqKzR4R54eOPkBEWRfvuY
91AMjlhpivg8e4JWkq4LVQUKbmiFYwIdK8XQiN4blY9WwXwJFYs5sQ/UYMwBFi0H
kWOh7GEjfxgoUOPauIueZSMSlQp7zqAH39N0ZSYb6cS0Npj57QoWZSY3ak87ebcR
Nf4rCvZLby7LoN7qYCKxmCaDD3x2+NYpWH8CAwEAAaM1MDMwDgYDVR0PAQH/BAQD
AgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcN
AQELBQADggEBAHSITqIQSNzonFl3DzxHPEzr2hp6peo45buAAtu8FZHoA+U7Icfh
/ZXjPg7Xz+hgFwM/DTNGXkMWacQA/PaNWvZspgRJf2AXvNbMSs2UQODr7Tbv+Fb4
lyblmMUNYFMCFVAMU0eIxXAFq2qcwv8UMcQFT0Z/35s6PVOakYnAGGQjTfp5Ljuq
wsdc/xWmM0cHWube6sdRRUD7SY20KU/kWzl8iFO0VbSSrDf1AlEhnLEkp1SPaxXg
OdBnl98MeoramNiJ7NT6Jnyb3zZ578fjaWfThiBpagItI8GZmG4s4Ovh2JbheN8i
ZsjNr9jqHTjhyLVbDRlmJzcqoj4JhbKs6/I^invalid DO NOT USE=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDBDCCAeygAwIBAgIRALJsPg21kA0zY4F1wUCIuoMwDQYJKoZIhvcNAQELBQAw
EzERMA8GA1UEChMIQXV0aGVsaWEwHhcNNzAwMTAxMDAwMDAwWhcNNzEwMTAxMDAw
MDAwWjATMREwDwYDVQQKEwhBdXRoZWxpYTCCASIwDQYJKoZIhvcNAQEBBQADggEP
ADCCAQoCggEBAMXHBvVxUzYk0u34/DINMSF+uiOekKOAjOrC6Mi9Ww8ytPVO7t2S
zfTvM+XnEJqkFQFgimERfG/eGhjF9XIEY6LtnXe8ATvOK4nTwdufzBaoeQu3Gd50
5VXr6OHRo//ErrGvFXwP3g8xLePABsi/fkH3oDN+ztewOBMDzpd+KgTrk8ysv2ou
kNRMKFZZqASvCgv0LD5KWvUCnL6wgf1oTXG7aztduA4oSkUP321GpOmBC5+5ElU7
ysoRzvD12o9QJ/IfEaulIX06w9yVMo60C/h6A3U6GdkT1SiyTIqR7v7KU/IWd/Qi
Lfftcj91VhCmJ73Meff2e2S2PrpjdXbG5FMCAwEAAaNTMFEwDgYDVR0PAQH/BAQD
AgKkMA8GA1UdJQQIMAYGBFUdJQAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU
Z7AtA3mzFc0InSBA5fiMfeLXA3owDQYJKoZIhvcNAQELBQADggEBAEE5hm1mtlk/
kviCoHH4evbpw7rxPxDftIQlqYTtvMM4eWY/6icFoSZ4fUHEWYyps8SsPu/8f2tf
71LGgZn0FdHi1QU2H8m0HHK7TFw+5Q6RLrLdSyk0PItJ71s9en7r8pX820nAFEHZ
HkOSfJZ7B5hFgUDkMtVM6bardXAhoqcMk4YCU96e9d4PB4eI+xGc+mNuYvov3RbB
D0s8ICyojeyPVLerz4wHjZu68Z5frAzhZ68YbzNs8j2fIBKKHkHyLG1iQyF+LJVj
2PjCP+auJsj6fQQpMGoyGtpLcSDh+ptcTngUD8JsWipzTCjmaNqdPHAOYmcgtf4b
qocikt3WAdU^invalid DO NOT USE=
-----END CERTIFICATE-----
issuer_private_key: | issuer_private_key: |
-----BEGIN RSA PRIVATE KEY----- -----BEGIN RSA PRIVATE KEY-----
MXIEogIB$AKCAQEAxZVJP3WF//PG2fLQoEC9DtdiFG/+00vqlbVzz47nyxKONIPI MIIEpAIBAAKCAQEA8q/elLI/ijMYSJUsnXh0hYUIQYSCrtZQwjRJlmpADYgPQvn1
lmL3UdmqpGTKMe/5Brqse4ZAKlQHiDbwzK9ypnfigtHuvh/JO0S7ChP70RC67ed1 T9D9SzLLu4L2B8xTM4NOkA22Q6MVBxACzGVHUU6NUGtflCCNK9fBtCfcO3AwDtdZ
HV1nyfz5eW3llbtGJPrlYLqITNgctHp6zmRUFtSzPj9qFvozI93LJi492yL1+vu8 KXou5jHasFhKUxI3lRlCb9HEy1d8srZvnVaAQRgMWL6cQJKorNHhHnh44+QERZF+
Un3Dm8+Qq6XM2tPdEcldB/dtBwOWoF+8eOOVsu0TDuB5bwlhBVGJuSAuzBPRS2bF +5j3UAyOWGmK+Dx7glaSrgtVBQpuaIVjAh0rxdCI3huVj1bBfAkVizmxD9RgzAEW
Ga4uk0JDdkDOMCEQxC5uWDFxgfERSMFyfLVWD47woDbuWEBq10c0z+dpWPMp7Ain LQeRY6HsYSN/GChQ49q4i55lIxKVCnvOoAff03RlJhvpxLQ2mPntChZlJjdqTzt5
YnnkqicwCN88Z0zid6MmMQ65F4+9Hc+qC/p6xwIDAQABAoIBAGlhaAHKor+Su3o/ txE1/isK9ktvLsug3upgIrGYJoMPfHb41ilYfwIDAQABAoIBAQDTOdFf2JjHH1um
AXqXTL5/rbYMzbLQiLt0XeJT69jpeqMTroZXHmWvXE3128mqnf0yzw/K2Ko6yxGh aPgRAvNf9v7Nj5jytaRKs5nM6iNf46ls4QPreXnMhqSeSwj6lpNgBYxOgzC9Q+cc
i+j/onya8FqpsVYCCgfsbn2/js1AyRJeIp6Y1ORsYnqbXJnxmkXa80AV/OBPW2/+ Y4ob/paJJPaIJTxmP8K/gyWcOQlNToL1l+eJ20eQoZm23NGr5fIsunSBwLEpTrdB
60TtSdQrebY3iFPc+i2k+9bPTvpyyDLKlz8UwdZG+k5uyYNIyQTccz+PjwsIvDij ENqqtcwhW937K8Pxy/Q1nuLyU2bc6Tn/ivLozc8n27dpQWWKh8537VY7ancIaACr
7tKYamhhLN3QXt3/aZTFpjTgezP4WyriZxjWrddHowc47q2rwNS95ND39JcysJAc LJJLYxKqhQpjtBWAyCDvZQirnAOm9KnvIHaGXIswCZ4Xbsu0Y9NL+woARPyRVQvG
0Pcbu8A5lVa7Fx33uOtzDfKWIW7xVEN+OtPgN+FbTjXcXk5IZedl+pW5lU5P++G/ jfxy4EmO9s1s6y7OObSukwKDSNihAKHx/VIbvVWx8g2Lv5fGOa+J2Y7o9Qurs8t5
ZPvz+WECgYEA9g6HwdODW3e68bOqsFoKg35+vfUFMzlyMF8HFylNVfnLpTEDr637 BQwMTt0BAoGBAPUw5Z32EszNepAeV3E2mPFUc5CLiqAxagZJuNDO2pKtyN29ETTR
owzMFvcUxVd71b+gV5nnnbI+riUFIgyR8vhCjhy4moopDPahC4/KwN4NG6uz+i1h Ma4O1cWtGb6RqcNNN/Iukfkdk27Q5nC9VJSUUPYelOLc1WYOoUf6oKRzE72dkMQV
AB6D5+zn2BjnO/5xMMFGlApWtRNmJVGYlNDj3bXKh2VXzzy03VNeD8kCgYEAzZFL R4bf6TkjD+OVR17fAfkswkGahZ5XA7j48KIQ+YC4jbnYKSxZTYyKPjH/AoGBAP1i
OlzoRB1HKpTWIECcuvxofMxLOLb3zs0k2t/FYNYIpovmGWCCAULz13y53e5+/+5m tqXt36OVlP+y84wWqZSjMelBIVa9phDVGJmmhz3i1cMni8eLpJzWecA3pfnG6Tm9
7I9VUZJFaIhaZ36qVBApCKdru69pZMkWCcQO9jELFcx51Ez7OgJWzu7GS1QJCPKC ze5M4whASleEt+M00gEvNaU9ND+z0wBfi+/DwJYIbv8PQdGrBiZFrPhTPjGQUldR
fEDxI0rZK21j93/Sl/nUnEir7CYpQ+wvCaGuHg8CgYAXgbncfY1+DokwkB6NbHy2 lXccV2meeLZv7TagVxSi3DO6dSJfSEHyemd5j9mBAoGAX8Hv+0gOQZQCSOTAq8Nx
pT4Mfbz6cNGE538w6kQ2I4AeDvmwLentYMqaow478CinegAiflSPTzkHwAemghbr 6dZcp9gHlNaXnMsP9eTDckOSzh636JPGvj6m+GPJSSbkURUIQ3oyokMNwFqvlNos
ZGZPV1UXhn13fJRUG2+eT1hnPVcbXnx223N0k8Bud6qXo65CnyRT/kzcTbcjd5Eh fTaLhAOfjBZI9WnDTTQxpugWjphJ4HqbC67JC/qIiw5S6FdaEvGLEEoD4zoChywZ
Hne2daicmMTzynPo9Q72aQKBgBmobO9X8VWvIdbaxO85oVZlctVA2pK1o7CYQmVf 9oGAn+fz2d/0/JAH/FpFPgsCgYEAp/ipZgPzziiZ9ov1wbdAQcWRj7RaWnssPFpX
UM+JZ4MCKzI3rYJizPS0iK5+ujNPmmEkcs2/qBIoEsCgOrpLWhPOcc/3UPxXbPzD jXwEiXT3CgEMO4MJ4+KWIWOChrti3qFBg6i6lDyyS6Qyls7sLFbUdC7HlTcrOEMe
D+sCrBOIdhxdj23qJNOnUfDNCGOpgUfpAzAYg4q8GKInvi1h7XukRnEvQi9MJ4LY rBoTcCI1GqZNlqWOVQ65ZIEiaI7o1vPBZo2GMQEZuq8mDKFsOMThvvTrM5cAep84
P1dZAoGASGcGnTMkmeSXP8ux+dvQJAiJskn/sJIgBZ5uq5GRCeLBUosRSVxM75UK n6HJR4ECgYABWcbsSnr0MKvVth/inxjbKapbZnp2HUCuw87Ie5zK2Of/tbC20wwk
vAh/c/RBj+pYXVKuPuHGZCQJxsdcRXzXNGouUtgbaYML5Me/Hagt20QzDRBfuGBg yKw3vrGoE3O1t1g2m2tn8UGGASeZ842jZWjIODdSi5+icysQGuULKt86h/woz2SQ
qeZBJaXhjElvw6PUWtg4x+LYRCBpq/bS3LK3ozZrSTukVkKDegw= 27GoE2i5mh6Yez6VAYbUuns3FcwIsMyWLq043Tu2DNkx9ijOOAuQzw^invalid..
DO NOT USE==
-----END RSA PRIVATE KEY----- -----END RSA PRIVATE KEY-----
access_token_lifespan: 1h access_token_lifespan: 1h
authorize_code_lifespan: 1m authorize_code_lifespan: 1m
@ -120,6 +159,23 @@ It's __strongly recommended__ this is a
[Random Alphanumeric String](../miscellaneous/guides.md#generating-a-random-alphanumeric-string) with 64 or more [Random Alphanumeric String](../miscellaneous/guides.md#generating-a-random-alphanumeric-string) with 64 or more
characters. 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] [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://www.rfc-editor.org/rfc/rfc7517
[x5c]: https://www.rfc-editor.org/rfc/rfc7517#section-4.7
[x5t]: https://www.rfc-editor.org/rfc/rfc7517#section-4.8
The first certificate in the chain must have the public key for the [issuer_private_key](#issuer_private_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.
### issuer_private_key ### issuer_private_key
{{< confkey type="string" required="yes" >}} {{< confkey type="string" required="yes" >}}
@ -127,10 +183,13 @@ characters.
*__Important Note:__ This can also be defined using a [secret](../methods/secrets.md) which is __strongly recommended__ *__Important Note:__ This can also be defined using a [secret](../methods/secrets.md) which is __strongly recommended__
especially for containerized deployments.* especially for containerized deployments.*
The private key in DER base64 ([RFC4648]) encoded PEM format used to encrypt the [OpenID Connect] [JWT]'s. The key must The private key in DER base64 ([RFC4648]) encoded PEM format used to sign/encrypt the [OpenID Connect] issued [JWT]'s.
be generated by the administrator and can be done by following the The key must be generated by the administrator and can be done by following the
[Generating an RSA Keypair](../miscellaneous/guides.md#generating-an-rsa-keypair) guide. [Generating an RSA Keypair](../miscellaneous/guides.md#generating-an-rsa-keypair) guide.
If the [issuer_certificate_chain](#issuer_certificate_chain) is provided the private key must include matching public
key data for the first certificate in the chain.
### access_token_lifespan ### access_token_lifespan
{{< confkey type="duration" default="1h" required="no" >}} {{< confkey type="duration" default="1h" required="no" >}}

View File

@ -815,35 +815,78 @@ notifier:
## HMAC Secret can also be set using a secret: https://www.authelia.com/c/secrets ## HMAC Secret can also be set using a secret: https://www.authelia.com/c/secrets
# hmac_secret: this_is_a_secret_abc123abc123abc # hmac_secret: this_is_a_secret_abc123abc123abc
## The issuer_certificate_chain is an optional PEM encoded certificate chain. It's used in conjunction with the
## issuer_private_key to sign JWT's. All certificates in the chain must be within the validity period, and every
## certificate included must be signed by the certificate immediately after it if provided.
# issuer_certificate_chain: |
# -----BEGIN CERTIFICATE-----
# MIIC5jCCAc6gAwIBAgIRAK4Sj7FiN6PXo/urPfO4E7owDQYJKoZIhvcNAQELBQAw
# EzERMA8GA1UEChMIQXV0aGVsaWEwHhcNNzAwMTAxMDAwMDAwWhcNNzEwMTAxMDAw
# MDAwWjATMREwDwYDVQQKEwhBdXRoZWxpYTCCASIwDQYJKoZIhvcNAQEBBQADggEP
# ADCCAQoCggEBAPKv3pSyP4ozGEiVLJ14dIWFCEGEgq7WUMI0SZZqQA2ID0L59U/Q
# /Usyy7uC9gfMUzODTpANtkOjFQcQAsxlR1FOjVBrX5QgjSvXwbQn3DtwMA7XWSl6
# LuYx2rBYSlMSN5UZQm/RxMtXfLK2b51WgEEYDFi+nECSqKzR4R54eOPkBEWRfvuY
# 91AMjlhpivg8e4JWkq4LVQUKbmiFYwIdK8XQiN4blY9WwXwJFYs5sQ/UYMwBFi0H
# kWOh7GEjfxgoUOPauIueZSMSlQp7zqAH39N0ZSYb6cS0Npj57QoWZSY3ak87ebcR
# Nf4rCvZLby7LoN7qYCKxmCaDD3x2+NYpWH8CAwEAAaM1MDMwDgYDVR0PAQH/BAQD
# AgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcN
# AQELBQADggEBAHSITqIQSNzonFl3DzxHPEzr2hp6peo45buAAtu8FZHoA+U7Icfh
# /ZXjPg7Xz+hgFwM/DTNGXkMWacQA/PaNWvZspgRJf2AXvNbMSs2UQODr7Tbv+Fb4
# lyblmMUNYFMCFVAMU0eIxXAFq2qcwv8UMcQFT0Z/35s6PVOakYnAGGQjTfp5Ljuq
# wsdc/xWmM0cHWube6sdRRUD7SY20KU/kWzl8iFO0VbSSrDf1AlEhnLEkp1SPaxXg
# OdBnl98MeoramNiJ7NT6Jnyb3zZ578fjaWfThiBpagItI8GZmG4s4Ovh2JbheN8i
# ZsjNr9jqHTjhyLVbDRlmJzcqoj4JhbKs6/I^invalid DO NOT USE=
# -----END CERTIFICATE-----
# -----BEGIN CERTIFICATE-----
# MIIDBDCCAeygAwIBAgIRALJsPg21kA0zY4F1wUCIuoMwDQYJKoZIhvcNAQELBQAw
# EzERMA8GA1UEChMIQXV0aGVsaWEwHhcNNzAwMTAxMDAwMDAwWhcNNzEwMTAxMDAw
# MDAwWjATMREwDwYDVQQKEwhBdXRoZWxpYTCCASIwDQYJKoZIhvcNAQEBBQADggEP
# ADCCAQoCggEBAMXHBvVxUzYk0u34/DINMSF+uiOekKOAjOrC6Mi9Ww8ytPVO7t2S
# zfTvM+XnEJqkFQFgimERfG/eGhjF9XIEY6LtnXe8ATvOK4nTwdufzBaoeQu3Gd50
# 5VXr6OHRo//ErrGvFXwP3g8xLePABsi/fkH3oDN+ztewOBMDzpd+KgTrk8ysv2ou
# kNRMKFZZqASvCgv0LD5KWvUCnL6wgf1oTXG7aztduA4oSkUP321GpOmBC5+5ElU7
# ysoRzvD12o9QJ/IfEaulIX06w9yVMo60C/h6A3U6GdkT1SiyTIqR7v7KU/IWd/Qi
# Lfftcj91VhCmJ73Meff2e2S2PrpjdXbG5FMCAwEAAaNTMFEwDgYDVR0PAQH/BAQD
# AgKkMA8GA1UdJQQIMAYGBFUdJQAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU
# Z7AtA3mzFc0InSBA5fiMfeLXA3owDQYJKoZIhvcNAQELBQADggEBAEE5hm1mtlk/
# kviCoHH4evbpw7rxPxDftIQlqYTtvMM4eWY/6icFoSZ4fUHEWYyps8SsPu/8f2tf
# 71LGgZn0FdHi1QU2H8m0HHK7TFw+5Q6RLrLdSyk0PItJ71s9en7r8pX820nAFEHZ
# HkOSfJZ7B5hFgUDkMtVM6bardXAhoqcMk4YCU96e9d4PB4eI+xGc+mNuYvov3RbB
# D0s8ICyojeyPVLerz4wHjZu68Z5frAzhZ68YbzNs8j2fIBKKHkHyLG1iQyF+LJVj
# 2PjCP+auJsj6fQQpMGoyGtpLcSDh+ptcTngUD8JsWipzTCjmaNqdPHAOYmcgtf4b
# qocikt3WAdU^invalid DO NOT USE=
# -----END CERTIFICATE-----
## The issuer_private_key is used to sign the JWT forged by OpenID Connect. ## The issuer_private_key is used to sign the JWT forged by OpenID Connect.
## Issuer Private Key can also be set using a secret: https://www.authelia.com/c/secrets ## Issuer Private Key can also be set using a secret: https://www.authelia.com/c/secrets
# issuer_private_key: | # issuer_private_key: |
# -----BEGIN RSA PRIVATE KEY----- # -----BEGIN RSA PRIVATE KEY-----
# MXIEogIB$AKCAQEAxZVJP3WF//PG2fLQoEC9DtdiFG/+00vqlbVzz47nyxKONIPI # MIIEpAIBAAKCAQEA8q/elLI/ijMYSJUsnXh0hYUIQYSCrtZQwjRJlmpADYgPQvn1
# lmL3UdmqpGTKMe/5Brqse4ZAKlQHiDbwzK9ypnfigtHuvh/JO0S7ChP70RC67ed1 # T9D9SzLLu4L2B8xTM4NOkA22Q6MVBxACzGVHUU6NUGtflCCNK9fBtCfcO3AwDtdZ
# HV1nyfz5eW3llbtGJPrlYLqITNgctHp6zmRUFtSzPj9qFvozI93LJi492yL1+vu8 # KXou5jHasFhKUxI3lRlCb9HEy1d8srZvnVaAQRgMWL6cQJKorNHhHnh44+QERZF+
# Un3Dm8+Qq6XM2tPdEcldB/dtBwOWoF+8eOOVsu0TDuB5bwlhBVGJuSAuzBPRS2bF # +5j3UAyOWGmK+Dx7glaSrgtVBQpuaIVjAh0rxdCI3huVj1bBfAkVizmxD9RgzAEW
# Ga4uk0JDdkDOMCEQxC5uWDFxgfERSMFyfLVWD47woDbuWEBq10c0z+dpWPMp7Ain # LQeRY6HsYSN/GChQ49q4i55lIxKVCnvOoAff03RlJhvpxLQ2mPntChZlJjdqTzt5
# YnnkqicwCN88Z0zid6MmMQ65F4+9Hc+qC/p6xwIDAQABAoIBAGlhaAHKor+Su3o/ # txE1/isK9ktvLsug3upgIrGYJoMPfHb41ilYfwIDAQABAoIBAQDTOdFf2JjHH1um
# AXqXTL5/rbYMzbLQiLt0XeJT69jpeqMTroZXHmWvXE3128mqnf0yzw/K2Ko6yxGh # aPgRAvNf9v7Nj5jytaRKs5nM6iNf46ls4QPreXnMhqSeSwj6lpNgBYxOgzC9Q+cc
# i+j/onya8FqpsVYCCgfsbn2/js1AyRJeIp6Y1ORsYnqbXJnxmkXa80AV/OBPW2/+ # Y4ob/paJJPaIJTxmP8K/gyWcOQlNToL1l+eJ20eQoZm23NGr5fIsunSBwLEpTrdB
# 60TtSdQrebY3iFPc+i2k+9bPTvpyyDLKlz8UwdZG+k5uyYNIyQTccz+PjwsIvDij # ENqqtcwhW937K8Pxy/Q1nuLyU2bc6Tn/ivLozc8n27dpQWWKh8537VY7ancIaACr
# 7tKYamhhLN3QXt3/aZTFpjTgezP4WyriZxjWrddHowc47q2rwNS95ND39JcysJAc # LJJLYxKqhQpjtBWAyCDvZQirnAOm9KnvIHaGXIswCZ4Xbsu0Y9NL+woARPyRVQvG
# 0Pcbu8A5lVa7Fx33uOtzDfKWIW7xVEN+OtPgN+FbTjXcXk5IZedl+pW5lU5P++G/ # jfxy4EmO9s1s6y7OObSukwKDSNihAKHx/VIbvVWx8g2Lv5fGOa+J2Y7o9Qurs8t5
# ZPvz+WECgYEA9g6HwdODW3e68bOqsFoKg35+vfUFMzlyMF8HFylNVfnLpTEDr637 # BQwMTt0BAoGBAPUw5Z32EszNepAeV3E2mPFUc5CLiqAxagZJuNDO2pKtyN29ETTR
# owzMFvcUxVd71b+gV5nnnbI+riUFIgyR8vhCjhy4moopDPahC4/KwN4NG6uz+i1h # Ma4O1cWtGb6RqcNNN/Iukfkdk27Q5nC9VJSUUPYelOLc1WYOoUf6oKRzE72dkMQV
# AB6D5+zn2BjnO/5xMMFGlApWtRNmJVGYlNDj3bXKh2VXzzy03VNeD8kCgYEAzZFL # R4bf6TkjD+OVR17fAfkswkGahZ5XA7j48KIQ+YC4jbnYKSxZTYyKPjH/AoGBAP1i
# OlzoRB1HKpTWIECcuvxofMxLOLb3zs0k2t/FYNYIpovmGWCCAULz13y53e5+/+5m # tqXt36OVlP+y84wWqZSjMelBIVa9phDVGJmmhz3i1cMni8eLpJzWecA3pfnG6Tm9
# 7I9VUZJFaIhaZ36qVBApCKdru69pZMkWCcQO9jELFcx51Ez7OgJWzu7GS1QJCPKC # ze5M4whASleEt+M00gEvNaU9ND+z0wBfi+/DwJYIbv8PQdGrBiZFrPhTPjGQUldR
# fEDxI0rZK21j93/Sl/nUnEir7CYpQ+wvCaGuHg8CgYAXgbncfY1+DokwkB6NbHy2 # lXccV2meeLZv7TagVxSi3DO6dSJfSEHyemd5j9mBAoGAX8Hv+0gOQZQCSOTAq8Nx
# pT4Mfbz6cNGE538w6kQ2I4AeDvmwLentYMqaow478CinegAiflSPTzkHwAemghbr # 6dZcp9gHlNaXnMsP9eTDckOSzh636JPGvj6m+GPJSSbkURUIQ3oyokMNwFqvlNos
# ZGZPV1UXhn13fJRUG2+eT1hnPVcbXnx223N0k8Bud6qXo65CnyRT/kzcTbcjd5Eh # fTaLhAOfjBZI9WnDTTQxpugWjphJ4HqbC67JC/qIiw5S6FdaEvGLEEoD4zoChywZ
# Hne2daicmMTzynPo9Q72aQKBgBmobO9X8VWvIdbaxO85oVZlctVA2pK1o7CYQmVf # 9oGAn+fz2d/0/JAH/FpFPgsCgYEAp/ipZgPzziiZ9ov1wbdAQcWRj7RaWnssPFpX
# UM+JZ4MCKzI3rYJizPS0iK5+ujNPmmEkcs2/qBIoEsCgOrpLWhPOcc/3UPxXbPzD # jXwEiXT3CgEMO4MJ4+KWIWOChrti3qFBg6i6lDyyS6Qyls7sLFbUdC7HlTcrOEMe
# D+sCrBOIdhxdj23qJNOnUfDNCGOpgUfpAzAYg4q8GKInvi1h7XukRnEvQi9MJ4LY # rBoTcCI1GqZNlqWOVQ65ZIEiaI7o1vPBZo2GMQEZuq8mDKFsOMThvvTrM5cAep84
# P1dZAoGASGcGnTMkmeSXP8ux+dvQJAiJskn/sJIgBZ5uq5GRCeLBUosRSVxM75UK # n6HJR4ECgYABWcbsSnr0MKvVth/inxjbKapbZnp2HUCuw87Ie5zK2Of/tbC20wwk
# vAh/c/RBj+pYXVKuPuHGZCQJxsdcRXzXNGouUtgbaYML5Me/Hagt20QzDRBfuGBg # yKw3vrGoE3O1t1g2m2tn8UGGASeZ842jZWjIODdSi5+icysQGuULKt86h/woz2SQ
# qeZBJaXhjElvw6PUWtg4x+LYRCBpq/bS3LK3ozZrSTukVkKDegw= # 27GoE2i5mh6Yez6VAYbUuns3FcwIsMyWLq043Tu2DNkx9ijOOAuQzw^invalid..
# DO NOT USE==
# -----END RSA PRIVATE KEY----- # -----END RSA PRIVATE KEY-----
## The lifespans configure the expiration for these token types. ## The lifespans configure the expiration for these token types.

View File

@ -31,8 +31,9 @@ const (
errFmtSecretIOIssue = "secrets: error loading secret path %s into key '%s': %v" errFmtSecretIOIssue = "secrets: error loading secret path %s into key '%s': %v"
errFmtGenerateConfiguration = "error occurred generating configuration: %+v" errFmtGenerateConfiguration = "error occurred generating configuration: %+v"
errFmtDecodeHookCouldNotParse = "could not decode '%s' to a %s: %w" errFmtDecodeHookCouldNotParse = "could not decode '%s' to a %s%s: %w"
errFmtDecodeHookCouldNotParseEmptyValue = "could not decode an empty value to a %s: %w" errFmtDecodeHookCouldNotParseBasic = "could not decode to a %s%s: %w"
errFmtDecodeHookCouldNotParseEmptyValue = "could not decode an empty value to a %s%s: %w"
) )
var secretSuffixes = []string{"key", "secret", "password", "token"} var secretSuffixes = []string{"key", "secret", "password", "token"}

View File

@ -1,6 +1,8 @@
package configuration package configuration
import ( import (
"crypto/rsa"
"crypto/x509"
"fmt" "fmt"
"net/mail" "net/mail"
"net/url" "net/url"
@ -23,11 +25,11 @@ func StringToMailAddressHookFunc() mapstructure.DecodeHookFuncType {
return data, nil return data, nil
} }
kindStr := "mail.Address (RFC5322)" prefixType := ""
if t.Kind() == reflect.Ptr { if t.Kind() == reflect.Ptr {
ptr = true ptr = true
kindStr = "*" + kindStr prefixType = "*"
} }
expectedType := reflect.TypeOf(mail.Address{}) expectedType := reflect.TypeOf(mail.Address{})
@ -44,7 +46,7 @@ func StringToMailAddressHookFunc() mapstructure.DecodeHookFuncType {
if dataStr != "" { if dataStr != "" {
if result, err = mail.ParseAddress(dataStr); err != nil { if result, err = mail.ParseAddress(dataStr); err != nil {
return nil, fmt.Errorf(errFmtDecodeHookCouldNotParse, dataStr, kindStr, err) return nil, fmt.Errorf(errFmtDecodeHookCouldNotParse, dataStr, prefixType, expectedType.String()+" (RFC5322)", err)
} }
} }
@ -69,11 +71,11 @@ func StringToURLHookFunc() mapstructure.DecodeHookFuncType {
return data, nil return data, nil
} }
kindStr := "url.URL" prefixType := ""
if t.Kind() == reflect.Ptr { if t.Kind() == reflect.Ptr {
ptr = true ptr = true
kindStr = "*" + kindStr prefixType = "*"
} }
expectedType := reflect.TypeOf(url.URL{}) expectedType := reflect.TypeOf(url.URL{})
@ -90,7 +92,7 @@ func StringToURLHookFunc() mapstructure.DecodeHookFuncType {
if dataStr != "" { if dataStr != "" {
if result, err = url.Parse(dataStr); err != nil { if result, err = url.Parse(dataStr); err != nil {
return nil, fmt.Errorf(errFmtDecodeHookCouldNotParse, dataStr, kindStr, err) return nil, fmt.Errorf(errFmtDecodeHookCouldNotParse, dataStr, prefixType, expectedType, err)
} }
} }
@ -119,11 +121,11 @@ func ToTimeDurationHookFunc() mapstructure.DecodeHookFuncType {
return data, nil return data, nil
} }
kindStr := "time.Duration" prefixType := ""
if t.Kind() == reflect.Ptr { if t.Kind() == reflect.Ptr {
ptr = true ptr = true
kindStr = "*" + kindStr prefixType = "*"
} }
expectedType := reflect.TypeOf(time.Duration(0)) expectedType := reflect.TypeOf(time.Duration(0))
@ -141,7 +143,7 @@ func ToTimeDurationHookFunc() mapstructure.DecodeHookFuncType {
dataStr := data.(string) dataStr := data.(string)
if result, err = utils.ParseDurationString(dataStr); err != nil { if result, err = utils.ParseDurationString(dataStr); err != nil {
return nil, fmt.Errorf(errFmtDecodeHookCouldNotParse, dataStr, kindStr, err) return nil, fmt.Errorf(errFmtDecodeHookCouldNotParse, dataStr, prefixType, expectedType, err)
} }
case f.Kind() == reflect.Int: case f.Kind() == reflect.Int:
seconds := data.(int) seconds := data.(int)
@ -176,11 +178,11 @@ func StringToRegexpHookFunc() mapstructure.DecodeHookFuncType {
return data, nil return data, nil
} }
kindStr := "regexp.Regexp" prefixType := ""
if t.Kind() == reflect.Ptr { if t.Kind() == reflect.Ptr {
ptr = true ptr = true
kindStr = "*" + kindStr prefixType = "*"
} }
expectedType := reflect.TypeOf(regexp.Regexp{}) expectedType := reflect.TypeOf(regexp.Regexp{})
@ -197,7 +199,7 @@ func StringToRegexpHookFunc() mapstructure.DecodeHookFuncType {
if dataStr != "" { if dataStr != "" {
if result, err = regexp.Compile(dataStr); err != nil { if result, err = regexp.Compile(dataStr); err != nil {
return nil, fmt.Errorf(errFmtDecodeHookCouldNotParse, dataStr, kindStr, err) return nil, fmt.Errorf(errFmtDecodeHookCouldNotParse, dataStr, prefixType, expectedType, err)
} }
} }
@ -206,7 +208,7 @@ func StringToRegexpHookFunc() mapstructure.DecodeHookFuncType {
} }
if result == nil { if result == nil {
return nil, fmt.Errorf(errFmtDecodeHookCouldNotParseEmptyValue, kindStr, errDecodeNonPtrMustHaveValue) return nil, fmt.Errorf(errFmtDecodeHookCouldNotParseEmptyValue, prefixType, expectedType, errDecodeNonPtrMustHaveValue)
} }
return *result, nil return *result, nil
@ -222,11 +224,11 @@ func StringToAddressHookFunc() mapstructure.DecodeHookFuncType {
return data, nil return data, nil
} }
kindStr := "Address" prefixType := ""
if t.Kind() == reflect.Ptr { if t.Kind() == reflect.Ptr {
ptr = true ptr = true
kindStr = "*" + kindStr prefixType = "*"
} }
expectedType := reflect.TypeOf(schema.Address{}) expectedType := reflect.TypeOf(schema.Address{})
@ -242,7 +244,7 @@ func StringToAddressHookFunc() mapstructure.DecodeHookFuncType {
var result *schema.Address var result *schema.Address
if result, err = schema.NewAddressFromString(dataStr); err != nil { if result, err = schema.NewAddressFromString(dataStr); err != nil {
return nil, fmt.Errorf(errFmtDecodeHookCouldNotParse, dataStr, kindStr, err) return nil, fmt.Errorf(errFmtDecodeHookCouldNotParse, dataStr, prefixType, expectedType, err)
} }
if ptr { if ptr {
@ -252,3 +254,131 @@ func StringToAddressHookFunc() mapstructure.DecodeHookFuncType {
return *result, nil return *result, nil
} }
} }
// StringToX509CertificateHookFunc decodes strings to x509.Certificate's.
func StringToX509CertificateHookFunc() mapstructure.DecodeHookFuncType {
return func(f reflect.Type, t reflect.Type, data interface{}) (value interface{}, err error) {
if f.Kind() != reflect.String {
return data, nil
}
if t.Kind() != reflect.Ptr {
return data, nil
}
expectedType := reflect.TypeOf(x509.Certificate{})
if t.Elem() != expectedType {
return data, nil
}
dataStr := data.(string)
var result *x509.Certificate
if dataStr == "" {
return result, nil
}
var i interface{}
if i, err = utils.ParseX509FromPEM([]byte(dataStr)); err != nil {
return nil, fmt.Errorf(errFmtDecodeHookCouldNotParseBasic, "*", expectedType, err)
}
switch r := i.(type) {
case *x509.Certificate:
return r, nil
default:
return nil, fmt.Errorf(errFmtDecodeHookCouldNotParseBasic, "*", expectedType, fmt.Errorf("the data is for a %T not a *%s", r, expectedType))
}
}
}
// StringToX509CertificateChainHookFunc decodes strings to schema.X509CertificateChain's.
func StringToX509CertificateChainHookFunc() mapstructure.DecodeHookFuncType {
return func(f reflect.Type, t reflect.Type, data interface{}) (value interface{}, err error) {
var ptr bool
if f.Kind() != reflect.String {
return data, nil
}
prefixType := ""
if t.Kind() == reflect.Ptr {
ptr = true
prefixType = "*"
}
expectedType := reflect.TypeOf(schema.X509CertificateChain{})
if ptr && t.Elem() != expectedType {
return data, nil
} else if !ptr && t != expectedType {
return data, nil
}
dataStr := data.(string)
var result *schema.X509CertificateChain
if dataStr == "" && ptr {
return result, nil
}
if result, err = schema.NewX509CertificateChain(dataStr); err != nil {
return nil, fmt.Errorf(errFmtDecodeHookCouldNotParseBasic, prefixType, expectedType, err)
}
if ptr {
return result, nil
}
if result == nil {
return schema.X509CertificateChain{}, nil
}
return *result, nil
}
}
// StringToRSAPrivateKeyHookFunc decodes strings to rsa.PrivateKey's.
func StringToRSAPrivateKeyHookFunc() mapstructure.DecodeHookFuncType {
return func(f reflect.Type, t reflect.Type, data interface{}) (value interface{}, err error) {
if f.Kind() != reflect.String {
return data, nil
}
if t.Kind() != reflect.Ptr {
return data, nil
}
expectedType := reflect.TypeOf(rsa.PrivateKey{})
if t.Elem() != expectedType {
return data, nil
}
dataStr := data.(string)
var result *rsa.PrivateKey
if dataStr == "" {
return result, nil
}
var i interface{}
if i, err = utils.ParseX509FromPEM([]byte(dataStr)); err != nil {
return nil, fmt.Errorf(errFmtDecodeHookCouldNotParseBasic, "*", expectedType, err)
}
switch r := i.(type) {
case *rsa.PrivateKey:
return r, nil
default:
return nil, fmt.Errorf(errFmtDecodeHookCouldNotParseBasic, "*", expectedType, fmt.Errorf("the data is for a %T not a *%s", r, expectedType))
}
}
}

View File

@ -1,6 +1,11 @@
package configuration_test package configuration_test
import ( import (
"bytes"
"crypto/ecdsa"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"net/mail" "net/mail"
"net/url" "net/url"
"reflect" "reflect"
@ -833,7 +838,7 @@ func TestStringToAddressHookFunc(t *testing.T) {
name: "ShouldFailDecode", name: "ShouldFailDecode",
have: "tcp://&!@^#*&!@#&*@!:2020", have: "tcp://&!@^#*&!@#&*@!:2020",
expected: schema.Address{}, expected: schema.Address{},
err: "could not decode 'tcp://&!@^#*&!@#&*@!:2020' to a Address: could not parse string 'tcp://&!@^#*&!@#&*@!:2020' as address: expected format is [<scheme>://]<ip>[:<port>]: parse \"tcp://&!@^\": invalid character \"^\" in host name", err: "could not decode 'tcp://&!@^#*&!@#&*@!:2020' to a schema.Address: could not parse string 'tcp://&!@^#*&!@#&*@!:2020' as address: expected format is [<scheme>://]<ip>[:<port>]: parse \"tcp://&!@^\": invalid character \"^\" in host name",
decode: false, decode: false,
}, },
} }
@ -862,6 +867,426 @@ func TestStringToAddressHookFunc(t *testing.T) {
} }
} }
func TestStringToRSAPrivateKeyHookFunc(t *testing.T) {
var nilkey *rsa.PrivateKey
testCases := []struct {
desc string
have interface{}
want interface{}
err string
decode bool
}{
{
desc: "ShouldDecodeRSAPrivateKey",
have: x509PrivateKeyRSA1,
want: mustParseRSAPrivateKey(x509PrivateKeyRSA1),
decode: true,
},
{
desc: "ShouldNotDecodeToECDSAPrivateKey",
have: x509PrivateKeyRSA1,
want: &ecdsa.PrivateKey{},
decode: false,
},
{
desc: "ShouldNotDecodeEmptyKey",
have: "",
want: nilkey,
decode: true,
},
{
desc: "ShouldNotDecodeECDSAKeyToRSAKey",
have: x509PrivateKeyEC1,
want: nilkey,
decode: true,
err: "could not decode to a *rsa.PrivateKey: the data is for a *ecdsa.PrivateKey not a *rsa.PrivateKey",
},
{
desc: "ShouldNotDecodeBadRSAPrivateKey",
have: x509PrivateKeyRSA2,
want: nilkey,
decode: true,
err: "could not decode to a *rsa.PrivateKey: failed to parse PEM block containing the key",
},
}
hook := configuration.StringToRSAPrivateKeyHookFunc()
for _, tc := range testCases {
t.Run(tc.desc, func(t *testing.T) {
result, err := hook(reflect.TypeOf(tc.have), reflect.TypeOf(tc.want), tc.have)
switch {
case !tc.decode:
assert.NoError(t, err)
assert.Equal(t, tc.have, result)
case tc.err == "":
assert.NoError(t, err)
require.Equal(t, tc.want, result)
default:
assert.EqualError(t, err, tc.err)
assert.Nil(t, result)
}
})
}
}
func TestStringToX509CertificateHookFunc(t *testing.T) {
var nilkey *x509.Certificate
testCases := []struct {
desc string
have interface{}
want interface{}
err string
decode bool
}{
{
desc: "ShouldDecodeRSACertificate",
have: x509CertificateRSA1,
want: mustParseX509Certificate(x509CertificateRSA1),
decode: true,
},
{
desc: "ShouldDecodeECDSACertificate",
have: x509CACertificateECDSA,
want: mustParseX509Certificate(x509CACertificateECDSA),
decode: true,
},
{
desc: "ShouldDecodeRSACACertificate",
have: x509CACertificateRSA,
want: mustParseX509Certificate(x509CACertificateRSA),
decode: true,
},
{
desc: "ShouldDecodeECDSACACertificate",
have: x509CACertificateECDSA,
want: mustParseX509Certificate(x509CACertificateECDSA),
decode: true,
},
{
desc: "ShouldDecodeEmptyCertificateToNil",
have: "",
want: nilkey,
decode: true,
},
{
desc: "ShouldNotDecodeECDSAKeyToCertificate",
have: x509PrivateKeyEC1,
want: nilkey,
decode: true,
err: "could not decode to a *x509.Certificate: the data is for a *ecdsa.PrivateKey not a *x509.Certificate",
},
{
desc: "ShouldNotDecodeBadRSAPrivateKeyToCertificate",
have: x509PrivateKeyRSA2,
want: nilkey,
decode: true,
err: "could not decode to a *x509.Certificate: failed to parse PEM block containing the key",
},
}
hook := configuration.StringToX509CertificateHookFunc()
for _, tc := range testCases {
t.Run(tc.desc, func(t *testing.T) {
result, err := hook(reflect.TypeOf(tc.have), reflect.TypeOf(tc.want), tc.have)
switch {
case !tc.decode:
assert.NoError(t, err)
assert.Equal(t, tc.have, result)
case tc.err == "":
assert.NoError(t, err)
require.Equal(t, tc.want, result)
default:
assert.EqualError(t, err, tc.err)
assert.Nil(t, result)
}
})
}
}
func TestStringToX509CertificateChainHookFunc(t *testing.T) {
var nilkey *schema.X509CertificateChain
testCases := []struct {
desc string
have interface{}
expected interface{}
err, verr string
decode bool
}{
{
desc: "ShouldDecodeRSACertificate",
have: x509CertificateRSA1,
expected: mustParseX509CertificateChain(x509CertificateRSA1),
decode: true,
},
{
desc: "ShouldDecodeRSACertificateNoPtr",
have: x509CertificateRSA1,
expected: *mustParseX509CertificateChain(x509CertificateRSA1),
decode: true,
},
{
desc: "ShouldDecodeRSACertificateChain",
have: buildChain(x509CertificateRSA1, x509CACertificateRSA),
expected: mustParseX509CertificateChain(x509CertificateRSA1, x509CACertificateRSA),
decode: true,
},
{
desc: "ShouldDecodeRSACertificateChainNoPtr",
have: buildChain(x509CertificateRSA1, x509CACertificateRSA),
expected: *mustParseX509CertificateChain(x509CertificateRSA1, x509CACertificateRSA),
decode: true,
},
{
desc: "ShouldNotDecodeBadRSACertificateChain",
have: buildChain(x509CertificateRSA1, x509CACertificateECDSA),
expected: mustParseX509CertificateChain(x509CertificateRSA1, x509CACertificateECDSA),
verr: "certificate #1 in chain is not signed properly by certificate #2 in chain: x509: signature algorithm specifies an RSA public key, but have public key of type *ecdsa.PublicKey",
decode: true,
},
{
desc: "ShouldDecodeECDSACertificate",
have: x509CACertificateECDSA,
expected: mustParseX509CertificateChain(x509CACertificateECDSA),
decode: true,
},
{
desc: "ShouldDecodeRSACACertificate",
have: x509CACertificateRSA,
expected: mustParseX509CertificateChain(x509CACertificateRSA),
decode: true,
},
{
desc: "ShouldDecodeECDSACACertificate",
have: x509CACertificateECDSA,
expected: mustParseX509CertificateChain(x509CACertificateECDSA),
decode: true,
},
{
desc: "ShouldDecodeEmptyCertificateToNil",
have: "",
expected: nilkey,
decode: true,
},
{
desc: "ShouldDecodeEmptyCertificateToEmptyStruct",
have: "",
expected: schema.X509CertificateChain{},
decode: true,
},
{
desc: "ShouldNotDecodeECDSAKeyToCertificate",
have: x509PrivateKeyEC1,
expected: nilkey,
decode: true,
err: "could not decode to a *schema.X509CertificateChain: the PEM data chain contains a EC PRIVATE KEY but only certificates are expected",
},
{
desc: "ShouldNotDecodeBadRSAPrivateKeyToCertificate",
have: x509PrivateKeyRSA2,
expected: nilkey,
decode: true,
err: "could not decode to a *schema.X509CertificateChain: invalid PEM block",
},
}
hook := configuration.StringToX509CertificateChainHookFunc()
for _, tc := range testCases {
t.Run(tc.desc, func(t *testing.T) {
actual, err := hook(reflect.TypeOf(tc.have), reflect.TypeOf(tc.expected), tc.have)
switch {
case !tc.decode:
assert.NoError(t, err)
assert.Equal(t, tc.have, actual)
case tc.err == "":
assert.NoError(t, err)
require.Equal(t, tc.expected, actual)
if tc.expected == nilkey {
break
}
switch chain := actual.(type) {
case *schema.X509CertificateChain:
require.NotNil(t, chain)
if tc.verr == "" {
assert.NoError(t, chain.Validate())
} else {
assert.EqualError(t, chain.Validate(), tc.verr)
}
case schema.X509CertificateChain:
require.NotNil(t, chain)
if tc.verr == "" {
assert.NoError(t, chain.Validate())
} else {
assert.EqualError(t, chain.Validate(), tc.verr)
}
}
default:
assert.EqualError(t, err, tc.err)
assert.Nil(t, actual)
}
})
}
}
var (
x509PrivateKeyRSA1 = `
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEA6z1LOg1ZCqb0lytXWZ+MRBpMHEXOoTOLYgfZXt1IYyE3Z758
cyalk0NYQhY5cZDsXPYWPvAHiPMUxutWkoxFwby56S+AbIMa3/Is+ILrHRJs8Exn
ZkpyrYFxPX12app2kErdmAkHSx0Z5/kuXiz96PHs8S8/ZbyZolLHzdfLtSzjvRm5
Zue5iFzsf19NJz5CIBfv8g5lRwtE8wNJoRSpn1xq7fqfuA0weDNFPzjlNWRLy6aa
rK7qJexRkmkCs4sLgyl+9NODYJpvmN8E1yhyC27E0joI6rBFVW7Ihv+cSPCdDzGp
EWe81x3AeqAa3mjVqkiq4u4Z2i8JDgBaPboqJwIDAQABAoIBAAFdLZ58jVOefDSU
L8F5R1rtvBs93GDa56f926jNJ6pLewLC+/2+757W+SAI+PRLntM7Kg3bXm/Q2QH+
Q1Y+MflZmspbWCdI61L5GIGoYKyeers59i+FpvySj5GHtLQRiTZ0+Kv1AXHSDWBm
9XneUOqU3IbZe0ifu1RRno72/VtjkGXbW8Mkkw+ohyGbIeTx/0/JQ6sSNZTT3Vk7
8i4IXptq3HSF0/vqZuah8rShoeNq72pD1YLM9YPdL5by1QkDLnqATDiCpLBTCaNV
I8sqYEun+HYbQzBj8ZACG2JVZpEEidONWQHw5BPWO95DSZYrVnEkuCqeH+u5vYt7
CHuJ3AECgYEA+W3v5z+j91w1VPHS0VB3SCDMouycAMIUnJPAbt+0LPP0scUFsBGE
hPAKddC54pmMZRQ2KIwBKiyWfCrJ8Xz8Yogn7fJgmwTHidJBr2WQpIEkNGlK3Dzi
jXL2sh0yC7sHvn0DqiQ79l/e7yRbSnv2wrTJEczOOH2haD7/tBRyCYECgYEA8W+q
E9YyGvEltnPFaOxofNZ8LHVcZSsQI5b6fc0iE7fjxFqeXPXEwGSOTwqQLQRiHn9b
CfPmIG4Vhyq0otVmlPvUnfBZ2OK+tl5X2/mQFO3ROMdvpi0KYa994uqfJdSTaqLn
jjoKFB906UFHnDQDLZUNiV1WwnkTglgLc+xrd6cCgYEAqqthyv6NyBTM3Tm2gcio
Ra9Dtntl51LlXZnvwy3IkDXBCd6BHM9vuLKyxZiziGx+Vy90O1xI872cnot8sINQ
Am+dur/tAEVN72zxyv0Y8qb2yfH96iKy9gxi5s75TnOEQgAygLnYWaWR2lorKRUX
bHTdXBOiS58S0UzCFEslGIECgYBqkO4SKWYeTDhoKvuEj2yjRYyzlu28XeCWxOo1
otiauX0YSyNBRt2cSgYiTzhKFng0m+QUJYp63/wymB/5C5Zmxi0XtWIDADpLhqLj
HmmBQ2Mo26alQ5YkffBju0mZyhVzaQop1eZi8WuKFV1FThPlB7hc3E0SM5zv2Grd
tQnOWwKBgQC40yZY0PcjuILhy+sIc0Wvh7LUA7taSdTye149kRvbvsCDN7Jh75lM
USjhLXY0Nld2zBm9r8wMb81mXH29uvD+tDqqsICvyuKlA/tyzXR+QTr7dCVKVwu0
1YjCJ36UpTsLre2f8nOSLtNmRfDPtbOE2mkOoO9dD9UU0XZwnvn9xw==
-----END RSA PRIVATE KEY-----`
x509PrivateKeyRSA2 = `
-----BEGIN RSA PRIVATE KEY-----
bad key
-----END RSA PRIVATE KEY-----`
x509PrivateKeyEC1 = `
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIMn970LSn8aKVhBM4vyUmpZyEdCT4riN+Lp4QU04zUhYoAoGCCqGSM49
AwEHoUQDQgAEMD69n22nd78GmaRDzy/s7muqhbc/OEnFS2mNtiRAA5FaX+kbkCB5
8pu/k2jkaSVNZtBYKPVAibHkhvakjVb66A==
-----END EC PRIVATE KEY-----`
x509CertificateRSA1 = `
-----BEGIN CERTIFICATE-----
MIIC5TCCAc2gAwIBAgIQfBUmKLmEvMqS6S9auKCY2DANBgkqhkiG9w0BAQsFADAT
MREwDwYDVQQKEwhBdXRoZWxpYTAeFw0yMjA5MDgxMDA5MThaFw0yMzA5MDgxMDA5
MThaMBMxETAPBgNVBAoTCEF1dGhlbGlhMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
MIIBCgKCAQEApqno1cOpDcgKmOJqeDQGIGH5/ZnqcJ4xud6eOUfbDqel3b0RkAQX
mFYWEDO/PDOAOjYk/xSwZGo3jDofOHGhrKstQqLdweHGfme5NXYHJda7nGv/OY5q
zUuEG4xBVgUsvbshWZ18H+bIQpwiP6tDAabxc0B7J15F1pArK8QN4pDTfsqZDwMi
Qyo638XfUbDzEVZRbdDKxHz5g0w2vFdXon8uOxRRb0+zlHF9nM4PiESNgiUIYeua
8Q5yP10SY2k9zlQ/OFJ4XhQmioCJvNjJE/TSc5/ECub2n7hTZhN5TGKagukZ5NAy
KgbvNYW+CN+H4pFJt/9WptiDfBqhlUvjnwIDAQABozUwMzAOBgNVHQ8BAf8EBAMC
BaAwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0B
AQsFAAOCAQEAH9veGzfWqXxsa5s2KHV2Jzed9V8KSs1Qy9QKRez1i2OMvMPh2DRM
RLzAAp/XigjxLQF/LFXuoFW0Qg8BRb44iRgZrCiqVOUnd3xTrS/CcFExnpQI4F12
/U70o97rkTonCOHmUUW6vQfWSXR/GU3/faRLJjiqcpWLZhTQNrnsip1ym3B2NMdk
gMKkT8Acx1DX48MvTE4+DyqCS8TlJbacBJ2RFFELKu3jYnVNyrb0ywLxoCtWqBBE
veVj+VMn9hNY1u5uydLsUDOlT5QyQcEuUzjjdhsJKEgDE5daNtB2OJJnd9IOMzUA
hasPZETCCKabTpWiEPw1Cn/ZRqya0SZqFg==
-----END CERTIFICATE-----
`
x509CACertificateRSA = `
-----BEGIN CERTIFICATE-----
MIIDBDCCAeygAwIBAgIRAJfz0dHS9UkDngE55lUPdu4wDQYJKoZIhvcNAQELBQAw
EzERMA8GA1UEChMIQXV0aGVsaWEwHhcNMjIwOTA4MDk1OTI1WhcNMjMwOTA4MDk1
OTI1WjATMREwDwYDVQQKEwhBdXRoZWxpYTCCASIwDQYJKoZIhvcNAQEBBQADggEP
ADCCAQoCggEBALfivbwq9r5X8N+NSbNHVuKbCb9f9vD5Xw2pOjSVvVjFkWQ1YKJu
JGx9yskhHBZTBt76cInipA+0PqCBrBrjij1lh2StvzRVuQwgFG6H01LxBPi0JyYv
Is94F6PHr6fSBgFWB5GNQ797KQIOdIr057uEFbp0eBMxxqiQ9gdyD0HPretrx1Uy
kHuF6jck958combn9luHW0i53mt8706j7UAhxFqu9YUeklTM1VqUiRm5+nJKIdNA
LiDMGVAuoxjhF6aIgY0yh5mL5mKtYYzhtA8WryrMzBgFRUGzHCSI1TNisA8wSf2T
Z2JhbFHrFPR5fiSqAEHok3UXu++wsfl/lisCAwEAAaNTMFEwDgYDVR0PAQH/BAQD
AgKkMA8GA1UdJQQIMAYGBFUdJQAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU
OSXG42bCPNuWeP0ahScUMVjxe/wwDQYJKoZIhvcNAQELBQADggEBAFRnubHiYy1H
PGODKA++UY9eAmmaCJxzuWpY8FY9fBz8/VBzcp8vaURPmmQ/34QcqfaSHDM2jIaL
dQ2o9Ae5NjbRzLB6a5DcVO50oHG4BHP1ix4Bt3POr8J80KgA9pOIyAQqbAlFBSzQ
l9yrzVULyf+qpUmByRf5qy2kQJOBfMJbn5j+BprWKwbcI8OAZWWSLItTXqJDrFTk
OMZK4wZ6KiZM07KWMlwW/CE0QRzDk5MXfbwRt4D8pyx6rGKqI7QRusjm5osIpHZV
26FdBdBvEhq4i8UsmDsQqH3iMY1AKmojZToZb5rStOZWHO/BZZ7nT2bscNjwm0E8
6E2l6czk8ss=
-----END CERTIFICATE-----`
x509CACertificateECDSA = `
-----BEGIN CERTIFICATE-----
MIIBdzCCAR2gAwIBAgIQUzb62irYb/7B2H0c1AbriDAKBggqhkjOPQQDAjATMREw
DwYDVQQKEwhBdXRoZWxpYTAeFw0yMjA5MDgxMDEzNDZaFw0yMzA5MDgxMDEzNDZa
MBMxETAPBgNVBAoTCEF1dGhlbGlhMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE
b/EiIBpmifCI34JdI7luetygue2rTtoNH0QXhtrjMuZNugT29LUz+DobZQxvGsOY
4TXzAQXq4gnTb7enNWFgsaNTMFEwDgYDVR0PAQH/BAQDAgKkMA8GA1UdJQQIMAYG
BFUdJQAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUxlDPBKHKawuvhtQTN874
TeCEKjkwCgYIKoZIzj0EAwIDSAAwRQIgAQeV01FZ/VkSERwaRKTeXAXxmKyc/05O
uDv6M2spMi0CIQC8uOSMcv11vp1ylsGg38N6XYA+GQa1BHRd79+91hC+7w==
-----END CERTIFICATE-----`
)
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")
}
if block.Type != "RSA PRIVATE KEY" {
panic("not private key")
}
key, err := x509.ParsePKCS1PrivateKey(block.Bytes)
if err != nil {
panic(err)
}
return key
}
func mustParseX509Certificate(data string) *x509.Certificate {
block, _ := pem.Decode([]byte(data))
if block == nil || len(block.Bytes) == 0 {
panic("not a PEM")
}
cert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
panic(err)
}
return cert
}
func buildChain(pems ...string) string {
buf := bytes.Buffer{}
for i, data := range pems {
if i != 0 {
buf.WriteString("\n")
}
buf.WriteString(data)
}
return buf.String()
}
func mustParseX509CertificateChain(datas ...string) *schema.X509CertificateChain {
chain, err := schema.NewX509CertificateChain(buildChain(datas...))
if err != nil {
panic(err)
}
return chain
}
func testInt32Ptr(i int32) *int32 { func testInt32Ptr(i int32) *int32 {
return &i return &i
} }

View File

@ -61,6 +61,9 @@ func unmarshal(ko *koanf.Koanf, val *schema.StructValidator, path string, o inte
StringToURLHookFunc(), StringToURLHookFunc(),
StringToRegexpHookFunc(), StringToRegexpHookFunc(),
StringToAddressHookFunc(), StringToAddressHookFunc(),
StringToX509CertificateHookFunc(),
StringToX509CertificateChainHookFunc(),
StringToRSAPrivateKeyHookFunc(),
ToTimeDurationHookFunc(), ToTimeDurationHookFunc(),
), ),
Metadata: nil, Metadata: nil,

View File

@ -48,7 +48,7 @@ func TestShouldErrorSecretNotExist(t *testing.T) {
errFmt := utils.GetExpectedErrTxt("filenotfound") errFmt := utils.GetExpectedErrTxt("filenotfound")
// ignore the errors before this as they are checked by the valdator. // ignore the errors before this as they are checked by the validator.
assert.EqualError(t, errs[0], fmt.Sprintf(errFmtSecretIOIssue, filepath.Join(dir, "authentication"), "authentication_backend.ldap.password", fmt.Sprintf(errFmt, filepath.Join(dir, "authentication")))) assert.EqualError(t, errs[0], fmt.Sprintf(errFmtSecretIOIssue, filepath.Join(dir, "authentication"), "authentication_backend.ldap.password", fmt.Sprintf(errFmt, filepath.Join(dir, "authentication"))))
assert.EqualError(t, errs[1], fmt.Sprintf(errFmtSecretIOIssue, filepath.Join(dir, "duo"), "duo_api.secret_key", fmt.Sprintf(errFmt, filepath.Join(dir, "duo")))) assert.EqualError(t, errs[1], fmt.Sprintf(errFmtSecretIOIssue, filepath.Join(dir, "duo"), "duo_api.secret_key", fmt.Sprintf(errFmt, filepath.Join(dir, "duo"))))
assert.EqualError(t, errs[2], fmt.Sprintf(errFmtSecretIOIssue, filepath.Join(dir, "jwt"), "jwt_secret", fmt.Sprintf(errFmt, filepath.Join(dir, "jwt")))) assert.EqualError(t, errs[2], fmt.Sprintf(errFmtSecretIOIssue, filepath.Join(dir, "jwt"), "jwt_secret", fmt.Sprintf(errFmt, filepath.Join(dir, "jwt"))))

View File

@ -57,3 +57,8 @@ const (
// regexpHasScheme checks if a string has a scheme. Valid characters for schemes include alphanumeric, hyphen, // regexpHasScheme checks if a string has a scheme. Valid characters for schemes include alphanumeric, hyphen,
// period, and plus characters. // period, and plus characters.
var regexpHasScheme = regexp.MustCompile(`^[-+.a-zA-Z\d]+://`) var regexpHasScheme = regexp.MustCompile(`^[-+.a-zA-Z\d]+://`)
const (
blockCERTIFICATE = "CERTIFICATE"
blockRSAPRIVATEKEY = "RSA PRIVATE KEY"
)

View File

@ -1,6 +1,7 @@
package schema package schema
import ( import (
"crypto/rsa"
"net/url" "net/url"
"time" "time"
) )
@ -13,7 +14,8 @@ type IdentityProvidersConfiguration struct {
// OpenIDConnectConfiguration configuration for OpenID Connect. // OpenIDConnectConfiguration configuration for OpenID Connect.
type OpenIDConnectConfiguration struct { type OpenIDConnectConfiguration struct {
HMACSecret string `koanf:"hmac_secret"` HMACSecret string `koanf:"hmac_secret"`
IssuerPrivateKey string `koanf:"issuer_private_key"` IssuerCertificateChain X509CertificateChain `koanf:"issuer_certificate_chain"`
IssuerPrivateKey *rsa.PrivateKey `koanf:"issuer_private_key"`
AccessTokenLifespan time.Duration `koanf:"access_token_lifespan"` AccessTokenLifespan time.Duration `koanf:"access_token_lifespan"`
AuthorizeCodeLifespan time.Duration `koanf:"authorize_code_lifespan"` AuthorizeCodeLifespan time.Duration `koanf:"authorize_code_lifespan"`

View File

@ -18,6 +18,7 @@ var Keys = []string{
"log.file_path", "log.file_path",
"log.keep_stdout", "log.keep_stdout",
"identity_providers.oidc.hmac_secret", "identity_providers.oidc.hmac_secret",
"identity_providers.oidc.issuer_certificate_chain",
"identity_providers.oidc.issuer_private_key", "identity_providers.oidc.issuer_private_key",
"identity_providers.oidc.access_token_lifespan", "identity_providers.oidc.access_token_lifespan",
"identity_providers.oidc.authorize_code_lifespan", "identity_providers.oidc.authorize_code_lifespan",

View File

@ -1,11 +1,18 @@
package schema package schema
import ( import (
"crypto"
"crypto/ecdsa"
"crypto/ed25519"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"fmt" "fmt"
"net" "net"
"net/url" "net/url"
"strconv" "strconv"
"strings" "strings"
"time"
) )
// NewAddressFromString returns an *Address and error depending on the ability to parse the string as an Address. // NewAddressFromString returns an *Address and error depending on the ability to parse the string as an Address.
@ -99,3 +106,161 @@ func (a Address) HostPort() string {
func (a Address) Listener() (net.Listener, error) { func (a Address) Listener() (net.Listener, error) {
return net.Listen(a.Scheme, a.HostPort()) return net.Listen(a.Scheme, a.HostPort())
} }
// NewX509CertificateChain creates a new *X509CertificateChain from a given string, parsing each PEM block one by one.
func NewX509CertificateChain(in string) (chain *X509CertificateChain, err error) {
if in == "" {
return nil, nil
}
chain = &X509CertificateChain{
certs: []*x509.Certificate{},
}
data := []byte(in)
var (
block *pem.Block
cert *x509.Certificate
)
for {
block, data = pem.Decode(data)
if block == nil || len(block.Bytes) == 0 {
return nil, fmt.Errorf("invalid PEM block")
}
if block.Type != blockCERTIFICATE {
return nil, fmt.Errorf("the PEM data chain contains a %s but only certificates are expected", block.Type)
}
if cert, err = x509.ParseCertificate(block.Bytes); err != nil {
return nil, fmt.Errorf("the PEM data chain contains an invalid certificate: %w", err)
}
chain.certs = append(chain.certs, cert)
if len(data) == 0 {
break
}
}
return chain, nil
}
// X509CertificateChain is a helper struct that holds a list of *x509.Certificate's.
type X509CertificateChain struct {
certs []*x509.Certificate
}
// Thumbprint returns the Thumbprint for the first certificate.
func (c *X509CertificateChain) Thumbprint(hash crypto.Hash) []byte {
if len(c.certs) == 0 {
return nil
}
h := hash.New()
h.Write(c.certs[0].Raw)
return h.Sum(nil)
}
// HasCertificates returns true if the chain has any certificates.
func (c *X509CertificateChain) HasCertificates() (has bool) {
return len(c.certs) != 0
}
// Equal checks if the provided *x509.Certificate is equal to the first *x509.Certificate in the chain.
func (c *X509CertificateChain) Equal(other *x509.Certificate) (equal bool) {
if len(c.certs) == 0 {
return false
}
return c.certs[0].Equal(other)
}
// EqualKey checks if the provided key (public or private) has a public key equal to the first public key in this chain.
//
//nolint:gocyclo // This is an adequately clear function even with the complexity.
func (c *X509CertificateChain) EqualKey(other any) (equal bool) {
if len(c.certs) == 0 || other == nil {
return false
}
switch key := other.(type) {
case *rsa.PublicKey:
return key.Equal(c.certs[0].PublicKey)
case rsa.PublicKey:
return key.Equal(c.certs[0].PublicKey)
case *rsa.PrivateKey:
return key.PublicKey.Equal(c.certs[0].PublicKey)
case rsa.PrivateKey:
return key.PublicKey.Equal(c.certs[0].PublicKey)
case *ecdsa.PublicKey:
return key.Equal(c.certs[0].PublicKey)
case ecdsa.PublicKey:
return key.Equal(c.certs[0].PublicKey)
case *ecdsa.PrivateKey:
return key.PublicKey.Equal(c.certs[0].PublicKey)
case ecdsa.PrivateKey:
return key.PublicKey.Equal(c.certs[0].PublicKey)
case *ed25519.PublicKey:
return key.Equal(c.certs[0].PublicKey)
case ed25519.PublicKey:
return key.Equal(c.certs[0].PublicKey)
case *ed25519.PrivateKey:
switch pub := key.Public().(type) {
case *ed25519.PublicKey:
return pub.Equal(c.certs[0].PublicKey)
case ed25519.PublicKey:
return pub.Equal(c.certs[0].PublicKey)
default:
return false
}
case ed25519.PrivateKey:
switch pub := key.Public().(type) {
case *ed25519.PublicKey:
return pub.Equal(c.certs[0].PublicKey)
case ed25519.PublicKey:
return pub.Equal(c.certs[0].PublicKey)
default:
return false
}
default:
return false
}
}
// Certificates for this X509CertificateChain.
func (c *X509CertificateChain) Certificates() []*x509.Certificate {
return c.certs
}
// 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) {
n := len(c.certs)
now := time.Now()
for i, cert := range c.certs {
if !cert.NotBefore.IsZero() && cert.NotBefore.After(now) {
return fmt.Errorf("certificate #%d in chain is invalid before %d but the time is %d", i+1, cert.NotBefore.Unix(), now.Unix())
}
if cert.NotAfter.Before(now) {
return fmt.Errorf("certificate #%d in chain is invalid after %d but the time is %d", i+1, cert.NotAfter.Unix(), now.Unix())
}
if i+1 >= n {
break
}
if err = cert.CheckSignatureFrom(c.certs[i+1]); err != nil {
return fmt.Errorf("certificate #%d in chain is not signed properly by certificate #%d in chain: %w", i+1, i+2, err)
}
}
return nil
}

View File

@ -1,10 +1,17 @@
package schema package schema
import ( import (
"crypto"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"fmt"
"net" "net"
"regexp"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
) )
func TestNewAddressFromString(t *testing.T) { func TestNewAddressFromString(t *testing.T) {
@ -48,3 +55,499 @@ func TestNewAddressFromString(t *testing.T) {
}) })
} }
} }
func TestNewX509CertificateChain(t *testing.T) {
testCases := []struct {
name string
have string
thumbprintSHA256 string
err string
}{
{"ShouldParseCertificate", x509CertificateRSA,
"956e53c127b18143241fb75d2149369d41ad1e4094c40cedcef22e468b37886b", ""},
{"ShouldParseCertificateChain", x509CertificateRSA + "\n" + x509CACertificateRSA,
"956e53c127b18143241fb75d2149369d41ad1e4094c40cedcef22e468b37886b", ""},
{"ShouldNotParseInvalidCertificate", x509CertificateRSAInvalid, "",
"the PEM data chain contains an invalid certificate: x509: malformed certificate"},
{"ShouldNotParseInvalidCertificateBlock", x509CertificateRSAInvalidBlock, "", "invalid PEM block"},
{"ShouldNotParsePrivateKey", x509PrivateKeyRSA, "",
"the PEM data chain contains a RSA PRIVATE KEY but only certificates are expected"},
{"ShouldNotParseEmptyPEMBlock", x509CertificateEmpty, "", "invalid PEM block"},
{"ShouldNotParseEmptyData", "", "", ""},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
actual, err := NewX509CertificateChain(tc.have)
switch len(tc.err) {
case 0:
switch len(tc.have) {
case 0:
assert.Nil(t, actual)
default:
assert.NotNil(t, actual)
assert.Equal(t, tc.thumbprintSHA256, fmt.Sprintf("%x", actual.Thumbprint(crypto.SHA256)))
assert.True(t, actual.HasCertificates())
}
assert.NoError(t, err)
default:
assert.Nil(t, actual)
assert.EqualError(t, err, tc.err)
}
})
}
}
func TestX509CertificateChain(t *testing.T) {
chain := &X509CertificateChain{}
assert.Nil(t, chain.Thumbprint(crypto.SHA256))
assert.False(t, chain.HasCertificates())
assert.Len(t, chain.Certificates(), 0)
assert.False(t, chain.Equal(nil))
assert.False(t, chain.Equal(&x509.Certificate{}))
assert.False(t, chain.EqualKey(nil))
assert.False(t, chain.EqualKey(&rsa.PrivateKey{}))
cert := MustParseCertificate(x509CertificateRSA)
cacert := MustParseCertificate(x509CACertificateRSA)
chain = MustParseX509CertificateChain(x509CertificateRSA + "\n" + x509CACertificateRSA)
key := MustParseRSAPrivateKey(x509PrivateKeyRSA)
thumbprint := chain.Thumbprint(crypto.SHA256)
assert.NotNil(t, thumbprint)
assert.Equal(t, "956e53c127b18143241fb75d2149369d41ad1e4094c40cedcef22e468b37886b", fmt.Sprintf("%x", thumbprint))
assert.True(t, chain.Equal(cert))
assert.False(t, chain.Equal(cacert))
assert.True(t, chain.EqualKey(key))
assert.NoError(t, chain.Validate())
chain = MustParseX509CertificateChain(x509CertificateRSA + "\n" + x509CertificateRSA)
assert.EqualError(t, chain.Validate(), "certificate #1 in chain is not signed properly by certificate #2 in chain: x509: invalid signature: parent certificate cannot sign this kind of certificate")
chain = MustParseX509CertificateChain(x509CertificateRSAExpired + "\n" + x509CACertificateRSAExpired)
err := chain.Validate()
require.NotNil(t, err)
assert.Regexp(t, regexp.MustCompile(`^certificate #1 in chain is invalid after 31536000 but the time is \d+$`), err.Error())
chain = MustParseX509CertificateChain(x509CertificateRSANotBefore + "\n" + x509CACertificateRSAotBefore)
err = chain.Validate()
require.NotNil(t, err)
assert.Regexp(t, regexp.MustCompile(`^certificate #1 in chain is invalid before 13569465600 but the time is \d+$`), err.Error())
}
func MustParseX509CertificateChain(data string) *X509CertificateChain {
chain, err := NewX509CertificateChain(data)
if err != nil {
panic(err)
}
if chain == nil {
panic("nil chain")
}
return chain
}
func MustParseCertificate(data string) *x509.Certificate {
block, x := pem.Decode([]byte(data))
if block == nil {
panic("not pem")
}
if len(x) != 0 {
panic("extra data")
}
if block.Type != blockCERTIFICATE {
panic(fmt.Sprintf("not certifiate block: %s", block.Type))
}
cert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
panic(err)
}
return cert
}
func MustParseRSAPrivateKey(data string) *rsa.PrivateKey {
block, x := pem.Decode([]byte(data))
if block == nil {
panic("not pem")
}
if len(x) != 0 {
panic("extra data")
}
if block.Type != blockRSAPRIVATEKEY {
panic(fmt.Sprintf("not rsa private key block: %s", block.Type))
}
key, err := x509.ParsePKCS1PrivateKey(block.Bytes)
if err != nil {
panic(err)
}
return key
}
var (
// Valid from 2022 to 2122 (years).
x509CertificateRSA = `-----BEGIN CERTIFICATE-----
MIIC6DCCAdCgAwIBAgIRAIxvm0gFgsbh3D22rSZLuFQwDQYJKoZIhvcNAQELBQAw
EzERMA8GA1UEChMIQXV0aGVsaWEwIBcNMjIxMDAyMDAzMDQyWhgPMjEyMjA5MDgw
MDMwNDJaMBMxETAPBgNVBAoTCEF1dGhlbGlhMIIBIjANBgkqhkiG9w0BAQEFAAOC
AQ8AMIIBCgKCAQEAy71EOkV3jOpVQtVTH5HYcI4PryUCiAEyxAIuO+66gaAa4aCd
UCRr8iO/pt5nOwPxjPo+hMHhkcKpX7evj+wgYXAccpIQFSCYWTJkaXFL0jL7yFuE
5xpjgRM/x6FfK0IbN5WmVWO9EjesbyMCyDoYpjwzIrxnB70F9Y0nrXst1SnW/Sy0
01BQZNzD1tky1KDvEkw7L5mMPZFZMr5wV+ELvbo1LLvvrGYhhzbXWk7pPbxT0gAa
7yVvQbDKuCDqssAUyQa2JdlDaQocpldtK6l+dc3IsSWKd2UMouta75ngr9E1igy3
t7owMRqH8NjwKHt6KQeDVSdBnWNjG572vaRimQIDAQABozUwMzAOBgNVHQ8BAf8E
BAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADANBgkqhkiG
9w0BAQsFAAOCAQEAaZJ09yGa+DGr/iTqshGdPKNCtcf/CXCkL52xiI7DzLxDt30P
8vCuXXrrTGBY7eWYupcNy/MyqaUrz1ED+map3nQzZQBJ9vWIfr01B9phkg/WSaNJ
1DlYtbPYzr86BlGP1V5d3Wv6JqF3tkWHI0kI38CT68fWdDKrfa5j3JdZGIVJW+51
U0IE3Nqhfc76YzwQ3sNX5FT2Fr55RowH+l5OBPk0Bcztq58XmyPR/bvPfDASt8iS
DBT+0iiDiwk6LvOkasL8p7nuh5Grc9LMEYXY/QMUbkIWhIVRFlqyJA9s8vGHx1D4
96iYKudj+yvO17Szzr/NNmcwETbCs4j6P6QeiA==
-----END CERTIFICATE-----`
// Private Key for x509CertificateRSA.
x509PrivateKeyRSA = `-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAy71EOkV3jOpVQtVTH5HYcI4PryUCiAEyxAIuO+66gaAa4aCd
UCRr8iO/pt5nOwPxjPo+hMHhkcKpX7evj+wgYXAccpIQFSCYWTJkaXFL0jL7yFuE
5xpjgRM/x6FfK0IbN5WmVWO9EjesbyMCyDoYpjwzIrxnB70F9Y0nrXst1SnW/Sy0
01BQZNzD1tky1KDvEkw7L5mMPZFZMr5wV+ELvbo1LLvvrGYhhzbXWk7pPbxT0gAa
7yVvQbDKuCDqssAUyQa2JdlDaQocpldtK6l+dc3IsSWKd2UMouta75ngr9E1igy3
t7owMRqH8NjwKHt6KQeDVSdBnWNjG572vaRimQIDAQABAoIBAA/EhhM8bRQqzo5t
lBFNaELNu8kCRD/iV9tzj8BzqVt+2JW9qG8bYn9K5Po1HCglFfyjIVOE7cAqIJGX
1a59x8PCuXDkfPolm6TLkZnXeta5u2K2MoLwN+M1aio5AvSGGTUkD8tr/KX8SQwQ
2ZZFaML0xcBadF7U8jEey4NRlSp5/voiIAB+FrJHepZBz2XJYCX5s2vYLPMn+51R
1HyO0n2aQ9H1Na8aBjTfAp9GDKJWBV3bSM7cVaLGlMFj/HNXUNVnSsVsJj0tdWKz
K6r9zPskLnS+eNjCgqrOtZSqJ7M3PL0/PoTFPrr1Fevr+soKWCaPF94Ib94O9SEq
scvP3kECgYEA0HBdGab0HjcZgFtsIaMm+eBcDhUmUrvMPUw6FmspKnc8wplscONW
wrDGhR0dpT8+aAMD5jFC2pvyHjI5AWkW+53LB15j6SVzUlUMfS3VTwE2prLtDHDs
nCDW2+fXY2kjv45efZGpMGbLJVePx2RCPzUlAlc14lzxnHgpo7eho1cCgYEA+jpi
Eo/Jqa5CNd4hrXqFxZTFtU2Mn38ZKI3QK/l47/347yHLebjsYIIwJRoHenxWxNlz
Y+BZ38vkP+f9BGAVGiRcyMmIJU0X305wKwl26Y2Q/tEm2OpwmDboD2pL9byi9vfY
bz7pQGK/l9j86KofRwVJJRLsofPI1SsjnC8c448CgYAkpg0IjJ1RjriSJADwLSKW
PseQxlE1rMVtZbC07mSPjeWGBbnWY3KGytQs5YCn5GXRne4alEC/9Tlt68CwKc0b
spPXGNaSUL5lFIUcoWlm+bylNMKPNG+1x+RfR/VMCll5vcuJYooP85L2Xt3t3gfz
2yFFtxXHVjY5H7uaiJgIAwKBgQDvkGXEj5TqtsL8/6YOiHb6Kuz+Hzi6mtxjTyI2
d6mpWuWxTBGaf8kOvJWLb9gpFFGeNPGcdXaWJIZqCJjcT4Dkflu2f/uwepaYXGhX
S8Bk6fwfee5PTmRt1mNmHsaKhgcfmznDh9+YnPIBVuULe5RmUlEtBWk3xEZKj/qP
1Ss7UQKBgAwZQz+h5Z/XOJH3Qs5nJBKAZUiYkj3ux7G6tjx0cz7XcUYd/6enBpkY
JeqVHB6G+bMRLwb+Hc5Vgpbd5GdaUWo8udaghHgSGPUVcn0lK38XhYek6ACGz7Lo
xEfgtKoBlUq+uPb8H05HY0t9KybA3LA5wkRYYnJ17/nkZtrrJAmX
-----END RSA PRIVATE KEY-----`
// Valid from 2022 to 2122 (years).
x509CACertificateRSA = `-----BEGIN CERTIFICATE-----
MIIDBTCCAe2gAwIBAgIQAK/NIAl3Bdg4Xk0y/ZGL7jANBgkqhkiG9w0BAQsFADAT
MREwDwYDVQQKEwhBdXRoZWxpYTAgFw0yMjEwMDIwMDMwMjFaGA8yMTIyMDkwODAw
MzAyMVowEzERMA8GA1UEChMIQXV0aGVsaWEwggEiMA0GCSqGSIb3DQEBAQUAA4IB
DwAwggEKAoIBAQCg7jdO1HmydfkPzTtz57pvAS3YOdBT0hlNjJ4N2lrKhNnixrzK
+4R1dWQDP2SHbZQ0TskF8eQ8HhTr7AsApotTthJFkUgV2g+bv7wVroz0Hok5xtd4
bnpOvG3YUCP13Nk3ZVxdQXqR3/G3MrbyiXVPcgU+0giJ8EBykbtMu8L79/1iyk+m
w4fZfzTOeorRgspO3z3+pTAib2MCTA7bby1dX9qI/ysFPLdbJYfNQDxij8SzNLyJ
EkQ4kh3jKXf1VcZjbQTtYTZ3JJDqM08OxGMKuXUxPHd72Xlb+Fzql8LjYdEy/YKA
3r8FMf14lzcjvxtLnFXh//hiXh4+xgXMkrLZAgMBAAGjUzBRMA4GA1UdDwEB/wQE
AwICpDAPBgNVHSUECDAGBgRVHSUAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYE
FGKpXiZA+8VQyMBqTTep+dVTthSbMA0GCSqGSIb3DQEBCwUAA4IBAQAE4DJg+Rb4
iiocvxxQ85lhh94ql++E8MKuzIdN7ORs+ybUnsDD1WFDebubroTQuTSBkFrNuGNJ
8B7NZsHiWWLvNsrnxxeC5CicqfhSDti0rKWsbGyeoq7Kqok5E4pwOzeRsxL2e/Hm
G6LsUQuQMUG2vxKNynqmJS4VpgSVkiGhUfURFuRRDuRpVQ/XTl7jDIGf/ls7TAZq
1AnmnSi4Cqy4hrTnwYUYkFCcH69onUKAoaVNl1eAH7ogxakz32WyWObY98NBrjzA
I6VQlaQNSHtdFqDpu7NWJZZZSgN4BknbMYQEPNYCm701cPB4ahJbpg5C3pVPFSql
Bc9iI6nN3PCr
-----END CERTIFICATE-----`
/*
// Private Key for x509CACertificateRSA.
x509CAPrivateKeyRSA = `-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAoO43TtR5snX5D807c+e6bwEt2DnQU9IZTYyeDdpayoTZ4sa8
yvuEdXVkAz9kh22UNE7JBfHkPB4U6+wLAKaLU7YSRZFIFdoPm7+8Fa6M9B6JOcbX
eG56Trxt2FAj9dzZN2VcXUF6kd/xtzK28ol1T3IFPtIIifBAcpG7TLvC+/f9YspP
psOH2X80znqK0YLKTt89/qUwIm9jAkwO228tXV/aiP8rBTy3WyWHzUA8Yo/EszS8
iRJEOJId4yl39VXGY20E7WE2dySQ6jNPDsRjCrl1MTx3e9l5W/hc6pfC42HRMv2C
gN6/BTH9eJc3I78bS5xV4f/4Yl4ePsYFzJKy2QIDAQABAoIBADlsZw3Y4Ufdsq6B
w/oasLqVSB+EmaKfMGosh+VXidgDyZ+S3KDtWJl09uf1wdBVOHHlvvNBGfidn0eD
pXVo+AQ5zpFGQtuRQMqJgvqVmzQshTi5i/8sJLZdpDBwgDRlxphusaORDsRojV6a
WQ94HwTnIZoF5ggaU1TOTXAW+39er+3CAkHZlqeCHliSdVWdAPy2AGTOKqTP3Pko
owbHkuCw53oWsDB9N8zqdVF+UBht0sFOQ8tEHq0OY1HJRtPhfvcFno9rADsna/Tg
5m0sWUwP+uQ2+n18ahqunclzANu/w1SE+DVvXmeKdWMXEyuyL7muKsLgW5Ksa4jR
h6gAwBUCgYEAwmigbqAWrVhh5W1t388WcfsD0M7a4vMg7a3L5Im5mThgUHu3K5bM
EYLR8RnReEUdxdF86ASup4ddyVlz7z+YuQ9+XdTwMLK5Zujqfu9U/vsLHBc8h9eP
B7C6etfnTBfQeQryPaako8mixBpS0/pQhDhBHpWcYLLTh+uE17+C2U8CgYEA0+pe
EzWj7RHZiaHp6WWOqGuaCa3JR0uh2zns1gVx0cCdaCYFI+fOmrihrvBCYTXCiDZ4
k4HGD48i1R8nUsV6HJfw0yhR2UmB46TnFYb0RgzuJhXlXtlivYVbSDhyzl1NBafC
9zuCITMqGw921w/unOamMD94VSBfxT4EMBpvV1cCgYEAlhNuxfePigHQkOwJBd03
1oWQTIFjOA+4O8MOwz4OqNl8gKUAogWnQ11Z9GWZ7t5sPWmaowH6UhmNrQIBHZBa
tYHga08WnIFb3rWvUI4xbyUdTnIhqDwfjjA/xNUnGPbJWKe6mR0ru8TMgdZQWpPB
1FAY9SNJtNxXr3WA94w/1sECgYBu0cEgioyPDSaVsvZ/93wC10JWjWsUvZiG7GPO
CErdRb0LGdbWUALbJnJm6X3NGDACy3mCqfrJaDDvArutrVeOXGa0BgHHf4lNYo71
0v0rJNflUs4AK+5W7cYunlZrVJ9StchfQd9rPTZnsE6VaN9/bZ663HYxDh0HKMdH
4IsZQQKBgQCsBJ2bP9dc7QcqMXs8k37USIMs4nz6YWkV0Q74bBhbbnKIhY4mxhwf
5WMBUntxYQT0yyygA8q/wJrjiWI9dr+Le0VAtg4Wn8CW0bNYvnMySn2++2E8m6jM
DBXePomOtkIwEdLGcO8csBLYQKn4x/5ONpYI2QAifIB8Gdxqnp5clg==
-----END RSA PRIVATE KEY-----`
*/
// Valid from 1970 to 1971 (years).
x509CertificateRSAExpired = `-----BEGIN CERTIFICATE-----
MIIC5jCCAc6gAwIBAgIRAPKEPEnRO1hurtNAdEuDJA8wDQYJKoZIhvcNAQELBQAw
EzERMA8GA1UEChMIQXV0aGVsaWEwHhcNNzAwMTAxMDAwMDAwWhcNNzEwMTAxMDAw
MDAwWjATMREwDwYDVQQKEwhBdXRoZWxpYTCCASIwDQYJKoZIhvcNAQEBBQADggEP
ADCCAQoCggEBAN+qPAlnoqHMeBeXUF7qnaZXvHS4p8m2N9+hU8us6GYl3mYdFRDy
PGBYWewtIE0RsexBa7UYOV6IXdfheipsmRZZzUxjPbP/VfNuafxdZMVgQzWZZAtt
JJHRhLBATSfkutoPe3eUXxFonEhvl5ErU4327M9cZlLPRsIiVoTWOmigTT0jctx+
u/3IyEVtV982SpttYnpCZ9lCvaSgjpvf1Mim+dbGF0KPKitAbuFnNpWsbRzIYfiy
rGMvxuftkywJ/e6Lx34HJjq/4+K1qII9clIiwAxa1RTnLbBuSLzVHxmj3L5hQhap
jf7HMhLReW2XLJNw4xUShSKpvapBRGbly18CAwEAAaM1MDMwDgYDVR0PAQH/BAQD
AgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcN
AQELBQADggEBAE5cRfJFTGOozfa9vOedNjif6MLqCtnVv9SRy7k/pdz0KSpYfpRo
dPJ0kBIV2JwIVAXfge6SNn5DK/fyDzERq/kQaCzgBZ6//hCf6Y1BApGajLvOmhPx
KijvMtqQ8IZnqwrURN04pQCt936grsas3Y6haxNyfdJDnQ5FgTe6kDOJU+rXyLp1
N7H6hMXAd9+T++F50PM8AwtRZM6jSUVEhyrniKQSdkOQnXO5ng9if/7GntNzn56o
7cV3sBeenxEmvaXsR30C+A+Ankxr8HBlVOCYcJpbtsCmOB2PVRq9Q5KJAylHRWE1
JedOdWjWvrVaP2IqRopS9mV3Ckf1E19YWFg=
-----END CERTIFICATE-----`
/*
// Private Key for x509CertificateRSAExpired.
x509PrivateKeyRSAExpired = `-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEA36o8CWeiocx4F5dQXuqdple8dLinybY336FTy6zoZiXeZh0V
EPI8YFhZ7C0gTRGx7EFrtRg5Xohd1+F6KmyZFlnNTGM9s/9V825p/F1kxWBDNZlk
C20kkdGEsEBNJ+S62g97d5RfEWicSG+XkStTjfbsz1xmUs9GwiJWhNY6aKBNPSNy
3H67/cjIRW1X3zZKm21iekJn2UK9pKCOm9/UyKb51sYXQo8qK0Bu4Wc2laxtHMhh
+LKsYy/G5+2TLAn97ovHfgcmOr/j4rWogj1yUiLADFrVFOctsG5IvNUfGaPcvmFC
FqmN/scyEtF5bZcsk3DjFRKFIqm9qkFEZuXLXwIDAQABAoIBADBgYrHqD3wNfKAl
o0WUW1riOSnJ0sjHN9iPzU8NbArEABF4Etlie3qfQXva2tSwkho2oDRANBBlUF7k
LwdEC+yQqd3uzSbEgHOxmwzxql0ikAbk0YXDKpi7h4aTsdyCFYQauyrHFbTvOnZU
ZKUKiPz4vomvQ5Z/rJ9KzAnZSDLeqbJfBXPPitlE8DAiYypGKDUmX0unMJh/x0Pw
mIP/DTd+nMl+QpoSR0nS8r8Pr+4oBJ8K6k9Oni2DKdIW8IvoQJBBa9cm8Y0fHkSl
hB7fncY5bE0lOZ8jBlSNuGfZHjVihwBA+rYAcWpyzdBx3SHRSe5AH4RKBPaERgSt
SBV35PECgYEA7ayQs2SMggOiEK4Wf9AzywieaHiHa2ObJRg2dHlIgVkUDL3zB/b6
57jPMXAtMyGQDW6pZF6Oq3mgYP2A9alB6QKjpX1OGFmqZJxtRMAm0KPs2C2inWzg
dz9OW18jDlKKsHR00JktqsNgOZC8ldE2cyqgwBNXT/P9GyUMC9RmYhkCgYEA8Okk
9u8IoIHJEWbtmmh0d1CEmPz4zQosTgUl2GLbNaCE/zDvA82YUQmi1yaF1FHjXMoa
tD0Plkixoj/ezASeSE+duVpgXflYL4IHbqQq9JQg39vyaSuU1g3wP+hnmnCT62vb
z4v7ugDLLkSlvNeEQLR3GvZvInZnfdwI5/mjeDcCgYA1UGlhJGP0YjY/gZ2gbCbC
G5vVGXxfFYfeyVClzfL6uO2rcgyLM9bSlf08PMqW1qeGq9Upo6BjTLQyLYt5D8+u
Ih5tZ+9VvP9g9En6ixPp52ugjpQUtjCf7z53dp7ZfqCHtofhpwq8bHkwUIxNGxIY
wW4vx+blE3kqVqQeHzYcOQKBgBgBAv/fvVpQ1Dn5qX8THVeuHCgqPJghhVyYwraW
0wS648WRmJ8mYyDf9uu9GOSY7DCYqqR+2Qi+YYSrHIXzh9nopOyNBsEWUSUarabm
kKkiAUyM29CC2Sei5+dWPsxynyp76sD5T7Gu1o/boy/3wWO5F40GNPiYF6PAwtpq
U1FtAoGBANfr5OcnCIdtHLCEVRCaJdzTkQj5X1g9dF0D/gWkBIF0hibcs9yV2i1Z
JtxBrOvctkRsY7/C8dCms1gkfwDyTpKuMk9iDd3wfDGP3LdD1+V10pCm5ShHIGNm
/pRFpN45nR5iCX9mnvr8YJLUsrBkh7N4c4ao8xsXzOLBOk8WvtXL
-----END RSA PRIVATE KEY-----`
*/
// Valid from 1970 to 1971 (years).
x509CACertificateRSAExpired = `-----BEGIN CERTIFICATE-----
MIIDAzCCAeugAwIBAgIQB07G1WhPAiAHaM6FogkZ7TANBgkqhkiG9w0BAQsFADAT
MREwDwYDVQQKEwhBdXRoZWxpYTAeFw03MDAxMDEwMDAwMDBaFw03MTAxMDEwMDAw
MDBaMBMxETAPBgNVBAoTCEF1dGhlbGlhMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
MIIBCgKCAQEArly1jZXqLaAQJwTaoT0QaePV7SejQZj418Id7X7Gx1bgjmIS0mL8
058c8Av1ThRaUmVAMkbBc4MED9KnKKckU+qMBV0+2xovrn7TvAKHG3yLUtasn/Uz
2UzBXMgbCIMVCloWkPQ6BuSaNsZnSBmTj/IC16bAZwm5lIS9ZjzQ+QnZg8x5ftNR
Jx8Gar8xb4tQbhb6uZU5zxfuV1+4qk04lf6E48IVrf57NBXpJhdzxuvdBj0l46k3
zf44gybrXpr9O0n0Eb/H8lkIoIDM+vanoRvdg868QQw8C/r06M4E8gJJzZk7Ad2c
oCq66peom6eLUSo2DVfVmmQ2KjLUyW0L6QIDAQABo1MwUTAOBgNVHQ8BAf8EBAMC
AqQwDwYDVR0lBAgwBgYEVR0lADAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSQ
qzXTP6jV517cAknynvP0vr4ChzANBgkqhkiG9w0BAQsFAAOCAQEAGvX14cJM+uxh
rSBYa3qHEfSvlSwbQsCxZv5VXLJj6SLYUXqMLxCuPH16ZKD8MygmnF5/8Mp3ZIdF
Aesehtaoth1H06q6iXWw+wgT9fZKGIHL4RftvjFWncD+Lbk8hP/DkLqsGt+Mj23u
JAByhiG8QmbXu/X7kfXSvXjhQ7f7C+bNKxb03r7mT5gI8mCUp5MyLp3DPk3dKTwa
uby/wjlFMHi92HjfQ6mCn5ijc/ltiMh1wtXf53IEESYvrWV5ABjV8xnumI4j4idB
7yHjCn5id379go8e8a+W8jODNzUORzSio8tDhL4c13tiD4PzlMJ1tUr+CIoCzqB5
m99SvwJ74A==
-----END CERTIFICATE-----`
/*
// Private Key for x509CACertificateRSAExpired.
x509CAPrivateKeyRSAExpired = `-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEArly1jZXqLaAQJwTaoT0QaePV7SejQZj418Id7X7Gx1bgjmIS
0mL8058c8Av1ThRaUmVAMkbBc4MED9KnKKckU+qMBV0+2xovrn7TvAKHG3yLUtas
n/Uz2UzBXMgbCIMVCloWkPQ6BuSaNsZnSBmTj/IC16bAZwm5lIS9ZjzQ+QnZg8x5
ftNRJx8Gar8xb4tQbhb6uZU5zxfuV1+4qk04lf6E48IVrf57NBXpJhdzxuvdBj0l
46k3zf44gybrXpr9O0n0Eb/H8lkIoIDM+vanoRvdg868QQw8C/r06M4E8gJJzZk7
Ad2coCq66peom6eLUSo2DVfVmmQ2KjLUyW0L6QIDAQABAoIBAAiOZBpekO9MO36u
rkvbQ0Lu+0B4AXrmls9/pxhQcFC34q0aAvJwCRgZZsIg1BjQxt3kOhI9hqC0fS6J
l8pW6WF00QoyWTNHRa+6aYmAVkDzC6M1BaOT1MeFDLgQ2cLBK/cmFJVoZrCP50Fo
2wieuK8HoTwT4r0rrP+sw96QfXC7BjC1VSL9GXYemKz0RXEUvXXmzGGc9YE8vCt/
PXOb4TV30TIQrivkywSTJi8A1jUjYI2rPgo6JCl6GZGmc7hVX4jJ9lbBhUH76ozO
KS1Yzo/veWL4rVspc2exT5cuX7JIuFCjVi0Nlv1MKv39jpfTfKQh0ug6pHlxUzqX
Rl6Ln0ECgYEAwX0HtsmKMoSIrU4k90TKyhzjNoL8EaMKrSoZ4nK599igd8g+y6eD
jc1qO60lOHObyLPFor0rQT7K2KCD7GKYSX5+sh9leQEASl7cJkSAG17WBlrf9Pas
nUXjTRx3moEILAWmuov4UrYpkuEFk65d98xP3uPtDylFj57Bc+a8DOcCgYEA5rHK
qdjE8c0/1kgmItDJjmKxrJ5K5hY4w7SkpZeg4Rwr2WAjv3je/nx34D8S7m6R2uzp
NQYAAHXdzHt4iegupyW/3UXJboEscSTuC6/v3llawAozh02nDsMrdC1LreQ1IiFy
mKDmPZWxiAZXxEJ0hi0YMCcQnBY673eAleostq8CgYEAtia/mVvYh0Bv7z9O253e
jzFs0ce0B+KGzYiB/8XjvyknwDw6qbzUwy0romyZSrDDasma+F7AFtdHXXKXX3Ve
SmoUWhnmjGjd3iW5eSkptRqtwCPTDKkgzZqapuBy1Hg+ujrDwIC+0Rb+wnCmsGYJ
vpuQYZQPeyNugguBsVv5kucCgYBToTRE6k5LEgsIVVNt356RvXmHiELCsl+VotDl
Ltilgp7qyI1tBhZgzyJt6q+kO/UoFiZckHZDtHbZgBEsfT0cXvT09C2Xn8BKrAaX
ugoM4vuhDpGrhR0AnwQLs7fxq/8PBm0So5GT1cZr91Ct1yGC2qogGqlMzEpFMV8t
+ZyIBQKBgFyI2cZ3/uHQMkWUguml9w134bIGpqGdp8jf9YTvWs3Ax8/qxAVmFmtm
fot+QiamambblrhdT6pau11Mp06FzytorQH0qKd8mPAqvqtvcSoDZzqXjrkUnZIx
uLUcfb41clRhlfGDUj5MWimfi7d/Vakh86zAa8pg5WBCtXr0bZ/j
-----END RSA PRIVATE KEY-----`
*/
// Valid from 2400 to 2401 (years).
x509CertificateRSANotBefore = `-----BEGIN CERTIFICATE-----
MIIC6TCCAdGgAwIBAgIQYQWHYM90zNnwv22xhOPkszANBgkqhkiG9w0BAQsFADAT
MREwDwYDVQQKEwhBdXRoZWxpYTAiGA8yNDAwMDEwMTAwMDAwMFoYDzI0MDAxMjMx
MDAwMDAwWjATMREwDwYDVQQKEwhBdXRoZWxpYTCCASIwDQYJKoZIhvcNAQEBBQAD
ggEPADCCAQoCggEBAPFYYindnRfs8aJfXbaX5IRVj10uKlT4i0BwJ5IYaC4O/3UQ
km7do8lL2Ea2N2L5tQJhk2d+yoWGPeaUyuYP692jPA+4BW6RPuroSPB9WEU+x1ir
it/AzJtavg0Lu2fGZkXxAZJj2MlrXT7csaGwRAvvPEHS6EJW4UtERYIqfpKGB39I
sUhKNvY3edF9sosUAJmiZ8Q4K/uYoyCxyiE1QKLaiIjcZJxtzXkzwVBy1ZlmG+r5
VNNguQQFsS8f7uRlOmo0o3hDG9dByUn7PgFEExbgBtdmNoIPk/pfMFM8NIHK+wOC
q/SO2e/MX0IhJZXfq2VTZFgrisPovg8GpHSHRCkCAwEAAaM1MDMwDgYDVR0PAQH/
BAQDAgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDQYJKoZI
hvcNAQELBQADggEBADBTRbfg/UQeJpdMogm9tleXJBcHqgOgiBxkKYxGSlRg4vlr
tM8USAr24whLvb8KDhT6PaSY8wyPuCxqwqiKR84eselxOAcgDLV9n36OcWRm+oFl
Th1S1JUtjbrctU7i6pp4BwUmBwkVALrbrj+cGVG7uBfbP8L/onmjg9KkY5ttnbyb
Qi/Pv9zYLEo394hL3oeaphkP0iE6cHOvII/qFhnpLREINGp3g8V2I46Id/xYDi22
WRabgMuFGpel7Q26yh+YXyHoIkKdiOXMNNXTsuvp5EDBGybTIXWK3xrqTsREMVDr
EmiaOgL8c7+PpSWuUggJLb/JXDYnPtvekH3gPao=
-----END CERTIFICATE-----`
/*
// Private Key for x509CertificateRSANotBefore.
x509PrivateKeyRSANotBefore = `-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEA8VhiKd2dF+zxol9dtpfkhFWPXS4qVPiLQHAnkhhoLg7/dRCS
bt2jyUvYRrY3Yvm1AmGTZ37KhYY95pTK5g/r3aM8D7gFbpE+6uhI8H1YRT7HWKuK
38DMm1q+DQu7Z8ZmRfEBkmPYyWtdPtyxobBEC+88QdLoQlbhS0RFgip+koYHf0ix
SEo29jd50X2yixQAmaJnxDgr+5ijILHKITVAotqIiNxknG3NeTPBUHLVmWYb6vlU
02C5BAWxLx/u5GU6ajSjeEMb10HJSfs+AUQTFuAG12Y2gg+T+l8wUzw0gcr7A4Kr
9I7Z78xfQiElld+rZVNkWCuKw+i+DwakdIdEKQIDAQABAoIBACGcZHdeJK2bUv+A
9oUiXDHN1JxufHi+8G218NzYx1F6xzrfZvVHqrKy/FjEsav4CKxfOG8Wak/0JRTC
rgsiNn/0Zr3tq9v9IF0IonfTjQJ/vrVrlniY2iXcmlEozB2ktMOSz9w6SYurhx3l
EFvrN17OH38vRydOACxCQsfg8SWofY6SV0gcvlCcuM4lKBiuOBWGcf+xwIs3B+Bs
Frd282jRWtlcYd+zDE+vLxugNizLGpRKCMEdcKPRw9fkBKDI/f56WegNTUZYYFrV
LEmYIbOwMawvbi0mOdLsp27CfmeUjkEbwzgdNwjFrWIFAk0wT3QvDrKxDYDLM2Z3
+PtBMwECgYEA9ICYgzPMbN3CsQ+eWSQXXNk35V7PlMl1DC4UIHhi53BMT1ZhvkHf
D+eqXQ3BSqOUR7b417VBGkK8UtQuQXh9FwwVU0RhVkjpX0nTBhe8gGF3f3094rX4
Ckhm8XYQEWCUA9HNhCW+KSNVWqgw9Qi0awEY7HaiR628br39/EckvokCgYEA/LHI
HA9ixEBeTjds52rK6n9bPHeI87qxF62lLQYXvosyJij9/ruUfXwfJjG3EvCfcW7N
fr2EvgzPbCozC1V6gI9k5CXhOsf+wD6M8A7g7YHUa/dPq2B8bfqaMD0vW7OoZiLQ
NpfMtBvZxd1wukPGypLGWabPLo8u6bSfxTqz8KECgYAudnmFBUTls0aaKyOmQOuH
o2ex2NCNr7Lke6UrfnUdEgQOV5X/d7kR5q5DPKfsrSUyc5zaMQGMIf5zpwqbOnBa
/trWlfoBUZ23k+ncEIqrwtnYik5GVNor6hJV9F+dTcMS7r2lTR7T5nkD305eYicW
5oB7/xdbk7JpQQWQ+VwMMQKBgQC3hbKs1mvH1mvnaI+aftACgR5VCweW4/bsGHwG
+A7Unyl713eowrk0ban9xkuM4N8btfpe2uuGT61xhDBwQdNnfT0sCWrLkyasnoEj
c9reA9Wv1/yvnbKg+Ul0UWuMsS1TiGMp0xOjlzqRXqMZVFITG4gc4m5EBU9wAnOq
/VhkIQKBgBUwpoL+K1OswQKV/SHH7n5b/By02mLuMkQR0NlpWRJY6eTDk3FAUkHn
+T996U0a+7OY+mATVqrfLBcsa6i+HGpb4jZL+kkdtfmtHUnN8YOAwopWp3uoPRmF
lpakAQq8NPVcrX6PLDkHeOlKhE4ercYFqcCRKcnMB3nD+re6x5l6
-----END RSA PRIVATE KEY-----`
*/
// Valid from 2400 to 2401 (years).
x509CACertificateRSAotBefore = `-----BEGIN CERTIFICATE-----
MIIDBzCCAe+gAwIBAgIQeR2/TbyH9gEzyjuTijMGVzANBgkqhkiG9w0BAQsFADAT
MREwDwYDVQQKEwhBdXRoZWxpYTAiGA8yNDAwMDEwMTAwMDAwMFoYDzI0MDAxMjMx
MDAwMDAwWjATMREwDwYDVQQKEwhBdXRoZWxpYTCCASIwDQYJKoZIhvcNAQEBBQAD
ggEPADCCAQoCggEBAJ7vpRSDXVvwOLGmjbZdoG25OdsWgmhVAWpFCUifotqorz+z
+VgwPnvVDp1cTp07y+mDJK++GNBOG1pS5G4or6Y1HAlT3nGpE0FYhrtDBQmhvqV0
6mPM/Dq5JIuGiju4LX0KBlaFJugJevw3ySnoPUu0BQ9mTZUgggNwetqsAX7TioFj
TkVIMtgranigOvWjJQyLlmiK1TOgMqgWYNR20SE4CkmIp9SjOdeW7kNVMOojRx9a
VgElA2TN57/Je+/tLbvTDDCP9o59SIXxn5N6JQ2/XdDZPNBHrxnmchQVDXWCkP0A
gkV7V1dl8ur85iEdN+F31Kvd0nzCCaC3YUMxgmcCAwEAAaNTMFEwDgYDVR0PAQH/
BAQDAgKkMA8GA1UdJQQIMAYGBFUdJQAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4E
FgQUB4/oEXJWhMwKBpMesSOnQdD+G0UwDQYJKoZIhvcNAQELBQADggEBAIlzOnWB
xIhMm3zpLfpJGBi62d9J7Rlf5NitWoztyHdJpQ9y99s67QonR7UY7n11PMdtsLja
hWy1/iZ1o5F2zGKPzSS8pscdIuo4B+TocLiHkEx7ttyQ0MepoDt1RlTOjqilqbfD
A4GyGidns1VZuH8wP8NpZNlWajsXgvkYT433RzPgKe7qoI3DFQwc72SBuZSHHyjE
9SVgdN0KmfFXMum4BurwftelF1etGR+4II3cDG80CH2ZvYdqCURPoa+ny/qqMtzq
W2CnwP59TrotQgKCFJS5EdL3MXaZSvK9z2LERdxDvp4OSoJYoxSMJawfkVwZ15rk
apA21VwIrpFg54A=
-----END CERTIFICATE-----`
/*
// Private Key for x509CACertificateRSAotBefore.
x509CAPrivateKeyRSANotBefore = `-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAnu+lFINdW/A4saaNtl2gbbk52xaCaFUBakUJSJ+i2qivP7P5
WDA+e9UOnVxOnTvL6YMkr74Y0E4bWlLkbiivpjUcCVPecakTQViGu0MFCaG+pXTq
Y8z8Orkki4aKO7gtfQoGVoUm6Al6/DfJKeg9S7QFD2ZNlSCCA3B62qwBftOKgWNO
RUgy2CtqeKA69aMlDIuWaIrVM6AyqBZg1HbRITgKSYin1KM515buQ1Uw6iNHH1pW
ASUDZM3nv8l77+0tu9MMMI/2jn1IhfGfk3olDb9d0Nk80EevGeZyFBUNdYKQ/QCC
RXtXV2Xy6vzmIR034XfUq93SfMIJoLdhQzGCZwIDAQABAoIBAEneATBOeYZwWDkg
un5Gd3hnfN85T/SjhVvZqB3rq6nKemC2Ca4WBgRRmlBChXsIPpZR0CwpwqiVlJrf
KbGVEUXDKzuekiTrOrrFJSFFXcMDPHLzqrglnhjA0Z5TMk3dJK8XiKiPi+yN823j
k4f5mvtjOHLWzjn/+M0WatLU3IEPnqpnE+pEKrkZQa7Mg+xHprvt67Q4aCgP5lfy
A04eoUo7+TMRsK718vb02E81ZQSLgSbQMd0W8Dkt7vRkYRNL0OKBlPQcP9qZlw5s
swy4ne9jgmJKY3mmdnURTjvdJb20dUsSSzseZ8Tj6UYUDntXrB62YhZvC0ZRhGY/
Nnf10IECgYEAzSi1l1G6ZblV2g2jPqqD4EsdUvitnN5t592dw52+SyizNj7j+pLt
OPi+bt5HW4orHQHlPi6wt1BQIZ7UVHmljKQq7ByxOX0SRrRg428JPlFMjCp3bG1t
zmRQwADGkfqn3JQcmY9VDjtn5oCx18bNpDR1gNiK6zImK/jmWBOiaKcCgYEAxlKN
vYeG70ZrtVBz6jmX6yOtN8/hBA0mifZVHmkKX6earU3Ds2Uj2Hg+AgMqoFD2aRSp
wodEYzV6hSpvdLzqBi6SklnfF4NBqJ51TEFBWaVMUZjTwConOcc+vvvSDbCkmnoF
yTcqVm2p91HD7859ACcO+m8nsFJGldbJFl8RkEECgYEAkSLajEkyH2Kk3JTHRr7k
eplJDniEgbRNdjmusUN36r3JQnftWkf08FfwiIhRXO37IBNGNN5c/+IePhqZxYUl
W8CL6OtHaQ8VDdXvsRXNKTvkdkhYoeksRFVtVtd1orH7bK2PKgdfOalHEKc8qRSo
SCEge101sbuRi4wSkH6bZ4MCgYAerDXv0j40U5fk+wRyfWXZoDLyJtyOW9pSDB8u
DODl2m45z4UtAb+Bg1dTyFmXYe46Yk+/HlydW3APmHiUfYNUYW+Z4vx2Dn7hLWDG
4nDRBJfBJvnZBqv6a65wq1HZfDB5E9ZBQJ7zrxJShfrf4/fBRkkywm5I/vCbzBRd
uWZmAQKBgQCJE5rx3rWZz+srunmmkg5LXBMveD+1HRvlwlj/gELG2b2ytNUmnVey
2naApHnvW7lZdrADpbzLKGEDB/EsaIPJjqQw45OoIZwPdM4bm0/w5c1ZLnStXGCz
Th/7Sva6x6FW7tHY6ldqybcMj8w3kA4ByQEOg2BtPnWTm1NX/qcr8Q==
-----END RSA PRIVATE KEY-----`
*/
x509CertificateRSAInvalid = `-----BEGIN CERTIFICATE-----
mIIC5jCCAc6gAwIBAgIRAK4Sj7FiN6PXo/urpfO4E7owDQYJKoZIhvcNAQELBQAw
EzERMA8GA1UEChMIQXV0aGVsaWEwHhcNNzAwMTAxMDAwMDAwWhcNNzEwMTAxMDAw
MDAwWjATMREwDwYDVQQKEwhBdXRoZWxpYTCCASIwDQYJKoZIhvcNAQEBBQADggEP
ADCCAQoCggEBAPKv3pSyP4ozGEiVLJ14dIwFCEGEgq7WUMI0SZZqQA2ID0L59U/Q
/Usyy7uC9gfMUzODTpANtkOjFQcQAsxlR1FOjVBrX5QgjSvXwbQn3DtwMA7XWSl6
LuYx2rBYSlMSN5UZQm/RxMtXfLK2b51WgEEYDFi+nECSqKzR4R54eOPkBEWRfvuY
91AMjlhpivg8e4JWkq4LVQUKbmiFYwIdK8XQiN4blY9WwXwJFYs5sQ/UYMwBFi0H
kWOh7GEjfxgoUOPauIueZSMSlQp7zqAH39n0ZSYb6cS0Npj57QoWZSY3ak87ebcR
Nf4rCvZLby7LoN7qYCKxmCaDD3x2+NYpWH8CAwEAAaM1MDMwDgYDVR0PAQH/BAQD
AgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcN
AQELBQADggEBAHSITqIQSNzonFl3DzxHPEzr2hp6peo45buAAtu8FZHoA+U7Icfh
/ZXjPg7Xz+hgFwM/DTNGXkMWacQA/PaNWvZspgRJf2AXvNbMSs2UQODr7Tbv+Fb4
lyblmMUNYFMCFVAMU0eIxXAFq2qcwv8UMcQFT0Z/35s6PVOakYnAGGQjTfp5Ljuq
wsdc/xWmM0cHWube6sdRRUD7SY20KU/kWzl8iFO0VbSSrDf1AlEhnLEkp1SPaxXg
OdBnl98MeoramNiJ7NT6Jnyb3zZ578fjaWfThiBpagItI8GZmG4s4Ovh2JbheN8i
ZsjNr9jqHTjhyLVbDRlmJzcqoj4JhbKs6/I=
-----END CERTIFICATE-----`
x509CertificateRSAInvalidBlock = `-----BEGIN CERTIFICATE-----
MIIC5jCCAc6gAwIBAgIRAK4Sj7FiN6PXo/urPfO4E7owDQYJKoZIhvcNAQELBQAw
EzERMA8GA1UEChMIQXV0aGVsaWEwHhcNNzAwMTAxMDAwMDAwWhcNNzEwMTAxMDAw
MDAwWjATMREwDwYDVQQKEwhBdXRoZWxpYTCCASIwDQYJKoZIhvcNAQEBBQADggEP
ADCCAQoCggEBAPKv3pSyP4ozGEiVLJ14dIWFCEGEgq7WUMI0SZZqQA2ID0L59U/Q
/Usyy7uC9gfMUzODTpANtkOjFQcQAsxlR1FOjVBrX5QgjSvXwbQn3DtwMA7XWSl6
LuYx2rBYSlMSN5UZQm/RxMtXf^K2b51WgEEYDFi+nECSqKzR4R54eOPkBEWRfvuY
91AMjlhpivg8e4JWkq4LVQUKbmiFYwIdK8XQiN4blY9WwXwJFYs5sQ/UYMwBFi0H
kWOh7GEjfxgoUOPauIueZSMSlQp7zqAH39N0ZSYb6cS0Npj57QoWZSY3ak87ebcR
Nf4rCvZLby7LoN7qYCKxmCaDD3x2+NYpWH8CAwEAAaM1MDMwDgYDVR0PAQH/BAQD
AgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcN
AQELBQADggEBAHSITqIQSNzonFl3DzxHPEzr2hp6peo45buAAtu8FZHoA+U7Icfh
/ZXjPg7Xz+hgFwM/DTNGXkMWacQA/PaNWvZspgRJf2AXvNbMSs2UQODr7Tbv+Fb4
lyblmMUNYFMCFVAMU0eIxXAFq2qcwv8UMcQFT0Z/35s6PVOakYnAGGQjTfp5Ljuq
wsdc/xWmM0cHWube6sdRRUD7SY20KU/kWzl8iFO0VbSSrDf1AlEhnLEkp1SPaxXg
OdBnl98MeoramNiJ7NT6Jnyb3zZ578fjaWfThiBpagItI8GZmG4s4Ovh2JbheN8i
ZsjNr9jqHTjhyLVbDRlmJzcqoj4JhbKs6/I=
-----END CERTIFICATE-----`
x509CertificateEmpty = `-----BEGIN CERTIFICATE-----
-----END CERTIFICATE-----`
)

View File

@ -132,6 +132,8 @@ const (
errFmtOIDCNoClientsConfigured = "identity_providers: oidc: option 'clients' must have one or " + errFmtOIDCNoClientsConfigured = "identity_providers: oidc: option 'clients' must have one or " +
"more clients configured" "more clients configured"
errFmtOIDCNoPrivateKey = "identity_providers: oidc: option 'issuer_private_key' is required" errFmtOIDCNoPrivateKey = "identity_providers: oidc: option 'issuer_private_key' is required"
errFmtOIDCCertificateMismatch = "identity_providers: oidc: option 'issuer_private_key' does not appear to be the private key the certificate provided by option 'issuer_certificate_chain'"
errFmtOIDCCertificateChain = "identity_providers: oidc: option 'issuer_certificate_chain' produced an error during validation of the chain: %w"
errFmtOIDCEnforcePKCEInvalidValue = "identity_providers: oidc: option 'enforce_pkce' must be 'never', " + errFmtOIDCEnforcePKCEInvalidValue = "identity_providers: oidc: option 'enforce_pkce' must be 'never', " +
"'public_clients_only' or 'always', but it is configured as '%s'" "'public_clients_only' or 'always', but it is configured as '%s'"
@ -294,9 +296,11 @@ var validSessionSameSiteValues = []string{"none", "lax", "strict"}
var validLoLevels = []string{"trace", "debug", "info", "warn", "error"} var validLoLevels = []string{"trace", "debug", "info", "warn", "error"}
var validWebauthnConveyancePreferences = []string{string(protocol.PreferNoAttestation), string(protocol.PreferIndirectAttestation), string(protocol.PreferDirectAttestation)} var validWebauthnConveyancePreferences = []string{string(protocol.PreferNoAttestation), string(protocol.PreferIndirectAttestation), string(protocol.PreferDirectAttestation)}
var validWebauthnUserVerificationRequirement = []string{string(protocol.VerificationDiscouraged), string(protocol.VerificationPreferred), string(protocol.VerificationRequired)} var validWebauthnUserVerificationRequirement = []string{string(protocol.VerificationDiscouraged), string(protocol.VerificationPreferred), string(protocol.VerificationRequired)}
var validRFC7231HTTPMethodVerbs = []string{"GET", "HEAD", "POST", "PUT", "PATCH", "DELETE", "TRACE", "CONNECT", "OPTIONS"} var validRFC7231HTTPMethodVerbs = []string{"GET", "HEAD", "POST", "PUT", "PATCH", "DELETE", "TRACE", "CONNECT", "OPTIONS"}
var validRFC4918HTTPMethodVerbs = []string{"COPY", "LOCK", "MKCOL", "MOVE", "PROPFIND", "PROPPATCH", "UNLOCK"} var validRFC4918HTTPMethodVerbs = []string{"COPY", "LOCK", "MKCOL", "MOVE", "PROPFIND", "PROPPATCH", "UNLOCK"}
var validACLHTTPMethodVerbs = append(validRFC7231HTTPMethodVerbs, validRFC4918HTTPMethodVerbs...) var validACLHTTPMethodVerbs = append(validRFC7231HTTPMethodVerbs, validRFC4918HTTPMethodVerbs...)
@ -306,9 +310,13 @@ var validACLRulePolicies = []string{policyBypass, policyOneFactor, policyTwoFact
var validDefault2FAMethods = []string{"totp", "webauthn", "mobile_push"} var validDefault2FAMethods = []string{"totp", "webauthn", "mobile_push"}
var validOIDCScopes = []string{oidc.ScopeOpenID, oidc.ScopeEmail, oidc.ScopeProfile, oidc.ScopeGroups, oidc.ScopeOfflineAccess} var validOIDCScopes = []string{oidc.ScopeOpenID, oidc.ScopeEmail, oidc.ScopeProfile, oidc.ScopeGroups, oidc.ScopeOfflineAccess}
var validOIDCGrantTypes = []string{"implicit", "refresh_token", "authorization_code", "password", "client_credentials"} var validOIDCGrantTypes = []string{"implicit", "refresh_token", "authorization_code", "password", "client_credentials"}
var validOIDCResponseModes = []string{"form_post", "query", "fragment"} var validOIDCResponseModes = []string{"form_post", "query", "fragment"}
var validOIDCUserinfoAlgorithms = []string{"none", "RS256"} var validOIDCUserinfoAlgorithms = []string{"none", "RS256"}
var validOIDCCORSEndpoints = []string{oidc.AuthorizationEndpoint, oidc.TokenEndpoint, oidc.IntrospectionEndpoint, oidc.RevocationEndpoint, oidc.UserinfoEndpoint} var validOIDCCORSEndpoints = []string{oidc.AuthorizationEndpoint, oidc.TokenEndpoint, oidc.IntrospectionEndpoint, oidc.RevocationEndpoint, oidc.UserinfoEndpoint}
var reKeyReplacer = regexp.MustCompile(`\[\d+]`) var reKeyReplacer = regexp.MustCompile(`\[\d+]`)

View File

@ -16,11 +16,42 @@ func ValidateIdentityProviders(config *schema.IdentityProvidersConfiguration, va
} }
func validateOIDC(config *schema.OpenIDConnectConfiguration, validator *schema.StructValidator) { func validateOIDC(config *schema.OpenIDConnectConfiguration, validator *schema.StructValidator) {
if config != nil { if config == nil {
if config.IssuerPrivateKey == "" { return
validator.Push(fmt.Errorf(errFmtOIDCNoPrivateKey))
} }
setOIDCDefaults(config)
if config.IssuerPrivateKey == nil {
validator.Push(fmt.Errorf(errFmtOIDCNoPrivateKey))
} else if config.IssuerCertificateChain.HasCertificates() {
if !config.IssuerCertificateChain.EqualKey(config.IssuerPrivateKey) {
validator.Push(fmt.Errorf(errFmtOIDCCertificateMismatch))
}
if err := config.IssuerCertificateChain.Validate(); err != nil {
validator.Push(fmt.Errorf(errFmtOIDCCertificateChain, err))
}
}
if config.MinimumParameterEntropy != 0 && config.MinimumParameterEntropy < 8 {
validator.PushWarning(fmt.Errorf(errFmtOIDCServerInsecureParameterEntropy, config.MinimumParameterEntropy))
}
if config.EnforcePKCE != "never" && config.EnforcePKCE != "public_clients_only" && config.EnforcePKCE != "always" {
validator.Push(fmt.Errorf(errFmtOIDCEnforcePKCEInvalidValue, config.EnforcePKCE))
}
validateOIDCOptionsCORS(config, validator)
if len(config.Clients) == 0 {
validator.Push(fmt.Errorf(errFmtOIDCNoClientsConfigured))
} else {
validateOIDCClients(config, validator)
}
}
func setOIDCDefaults(config *schema.OpenIDConnectConfiguration) {
if config.AccessTokenLifespan == time.Duration(0) { if config.AccessTokenLifespan == time.Duration(0) {
config.AccessTokenLifespan = schema.DefaultOpenIDConnectConfiguration.AccessTokenLifespan config.AccessTokenLifespan = schema.DefaultOpenIDConnectConfiguration.AccessTokenLifespan
} }
@ -37,25 +68,9 @@ func validateOIDC(config *schema.OpenIDConnectConfiguration, validator *schema.S
config.RefreshTokenLifespan = schema.DefaultOpenIDConnectConfiguration.RefreshTokenLifespan config.RefreshTokenLifespan = schema.DefaultOpenIDConnectConfiguration.RefreshTokenLifespan
} }
if config.MinimumParameterEntropy != 0 && config.MinimumParameterEntropy < 8 {
validator.PushWarning(fmt.Errorf(errFmtOIDCServerInsecureParameterEntropy, config.MinimumParameterEntropy))
}
if config.EnforcePKCE == "" { if config.EnforcePKCE == "" {
config.EnforcePKCE = schema.DefaultOpenIDConnectConfiguration.EnforcePKCE config.EnforcePKCE = schema.DefaultOpenIDConnectConfiguration.EnforcePKCE
} }
if config.EnforcePKCE != "never" && config.EnforcePKCE != "public_clients_only" && config.EnforcePKCE != "always" {
validator.Push(fmt.Errorf(errFmtOIDCEnforcePKCEInvalidValue, config.EnforcePKCE))
}
validateOIDCOptionsCORS(config, validator)
validateOIDCClients(config, validator)
if len(config.Clients) == 0 {
validator.Push(fmt.Errorf(errFmtOIDCNoClientsConfigured))
}
}
} }
func validateOIDCOptionsCORS(config *schema.OpenIDConnectConfiguration, validator *schema.StructValidator) { func validateOIDCOptionsCORS(config *schema.OpenIDConnectConfiguration, validator *schema.StructValidator) {

View File

@ -1,6 +1,9 @@
package validator package validator
import ( import (
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"errors" "errors"
"fmt" "fmt"
"net/url" "net/url"
@ -20,7 +23,6 @@ func TestShouldRaiseErrorWhenInvalidOIDCServerConfiguration(t *testing.T) {
config := &schema.IdentityProvidersConfiguration{ config := &schema.IdentityProvidersConfiguration{
OIDC: &schema.OpenIDConnectConfiguration{ OIDC: &schema.OpenIDConnectConfiguration{
HMACSecret: "abc", HMACSecret: "abc",
IssuerPrivateKey: "",
}, },
} }
@ -37,7 +39,7 @@ func TestShouldNotRaiseErrorWhenCORSEndpointsValid(t *testing.T) {
config := &schema.IdentityProvidersConfiguration{ config := &schema.IdentityProvidersConfiguration{
OIDC: &schema.OpenIDConnectConfiguration{ OIDC: &schema.OpenIDConnectConfiguration{
HMACSecret: "rLABDrx87et5KvRHVUgTm3pezWWd8LMN", HMACSecret: "rLABDrx87et5KvRHVUgTm3pezWWd8LMN",
IssuerPrivateKey: "key-material", IssuerPrivateKey: &rsa.PrivateKey{},
CORS: schema.OpenIDConnectCORSConfiguration{ CORS: schema.OpenIDConnectCORSConfiguration{
Endpoints: []string{oidc.AuthorizationEndpoint, oidc.TokenEndpoint, oidc.IntrospectionEndpoint, oidc.RevocationEndpoint, oidc.UserinfoEndpoint}, Endpoints: []string{oidc.AuthorizationEndpoint, oidc.TokenEndpoint, oidc.IntrospectionEndpoint, oidc.RevocationEndpoint, oidc.UserinfoEndpoint},
}, },
@ -60,7 +62,7 @@ func TestShouldRaiseErrorWhenCORSEndpointsNotValid(t *testing.T) {
config := &schema.IdentityProvidersConfiguration{ config := &schema.IdentityProvidersConfiguration{
OIDC: &schema.OpenIDConnectConfiguration{ OIDC: &schema.OpenIDConnectConfiguration{
HMACSecret: "rLABDrx87et5KvRHVUgTm3pezWWd8LMN", HMACSecret: "rLABDrx87et5KvRHVUgTm3pezWWd8LMN",
IssuerPrivateKey: "key-material", IssuerPrivateKey: &rsa.PrivateKey{},
CORS: schema.OpenIDConnectCORSConfiguration{ CORS: schema.OpenIDConnectCORSConfiguration{
Endpoints: []string{oidc.AuthorizationEndpoint, oidc.TokenEndpoint, oidc.IntrospectionEndpoint, oidc.RevocationEndpoint, oidc.UserinfoEndpoint, "invalid_endpoint"}, Endpoints: []string{oidc.AuthorizationEndpoint, oidc.TokenEndpoint, oidc.IntrospectionEndpoint, oidc.RevocationEndpoint, oidc.UserinfoEndpoint, "invalid_endpoint"},
}, },
@ -85,7 +87,7 @@ func TestShouldRaiseErrorWhenOIDCPKCEEnforceValueInvalid(t *testing.T) {
config := &schema.IdentityProvidersConfiguration{ config := &schema.IdentityProvidersConfiguration{
OIDC: &schema.OpenIDConnectConfiguration{ OIDC: &schema.OpenIDConnectConfiguration{
HMACSecret: "rLABDrx87et5KvRHVUgTm3pezWWd8LMN", HMACSecret: "rLABDrx87et5KvRHVUgTm3pezWWd8LMN",
IssuerPrivateKey: "key-material", IssuerPrivateKey: &rsa.PrivateKey{},
EnforcePKCE: "invalid", EnforcePKCE: "invalid",
}, },
} }
@ -104,7 +106,7 @@ func TestShouldRaiseErrorWhenOIDCCORSOriginsHasInvalidValues(t *testing.T) {
config := &schema.IdentityProvidersConfiguration{ config := &schema.IdentityProvidersConfiguration{
OIDC: &schema.OpenIDConnectConfiguration{ OIDC: &schema.OpenIDConnectConfiguration{
HMACSecret: "rLABDrx87et5KvRHVUgTm3pezWWd8LMN", HMACSecret: "rLABDrx87et5KvRHVUgTm3pezWWd8LMN",
IssuerPrivateKey: "key-material", IssuerPrivateKey: &rsa.PrivateKey{},
CORS: schema.OpenIDConnectCORSConfiguration{ CORS: schema.OpenIDConnectCORSConfiguration{
AllowedOrigins: utils.URLsFromStringSlice([]string{"https://example.com/", "https://site.example.com/subpath", "https://site.example.com?example=true", "*"}), AllowedOrigins: utils.URLsFromStringSlice([]string{"https://example.com/", "https://site.example.com/subpath", "https://site.example.com?example=true", "*"}),
AllowedOriginsFromClientRedirectURIs: true, AllowedOriginsFromClientRedirectURIs: true,
@ -140,7 +142,7 @@ func TestShouldRaiseErrorWhenOIDCServerNoClients(t *testing.T) {
config := &schema.IdentityProvidersConfiguration{ config := &schema.IdentityProvidersConfiguration{
OIDC: &schema.OpenIDConnectConfiguration{ OIDC: &schema.OpenIDConnectConfiguration{
HMACSecret: "rLABDrx87et5KvRHVUgTm3pezWWd8LMN", HMACSecret: "rLABDrx87et5KvRHVUgTm3pezWWd8LMN",
IssuerPrivateKey: "key-material", IssuerPrivateKey: &rsa.PrivateKey{},
}, },
} }
@ -320,7 +322,7 @@ func TestShouldRaiseErrorWhenOIDCServerClientBadValues(t *testing.T) {
config := &schema.IdentityProvidersConfiguration{ config := &schema.IdentityProvidersConfiguration{
OIDC: &schema.OpenIDConnectConfiguration{ OIDC: &schema.OpenIDConnectConfiguration{
HMACSecret: "rLABDrx87et5KvRHVUgTm3pezWWd8LMN", HMACSecret: "rLABDrx87et5KvRHVUgTm3pezWWd8LMN",
IssuerPrivateKey: "key-material", IssuerPrivateKey: &rsa.PrivateKey{},
Clients: tc.Clients, Clients: tc.Clients,
}, },
} }
@ -344,7 +346,7 @@ func TestShouldRaiseErrorWhenOIDCClientConfiguredWithBadScopes(t *testing.T) {
config := &schema.IdentityProvidersConfiguration{ config := &schema.IdentityProvidersConfiguration{
OIDC: &schema.OpenIDConnectConfiguration{ OIDC: &schema.OpenIDConnectConfiguration{
HMACSecret: "rLABDrx87et5KvRHVUgTm3pezWWd8LMN", HMACSecret: "rLABDrx87et5KvRHVUgTm3pezWWd8LMN",
IssuerPrivateKey: "key-material", IssuerPrivateKey: &rsa.PrivateKey{},
Clients: []schema.OpenIDConnectClientConfiguration{ Clients: []schema.OpenIDConnectClientConfiguration{
{ {
ID: "good_id", ID: "good_id",
@ -370,7 +372,7 @@ func TestShouldRaiseErrorWhenOIDCClientConfiguredWithBadGrantTypes(t *testing.T)
config := &schema.IdentityProvidersConfiguration{ config := &schema.IdentityProvidersConfiguration{
OIDC: &schema.OpenIDConnectConfiguration{ OIDC: &schema.OpenIDConnectConfiguration{
HMACSecret: "rLABDrx87et5KvRHVUgTm3pezWWd8LMN", HMACSecret: "rLABDrx87et5KvRHVUgTm3pezWWd8LMN",
IssuerPrivateKey: "key-material", IssuerPrivateKey: &rsa.PrivateKey{},
Clients: []schema.OpenIDConnectClientConfiguration{ Clients: []schema.OpenIDConnectClientConfiguration{
{ {
ID: "good_id", ID: "good_id",
@ -391,12 +393,66 @@ func TestShouldRaiseErrorWhenOIDCClientConfiguredWithBadGrantTypes(t *testing.T)
assert.EqualError(t, validator.Errors()[0], "identity_providers: oidc: client 'good_id': option 'grant_types' must only have the values 'implicit', 'refresh_token', 'authorization_code', 'password', 'client_credentials' but one option is configured as 'bad_grant_type'") assert.EqualError(t, validator.Errors()[0], "identity_providers: oidc: client 'good_id': option 'grant_types' must only have the values 'implicit', 'refresh_token', 'authorization_code', 'password', 'client_credentials' but one option is configured as 'bad_grant_type'")
} }
func TestShouldNotErrorOnCertificateValid(t *testing.T) {
validator := schema.NewStructValidator()
config := &schema.IdentityProvidersConfiguration{
OIDC: &schema.OpenIDConnectConfiguration{
HMACSecret: "rLABDrx87et5KvRHVUgTm3pezWWd8LMN",
IssuerCertificateChain: mustParseX509CertificateChain(testCert1),
IssuerPrivateKey: mustParseRSAPrivateKey(testKey1),
Clients: []schema.OpenIDConnectClientConfiguration{
{
ID: "good_id",
Secret: "good_secret",
Policy: "two_factor",
RedirectURIs: []string{
"https://google.com/callback",
},
},
},
},
}
ValidateIdentityProviders(config, validator)
assert.Len(t, validator.Warnings(), 0)
assert.Len(t, validator.Errors(), 0)
}
func TestShouldRaiseErrorOnCertificateNotValid(t *testing.T) {
validator := schema.NewStructValidator()
config := &schema.IdentityProvidersConfiguration{
OIDC: &schema.OpenIDConnectConfiguration{
HMACSecret: "rLABDrx87et5KvRHVUgTm3pezWWd8LMN",
IssuerCertificateChain: mustParseX509CertificateChain(testCert1),
IssuerPrivateKey: mustParseRSAPrivateKey(testKey2),
Clients: []schema.OpenIDConnectClientConfiguration{
{
ID: "good_id",
Secret: "good_secret",
Policy: "two_factor",
RedirectURIs: []string{
"https://google.com/callback",
},
},
},
},
}
ValidateIdentityProviders(config, validator)
assert.Len(t, validator.Warnings(), 0)
require.Len(t, validator.Errors(), 1)
assert.EqualError(t, validator.Errors()[0], "identity_providers: oidc: option 'issuer_private_key' does not appear to be the private key the certificate provided by option 'issuer_certificate_chain'")
}
func TestShouldRaiseErrorWhenOIDCClientConfiguredWithBadResponseModes(t *testing.T) { func TestShouldRaiseErrorWhenOIDCClientConfiguredWithBadResponseModes(t *testing.T) {
validator := schema.NewStructValidator() validator := schema.NewStructValidator()
config := &schema.IdentityProvidersConfiguration{ config := &schema.IdentityProvidersConfiguration{
OIDC: &schema.OpenIDConnectConfiguration{ OIDC: &schema.OpenIDConnectConfiguration{
HMACSecret: "rLABDrx87et5KvRHVUgTm3pezWWd8LMN", HMACSecret: "rLABDrx87et5KvRHVUgTm3pezWWd8LMN",
IssuerPrivateKey: "key-material", IssuerPrivateKey: &rsa.PrivateKey{},
Clients: []schema.OpenIDConnectClientConfiguration{ Clients: []schema.OpenIDConnectClientConfiguration{
{ {
ID: "good_id", ID: "good_id",
@ -422,7 +478,7 @@ func TestShouldRaiseErrorWhenOIDCClientConfiguredWithBadUserinfoAlg(t *testing.T
config := &schema.IdentityProvidersConfiguration{ config := &schema.IdentityProvidersConfiguration{
OIDC: &schema.OpenIDConnectConfiguration{ OIDC: &schema.OpenIDConnectConfiguration{
HMACSecret: "rLABDrx87et5KvRHVUgTm3pezWWd8LMN", HMACSecret: "rLABDrx87et5KvRHVUgTm3pezWWd8LMN",
IssuerPrivateKey: "key-material", IssuerPrivateKey: &rsa.PrivateKey{},
Clients: []schema.OpenIDConnectClientConfiguration{ Clients: []schema.OpenIDConnectClientConfiguration{
{ {
ID: "good_id", ID: "good_id",
@ -448,7 +504,7 @@ func TestValidateIdentityProvidersShouldRaiseWarningOnSecurityIssue(t *testing.T
config := &schema.IdentityProvidersConfiguration{ config := &schema.IdentityProvidersConfiguration{
OIDC: &schema.OpenIDConnectConfiguration{ OIDC: &schema.OpenIDConnectConfiguration{
HMACSecret: "abc", HMACSecret: "abc",
IssuerPrivateKey: "abc", IssuerPrivateKey: &rsa.PrivateKey{},
MinimumParameterEntropy: 1, MinimumParameterEntropy: 1,
Clients: []schema.OpenIDConnectClientConfiguration{ Clients: []schema.OpenIDConnectClientConfiguration{
{ {
@ -476,7 +532,7 @@ func TestValidateIdentityProvidersShouldRaiseErrorsOnInvalidClientTypes(t *testi
config := &schema.IdentityProvidersConfiguration{ config := &schema.IdentityProvidersConfiguration{
OIDC: &schema.OpenIDConnectConfiguration{ OIDC: &schema.OpenIDConnectConfiguration{
HMACSecret: "hmac1", HMACSecret: "hmac1",
IssuerPrivateKey: "key2", IssuerPrivateKey: &rsa.PrivateKey{},
Clients: []schema.OpenIDConnectClientConfiguration{ Clients: []schema.OpenIDConnectClientConfiguration{
{ {
ID: "client-with-invalid-secret", ID: "client-with-invalid-secret",
@ -514,7 +570,7 @@ func TestValidateIdentityProvidersShouldNotRaiseErrorsOnValidPublicClients(t *te
config := &schema.IdentityProvidersConfiguration{ config := &schema.IdentityProvidersConfiguration{
OIDC: &schema.OpenIDConnectConfiguration{ OIDC: &schema.OpenIDConnectConfiguration{
HMACSecret: "hmac1", HMACSecret: "hmac1",
IssuerPrivateKey: "key2", IssuerPrivateKey: &rsa.PrivateKey{},
Clients: []schema.OpenIDConnectClientConfiguration{ Clients: []schema.OpenIDConnectClientConfiguration{
{ {
ID: "installed-app-client", ID: "installed-app-client",
@ -555,7 +611,7 @@ func TestValidateIdentityProvidersShouldSetDefaultValues(t *testing.T) {
config := &schema.IdentityProvidersConfiguration{ config := &schema.IdentityProvidersConfiguration{
OIDC: &schema.OpenIDConnectConfiguration{ OIDC: &schema.OpenIDConnectConfiguration{
HMACSecret: "rLABDrx87et5KvRHVUgTm3pezWWd8LMN", HMACSecret: "rLABDrx87et5KvRHVUgTm3pezWWd8LMN",
IssuerPrivateKey: "../../../README.md", IssuerPrivateKey: &rsa.PrivateKey{},
Clients: []schema.OpenIDConnectClientConfiguration{ Clients: []schema.OpenIDConnectClientConfiguration{
{ {
ID: "a-client", ID: "a-client",
@ -691,3 +747,111 @@ func TestValidateOIDCClientRedirectURIsSupportingPrivateUseURISchemes(t *testing
}) })
}) })
} }
var (
testCert1 = `
-----BEGIN CERTIFICATE-----
MIIC5jCCAc6gAwIBAgIRAJZ+6KrHw95zIDgm2arCTCgwDQYJKoZIhvcNAQELBQAw
EzERMA8GA1UEChMIQXV0aGVsaWEwHhcNMjIwOTA4MDIyNDQyWhcNMjMwOTA4MDIy
NDQyWjATMREwDwYDVQQKEwhBdXRoZWxpYTCCASIwDQYJKoZIhvcNAQEBBQADggEP
ADCCAQoCggEBAMAE7muDAJtLsV3WgOpjrZ1JD1RlhuSOa3V+4zo2NYFQSdZW18SZ
fYYgUwLOleEy3VQ3N9MEFh/rWNHYHdsBjDvz/Q1EzAlXqthGd0Sic/UDYtrahrko
jCSkZCQ5YVO9ivMRth6XdUlu7RHVYY3aSOWPx2wiw9cdN+e4p73W6KwyzT7ezbUD
0Nng0Z7CNQTLHv3LBsLUODc4aVOvp2B4aAaw6cn990buKMvUuo2ge9gh0c5gIOM5
dU7xOGAt7RzwCIHnG4CGAWPFuuS215ZeelgQr/9/fhtzDqSuBZw5f10vXnAyBwei
vN6Kffj2RXB+koFwBguT84A6cfmxWllGNF0CAwEAAaM1MDMwDgYDVR0PAQH/BAQD
AgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcN
AQELBQADggEBAFvORjj7RGoIc3q0fv6QjuncZ0Mu1/24O0smCr6tq5d6RQBRpb1M
jEsbTMLZErrHbyw/DWC75eJhW6T+6HiVTo6brBXkmDL+QGkLgRNOkZla6cnmIpmL
bf9iPmmcThscQERgYZzNg19zqK8JAQU/6PgU/N6OXTL/mQQoB972ET9dUl7lGx1Q
2l8XBe8t4QTp4t1xd3c4azxWvFNpzWBjC5eBWiVHLJmFXr4xpcnPFYFETOkvEqwt
pMQ2x895BoLrep6b+g0xeF4pmmIQwA9KrUVr++gpYaRzytaOIYwcIPMzt9iLWKQe
6ZSOrTVi8pPugYXp+LhVk/WI7r8EWtyADu0=
-----END CERTIFICATE-----`
testKey1 = `
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAwATua4MAm0uxXdaA6mOtnUkPVGWG5I5rdX7jOjY1gVBJ1lbX
xJl9hiBTAs6V4TLdVDc30wQWH+tY0dgd2wGMO/P9DUTMCVeq2EZ3RKJz9QNi2tqG
uSiMJKRkJDlhU72K8xG2Hpd1SW7tEdVhjdpI5Y/HbCLD1x0357invdborDLNPt7N
tQPQ2eDRnsI1BMse/csGwtQ4NzhpU6+nYHhoBrDpyf33Ru4oy9S6jaB72CHRzmAg
4zl1TvE4YC3tHPAIgecbgIYBY8W65LbXll56WBCv/39+G3MOpK4FnDl/XS9ecDIH
B6K83op9+PZFcH6SgXAGC5PzgDpx+bFaWUY0XQIDAQABAoIBAQClcdpHcglMxOwe
kRpkWdwWAAQgUJXoSbnW86wu1NRHBfmInyyrrSBVN3aunXbQITZIQIdt3kB94haW
P6KBt5Svd2saSqOOjSWb0SMkVOCaQ/+h19VqpcASNj4+Y94y+8ZD5ofHVfJtghDr
Y7H5OhHDEZ3e0xlwODGaCyUkUY4KBv/oIlILoh4phbDYHkZH8AzDnEiyVE1JAWlN
voAQysgSU7eEnNCi1S07jl5bY+MD3XpJkAfQsJYhqYT/qetStZ12PuXjpbIr3y53
qjCrKeWTyDN+gOznyIGuiR6nvXeQAw/o9hZiah4RuHXTPs/3GAcRXcuMR0pbgJ+B
yfX6eLK1AoGBAPKkJKPYJD2NHukAelNbT2OgeDIgJmfOIvLa73/x2hXvWwa4VwIC
POuKXtT/a02J4pYMGlaKXfHgLmaW2HPObOIjpxqgRIswsiKS1AbaDtkWnhaS1/SJ
oZ7Fk8DdX+1QT4J/fj/2uxRT0GhXdMxDpK7ekpmRE+APPCGhmOMgmWszAoGBAMqX
Ts1RdGWgDxLi15rDqdqRBARJG7Om/xC2voXVmbAb4Q+QoNrNeiIAM2usuhrVuj5V
c16m9fxswRNYqQBYyShDi5wp5a8UjfqDpzJdku2bmrBaL+XVq8PY+oTK6KS3ss8U
CGQ8P6Phz5JGavn/nDMRZ4EwEWqbEMUqJAJlpmIvAoGAQ9Wj8LJyn0qew6FAkaFL
dpzcPZdDZW35009l+a0RvWQnXJ+Yo5UglvEeRgoKY6kS0cQccOlKDl8QWdn+NZIW
WrqA8y6vOwKoKoZGBIxd7k8mb0UqXtFDf/HYtuis8tmrAN7H2vYNo0czUphwrNKU
bdcHwSsQFWns87IL3iO1AIUCgYBzmBX8jOePPN6c9hXzVoVKEshp8ZT+0uBilwLq
tk/07lNiYDGH5woy8E5mt62QtjaIbpVfgoCEwUEBWutDKWXNtYypVDabyWyhbhEu
abn2HX0L9smxqFNTcjCvKF/J7I74HQQUvVPKnIOlgMx1TOXBNcMLMXQekc/lz/+v
5nQjPQKBgQDjdJABeiy9tU0tzLWUVc5QoQKnlfSJoFLis46REb1yHwU9OjTc05Wx
5lAXdTjDmnelDdGWNWHjWOiKSkTxhvQD3jXriI5y8Sdxe3zS3ikYvbMbi97GJz0O
5oyNJo6/froW1dLkJJWR8hg2PQbtoOo6l9HHSd91BnJJ4qFbq9ZrXQ==
-----END RSA PRIVATE KEY-----`
testKey2 = `
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEA6z1LOg1ZCqb0lytXWZ+MRBpMHEXOoTOLYgfZXt1IYyE3Z758
cyalk0NYQhY5cZDsXPYWPvAHiPMUxutWkoxFwby56S+AbIMa3/Is+ILrHRJs8Exn
ZkpyrYFxPX12app2kErdmAkHSx0Z5/kuXiz96PHs8S8/ZbyZolLHzdfLtSzjvRm5
Zue5iFzsf19NJz5CIBfv8g5lRwtE8wNJoRSpn1xq7fqfuA0weDNFPzjlNWRLy6aa
rK7qJexRkmkCs4sLgyl+9NODYJpvmN8E1yhyC27E0joI6rBFVW7Ihv+cSPCdDzGp
EWe81x3AeqAa3mjVqkiq4u4Z2i8JDgBaPboqJwIDAQABAoIBAAFdLZ58jVOefDSU
L8F5R1rtvBs93GDa56f926jNJ6pLewLC+/2+757W+SAI+PRLntM7Kg3bXm/Q2QH+
Q1Y+MflZmspbWCdI61L5GIGoYKyeers59i+FpvySj5GHtLQRiTZ0+Kv1AXHSDWBm
9XneUOqU3IbZe0ifu1RRno72/VtjkGXbW8Mkkw+ohyGbIeTx/0/JQ6sSNZTT3Vk7
8i4IXptq3HSF0/vqZuah8rShoeNq72pD1YLM9YPdL5by1QkDLnqATDiCpLBTCaNV
I8sqYEun+HYbQzBj8ZACG2JVZpEEidONWQHw5BPWO95DSZYrVnEkuCqeH+u5vYt7
CHuJ3AECgYEA+W3v5z+j91w1VPHS0VB3SCDMouycAMIUnJPAbt+0LPP0scUFsBGE
hPAKddC54pmMZRQ2KIwBKiyWfCrJ8Xz8Yogn7fJgmwTHidJBr2WQpIEkNGlK3Dzi
jXL2sh0yC7sHvn0DqiQ79l/e7yRbSnv2wrTJEczOOH2haD7/tBRyCYECgYEA8W+q
E9YyGvEltnPFaOxofNZ8LHVcZSsQI5b6fc0iE7fjxFqeXPXEwGSOTwqQLQRiHn9b
CfPmIG4Vhyq0otVmlPvUnfBZ2OK+tl5X2/mQFO3ROMdvpi0KYa994uqfJdSTaqLn
jjoKFB906UFHnDQDLZUNiV1WwnkTglgLc+xrd6cCgYEAqqthyv6NyBTM3Tm2gcio
Ra9Dtntl51LlXZnvwy3IkDXBCd6BHM9vuLKyxZiziGx+Vy90O1xI872cnot8sINQ
Am+dur/tAEVN72zxyv0Y8qb2yfH96iKy9gxi5s75TnOEQgAygLnYWaWR2lorKRUX
bHTdXBOiS58S0UzCFEslGIECgYBqkO4SKWYeTDhoKvuEj2yjRYyzlu28XeCWxOo1
otiauX0YSyNBRt2cSgYiTzhKFng0m+QUJYp63/wymB/5C5Zmxi0XtWIDADpLhqLj
HmmBQ2Mo26alQ5YkffBju0mZyhVzaQop1eZi8WuKFV1FThPlB7hc3E0SM5zv2Grd
tQnOWwKBgQC40yZY0PcjuILhy+sIc0Wvh7LUA7taSdTye149kRvbvsCDN7Jh75lM
USjhLXY0Nld2zBm9r8wMb81mXH29uvD+tDqqsICvyuKlA/tyzXR+QTr7dCVKVwu0
1YjCJ36UpTsLre2f8nOSLtNmRfDPtbOE2mkOoO9dD9UU0XZwnvn9xw==
-----END RSA PRIVATE KEY-----`
)
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")
}
if block.Type != "RSA PRIVATE KEY" {
panic("not private key")
}
key, err := x509.ParsePKCS1PrivateKey(block.Bytes)
if err != nil {
panic(err)
}
return key
}
func mustParseX509CertificateChain(data string) schema.X509CertificateChain {
chain, err := schema.NewX509CertificateChain(data)
if err != nil {
panic(err)
}
return *chain
}

View File

@ -9,6 +9,7 @@ import (
"github.com/ory/fosite" "github.com/ory/fosite"
"github.com/ory/fosite/token/jwt" "github.com/ory/fosite/token/jwt"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/valyala/fasthttp"
"github.com/authelia/authelia/v4/internal/middlewares" "github.com/authelia/authelia/v4/internal/middlewares"
"github.com/authelia/authelia/v4/internal/model" "github.com/authelia/authelia/v4/internal/model"
@ -35,7 +36,7 @@ func OpenIDConnectUserinfo(ctx *middlewares.AutheliaCtx, rw http.ResponseWriter,
ctx.Logger.Errorf("UserInfo Request failed with error: %+v", rfc) ctx.Logger.Errorf("UserInfo Request failed with error: %+v", rfc)
if rfc.StatusCode() == http.StatusUnauthorized { if rfc.StatusCode() == http.StatusUnauthorized {
rw.Header().Set("WWW-Authenticate", fmt.Sprintf(`Bearer error="%s",error_description="%s"`, rfc.ErrorField, rfc.GetDescription())) rw.Header().Set(fasthttp.HeaderWWWAuthenticate, fmt.Sprintf(`Bearer error="%s",error_description="%s"`, rfc.ErrorField, rfc.GetDescription()))
} }
ctx.Providers.OpenIDConnect.WriteError(rw, req, err) ctx.Providers.OpenIDConnect.WriteError(rw, req, err)
@ -90,9 +91,7 @@ func OpenIDConnectUserinfo(ctx *middlewares.AutheliaCtx, rw http.ResponseWriter,
claims["aud"] = audience claims["aud"] = audience
var ( var token string
keyID, token string
)
ctx.Logger.Tracef("UserInfo Response with id '%s' on client with id '%s' is being sent with the following claims: %+v", requester.GetID(), clientID, claims) ctx.Logger.Tracef("UserInfo Response with id '%s' on client with id '%s' is being sent with the following claims: %+v", requester.GetID(), clientID, claims)
@ -109,14 +108,8 @@ func OpenIDConnectUserinfo(ctx *middlewares.AutheliaCtx, rw http.ResponseWriter,
claims["jti"] = jti.String() claims["jti"] = jti.String()
claims["iat"] = time.Now().Unix() claims["iat"] = time.Now().Unix()
if keyID, err = ctx.Providers.OpenIDConnect.KeyManager.Strategy().GetPublicKeyID(req.Context()); err != nil {
ctx.Providers.OpenIDConnect.WriteError(rw, req, fosite.ErrServerError.WithHintf("Could not find the active JWK."))
return
}
headers := &jwt.Headers{ headers := &jwt.Headers{
Extra: map[string]interface{}{"kid": keyID}, Extra: map[string]any{"kid": ctx.Providers.OpenIDConnect.KeyManager.GetActiveKeyID()},
} }
if token, _, err = ctx.Providers.OpenIDConnect.KeyManager.Strategy().Generate(req.Context(), claims, headers); err != nil { if token, _, err = ctx.Providers.OpenIDConnect.KeyManager.Strategy().Generate(req.Context(), claims, headers); err != nil {

View File

@ -37,8 +37,10 @@ func LoggerCtxPrintf(level logrus.Level) (logger *CtxPrintfLogger) {
func InitializeLogger(config schema.LogConfiguration, log bool) error { func InitializeLogger(config schema.LogConfiguration, log bool) error {
setLevelStr(config.Level, log) setLevelStr(config.Level, log)
callerLevels := []logrus.Level{} var callerLevels []logrus.Level
stackLevels := []logrus.Level{logrus.PanicLevel, logrus.FatalLevel, logrus.ErrorLevel} stackLevels := []logrus.Level{logrus.PanicLevel, logrus.FatalLevel, logrus.ErrorLevel}
logrus.AddHook(logrus_stack.NewHook(callerLevels, stackLevels)) logrus.AddHook(logrus_stack.NewHook(callerLevels, stackLevels))
if config.Format == logFormatJSON { if config.Format == logFormatJSON {

View File

@ -9,19 +9,17 @@ import (
"strings" "strings"
"github.com/ory/fosite/token/jwt" "github.com/ory/fosite/token/jwt"
jose "gopkg.in/square/go-jose.v2" "gopkg.in/square/go-jose.v2"
"github.com/authelia/authelia/v4/internal/configuration/schema" "github.com/authelia/authelia/v4/internal/configuration/schema"
"github.com/authelia/authelia/v4/internal/utils"
) )
// NewKeyManagerWithConfiguration when provided a schema.OpenIDConnectConfiguration creates a new KeyManager and adds an // NewKeyManagerWithConfiguration when provided a schema.OpenIDConnectConfiguration creates a new KeyManager and adds an
// initial key to the manager. // initial key to the manager.
func NewKeyManagerWithConfiguration(configuration *schema.OpenIDConnectConfiguration) (manager *KeyManager, err error) { func NewKeyManagerWithConfiguration(config *schema.OpenIDConnectConfiguration) (manager *KeyManager, err error) {
manager = NewKeyManager() manager = NewKeyManager()
_, _, err = manager.AddActivePrivateKeyData(configuration.IssuerPrivateKey) if _, err = manager.AddActiveJWK(config.IssuerCertificateChain, config.IssuerPrivateKey); err != nil {
if err != nil {
return nil, err return nil, err
} }
@ -30,174 +28,152 @@ func NewKeyManagerWithConfiguration(configuration *schema.OpenIDConnectConfigura
// NewKeyManager creates a new empty KeyManager. // NewKeyManager creates a new empty KeyManager.
func NewKeyManager() (manager *KeyManager) { func NewKeyManager() (manager *KeyManager) {
manager = new(KeyManager) return &KeyManager{
manager.keys = map[string]*rsa.PrivateKey{} jwks: &jose.JSONWebKeySet{},
manager.keySet = new(jose.JSONWebKeySet) }
return manager
} }
// Strategy returns the RS256JWTStrategy. // Strategy returns the fosite jwt.JWTStrategy.
func (m *KeyManager) Strategy() (strategy *RS256JWTStrategy) { func (m *KeyManager) Strategy() (strategy jwt.JWTStrategy) {
return m.strategy if m.jwk == nil {
return nil
}
return m.jwk.Strategy()
} }
// GetKeySet returns the joseJSONWebKeySet containing the rsa.PublicKey types. // GetKeySet returns the joseJSONWebKeySet containing the rsa.PublicKey types.
func (m *KeyManager) GetKeySet() (keySet *jose.JSONWebKeySet) { func (m *KeyManager) GetKeySet() (jwks *jose.JSONWebKeySet) {
return m.keySet return m.jwks
} }
// GetActiveWebKey obtains the currently active jose.JSONWebKey. // GetActiveJWK obtains the currently active jose.JSONWebKey.
func (m *KeyManager) GetActiveWebKey() (webKey *jose.JSONWebKey, err error) { func (m *KeyManager) GetActiveJWK() (jwk *jose.JSONWebKey, err error) {
webKeys := m.keySet.Key(m.activeKeyID) if m.jwks == nil || m.jwk == nil {
if len(webKeys) == 1 { return nil, errors.New("could not obtain the active JWK from an improperly configured key manager")
return &webKeys[0], nil
} }
if len(webKeys) == 0 { 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("could not find a key with the active key id")
} }
return &webKeys[0], errors.New("multiple keys with the same key id") return nil, errors.New("multiple keys with the same key id")
} }
// GetActiveKeyID returns the key id of the currently active key. // GetActiveKeyID returns the key id of the currently active key.
func (m *KeyManager) GetActiveKeyID() (keyID string) { func (m *KeyManager) GetActiveKeyID() (keyID string) {
return m.activeKeyID if m.jwk == nil {
return ""
} }
// GetActiveKey returns the rsa.PublicKey of the currently active key. return m.jwk.id
func (m *KeyManager) GetActiveKey() (key *rsa.PublicKey, err error) {
if key, ok := m.keys[m.activeKeyID]; ok {
return &key.PublicKey, nil
}
return nil, errors.New("failed to retrieve active public key")
} }
// GetActivePrivateKey returns the rsa.PrivateKey of the currently active key. // GetActivePrivateKey returns the rsa.PrivateKey of the currently active key.
func (m *KeyManager) GetActivePrivateKey() (key *rsa.PrivateKey, err error) { func (m *KeyManager) GetActivePrivateKey() (key *rsa.PrivateKey, err error) {
if key, ok := m.keys[m.activeKeyID]; ok { if m.jwk == nil {
return key, nil
}
return nil, errors.New("failed to retrieve active private key") return nil, errors.New("failed to retrieve active private key")
} }
// AddActivePrivateKeyData adds a rsa.PublicKey given the key in the PEM string format, then sets it to the active key. return m.jwk.key, nil
func (m *KeyManager) AddActivePrivateKeyData(data string) (key *rsa.PrivateKey, webKey *jose.JSONWebKey, err error) {
ikey, err := utils.ParseX509FromPEM([]byte(data))
if err != nil {
return nil, nil, err
} }
var ok bool // AddActiveJWK is used to add a cert and key pair.
func (m *KeyManager) AddActiveJWK(chain schema.X509CertificateChain, key *rsa.PrivateKey) (jwk *JWK, err error) {
if key, ok = ikey.(*rsa.PrivateKey); !ok { // TODO: Add a mutex when implementing key rotation to be utilized here and in methods which retrieve the JWK or JWKS.
return nil, nil, errors.New("key must be an RSA private key") if m.jwk, err = NewJWK(chain, key); err != nil {
}
webKey, err = m.AddActivePrivateKey(key)
return key, webKey, err
}
// AddActivePrivateKey adds a rsa.PublicKey, then sets it to the active key.
func (m *KeyManager) AddActivePrivateKey(key *rsa.PrivateKey) (webKey *jose.JSONWebKey, err error) {
wk := jose.JSONWebKey{
Key: &key.PublicKey,
Algorithm: "RS256",
Use: "sig",
}
keyID, err := wk.Thumbprint(crypto.SHA1)
if err != nil {
return nil, err return nil, err
} }
strKeyID := strings.ToLower(fmt.Sprintf("%x", keyID)) m.jwks.Keys = append(m.jwks.Keys, *m.jwk.JSONWebKey())
if len(strKeyID) >= 7 {
// Shorten the key if it's greater than 7 to a length of exactly 7. return m.jwk, nil
strKeyID = strKeyID[0:6]
} }
if _, ok := m.keys[strKeyID]; ok { // JWTStrategy is a decorator struct for the fosite jwt.JWTStrategy.
return nil, fmt.Errorf("key id %s already exists", strKeyID) type JWTStrategy struct {
} jwt.JWTStrategy
// TODO: Add Mutex here when implementing key rotation. id string
wk.KeyID = strKeyID
m.keySet.Keys = append(m.keySet.Keys, wk)
m.keys[strKeyID] = key
m.activeKeyID = strKeyID
m.strategy, err = NewRS256JWTStrategy(wk.KeyID, key)
if err != nil {
return &wk, err
}
return &wk, nil
}
// NewRS256JWTStrategy returns a new RS256JWTStrategy.
func NewRS256JWTStrategy(id string, key *rsa.PrivateKey) (strategy *RS256JWTStrategy, err error) {
strategy = new(RS256JWTStrategy)
strategy.JWTStrategy = new(jwt.RS256JWTStrategy)
strategy.SetKey(id, key)
return strategy, nil
}
// RS256JWTStrategy is a decorator struct for the fosite RS256JWTStrategy.
type RS256JWTStrategy struct {
JWTStrategy *jwt.RS256JWTStrategy
keyID string
} }
// KeyID returns the key id. // KeyID returns the key id.
func (s RS256JWTStrategy) KeyID() (id string) { func (s *JWTStrategy) KeyID() (id string) {
return s.keyID return s.id
}
// SetKey sets the provided key id and key as the active key (this is what triggers fosite to use it).
func (s *RS256JWTStrategy) SetKey(id string, key *rsa.PrivateKey) {
s.keyID = id
s.JWTStrategy.PrivateKey = key
}
// Hash is a decorator func for the underlying fosite RS256JWTStrategy.
func (s *RS256JWTStrategy) Hash(ctx context.Context, in []byte) ([]byte, error) {
return s.JWTStrategy.Hash(ctx, in)
}
// GetSigningMethodLength is a decorator func for the underlying fosite RS256JWTStrategy.
func (s *RS256JWTStrategy) GetSigningMethodLength() int {
return s.JWTStrategy.GetSigningMethodLength()
}
// GetSignature is a decorator func for the underlying fosite RS256JWTStrategy.
func (s *RS256JWTStrategy) GetSignature(ctx context.Context, token string) (string, error) {
return s.JWTStrategy.GetSignature(ctx, token)
}
// Generate is a decorator func for the underlying fosite RS256JWTStrategy.
func (s *RS256JWTStrategy) Generate(ctx context.Context, claims jwt.MapClaims, header jwt.Mapper) (string, string, error) {
return s.JWTStrategy.Generate(ctx, claims, header)
}
// Validate is a decorator func for the underlying fosite RS256JWTStrategy.
func (s *RS256JWTStrategy) Validate(ctx context.Context, token string) (string, error) {
return s.JWTStrategy.Validate(ctx, token)
}
// Decode is a decorator func for the underlying fosite RS256JWTStrategy.
func (s *RS256JWTStrategy) Decode(ctx context.Context, token string) (*jwt.Token, error) {
return s.JWTStrategy.Decode(ctx, token)
} }
// GetPublicKeyID is a decorator func for the underlying fosite RS256JWTStrategy. // GetPublicKeyID is a decorator func for the underlying fosite RS256JWTStrategy.
func (s *RS256JWTStrategy) GetPublicKeyID(_ context.Context) (string, error) { func (s *JWTStrategy) GetPublicKeyID(_ context.Context) (string, error) {
return s.keyID, nil 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: "RS256",
Use: "sig",
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.JWTStrategy) {
return &JWTStrategy{id: j.id, JWTStrategy: &jwt.RS256JWTStrategy{PrivateKey: j.key}}
}
// 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: "RS256",
Use: "sig",
Certificates: j.chain.Certificates(),
}
if len(jwk.Certificates) != 0 {
jwk.CertificateThumbprintSHA1, jwk.CertificateThumbprintSHA256 = j.chain.Thumbprint(crypto.SHA1), j.chain.Thumbprint(crypto.SHA256)
}
return jwk
} }

View File

@ -8,42 +8,37 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/authelia/authelia/v4/internal/configuration/schema"
) )
func TestKeyManager_AddActiveKeyData(t *testing.T) { func TestKeyManager_AddActiveJWK(t *testing.T) {
manager := NewKeyManager() manager := NewKeyManager()
assert.Nil(t, manager.strategy) assert.Nil(t, manager.jwk)
assert.Nil(t, manager.Strategy()) assert.Nil(t, manager.Strategy())
key, wk, err := manager.AddActivePrivateKeyData(exampleIssuerPrivateKey) j, err := manager.AddActiveJWK(schema.X509CertificateChain{}, mustParseRSAPrivateKey(exampleIssuerPrivateKey))
require.NoError(t, err) require.NoError(t, err)
require.NotNil(t, key) require.NotNil(t, j)
require.NotNil(t, wk) require.NotNil(t, manager.jwk)
require.NotNil(t, manager.strategy)
require.NotNil(t, manager.Strategy()) require.NotNil(t, manager.Strategy())
thumbprint, err := wk.Thumbprint(crypto.SHA1) thumbprint, err := j.JSONWebKey().Thumbprint(crypto.SHA1)
assert.NoError(t, err) assert.NoError(t, err)
kid := strings.ToLower(fmt.Sprintf("%x", thumbprint)[0:6]) kid := strings.ToLower(fmt.Sprintf("%x", thumbprint)[:6])
assert.Equal(t, manager.activeKeyID, kid) assert.Equal(t, manager.jwk.id, kid)
assert.Equal(t, kid, wk.KeyID) assert.Equal(t, kid, j.JSONWebKey().KeyID)
assert.Len(t, manager.keys, 1) assert.Len(t, manager.jwks.Keys, 1)
assert.Len(t, manager.keySet.Keys, 1)
assert.Contains(t, manager.keys, kid)
keys := manager.keySet.Key(kid) keys := manager.jwks.Key(kid)
assert.Equal(t, keys[0].KeyID, kid) assert.Equal(t, keys[0].KeyID, kid)
privKey, err := manager.GetActivePrivateKey() privKey, err := manager.GetActivePrivateKey()
assert.NoError(t, err) assert.NoError(t, err)
assert.NotNil(t, privKey) assert.NotNil(t, privKey)
pubKey, err := manager.GetActiveKey() webKey, err := manager.GetActiveJWK()
assert.NoError(t, err)
assert.NotNil(t, pubKey)
webKey, err := manager.GetActiveWebKey()
assert.NoError(t, err) assert.NoError(t, err)
assert.NotNil(t, webKey) assert.NotNil(t, webKey)

View File

@ -14,17 +14,16 @@ import (
// NewOpenIDConnectProvider new-ups a OpenIDConnectProvider. // NewOpenIDConnectProvider new-ups a OpenIDConnectProvider.
func NewOpenIDConnectProvider(config *schema.OpenIDConnectConfiguration, storageProvider storage.Provider) (provider OpenIDConnectProvider, err error) { func NewOpenIDConnectProvider(config *schema.OpenIDConnectConfiguration, storageProvider storage.Provider) (provider OpenIDConnectProvider, err error) {
provider = OpenIDConnectProvider{
Fosite: nil,
}
if config == nil { if config == nil {
return provider, nil return provider, nil
} }
provider.Store = NewOpenIDConnectStore(config, storageProvider) provider = OpenIDConnectProvider{
Fosite: nil,
Store: NewOpenIDConnectStore(config, storageProvider),
}
composeConfiguration := &compose.Config{ cconfig := &compose.Config{
AccessTokenLifespan: config.AccessTokenLifespan, AccessTokenLifespan: config.AccessTokenLifespan,
AuthorizeCodeLifespan: config.AuthorizeCodeLifespan, AuthorizeCodeLifespan: config.AuthorizeCodeLifespan,
IDTokenLifespan: config.IDTokenLifespan, IDTokenLifespan: config.IDTokenLifespan,
@ -50,19 +49,19 @@ func NewOpenIDConnectProvider(config *schema.OpenIDConnectConfiguration, storage
strategy := &compose.CommonStrategy{ strategy := &compose.CommonStrategy{
CoreStrategy: compose.NewOAuth2HMACStrategy( CoreStrategy: compose.NewOAuth2HMACStrategy(
composeConfiguration, cconfig,
[]byte(utils.HashSHA256FromString(config.HMACSecret)), []byte(utils.HashSHA256FromString(config.HMACSecret)),
nil, nil,
), ),
OpenIDConnectTokenStrategy: compose.NewOpenIDConnectStrategy( OpenIDConnectTokenStrategy: compose.NewOpenIDConnectStrategy(
composeConfiguration, cconfig,
key, key,
), ),
JWTStrategy: provider.KeyManager.Strategy(), JWTStrategy: provider.KeyManager.Strategy(),
} }
provider.Fosite = compose.Compose( provider.Fosite = compose.Compose(
composeConfiguration, cconfig,
provider.Store, provider.Store,
strategy, strategy,
PlainTextHasher{}, PlainTextHasher{},
@ -109,7 +108,7 @@ func (p OpenIDConnectProvider) Pairwise() bool {
} }
// Write writes data with herodot.JSONWriter. // Write writes data with herodot.JSONWriter.
func (p OpenIDConnectProvider) Write(w http.ResponseWriter, r *http.Request, e interface{}, opts ...herodot.EncoderOptions) { func (p OpenIDConnectProvider) Write(w http.ResponseWriter, r *http.Request, e any, opts ...herodot.EncoderOptions) {
p.herodot.Write(w, r, e, opts...) p.herodot.Write(w, r, e, opts...)
} }

View File

@ -1,6 +1,9 @@
package oidc package oidc
import ( import (
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"net/url" "net/url"
"testing" "testing"
@ -20,17 +23,10 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_NotConfigured(t *testing
assert.Nil(t, provider.Store) assert.Nil(t, provider.Store)
} }
func TestOpenIDConnectProvider_NewOpenIDConnectProvider_BadIssuerKey(t *testing.T) {
_, err := NewOpenIDConnectProvider(&schema.OpenIDConnectConfiguration{
IssuerPrivateKey: "BAD KEY",
}, nil)
assert.Error(t, err, "abc")
}
func TestNewOpenIDConnectProvider_ShouldEnableOptionalDiscoveryValues(t *testing.T) { func TestNewOpenIDConnectProvider_ShouldEnableOptionalDiscoveryValues(t *testing.T) {
provider, err := NewOpenIDConnectProvider(&schema.OpenIDConnectConfiguration{ provider, err := NewOpenIDConnectProvider(&schema.OpenIDConnectConfiguration{
IssuerPrivateKey: exampleIssuerPrivateKey, IssuerCertificateChain: schema.X509CertificateChain{},
IssuerPrivateKey: mustParseRSAPrivateKey(exampleIssuerPrivateKey),
EnablePKCEPlainChallenge: true, EnablePKCEPlainChallenge: true,
HMACSecret: "asbdhaaskmdlkamdklasmdlkams", HMACSecret: "asbdhaaskmdlkamdklasmdlkams",
Clients: []schema.OpenIDConnectClientConfiguration{ Clients: []schema.OpenIDConnectClientConfiguration{
@ -63,7 +59,8 @@ func TestNewOpenIDConnectProvider_ShouldEnableOptionalDiscoveryValues(t *testing
func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GoodConfiguration(t *testing.T) { func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GoodConfiguration(t *testing.T) {
provider, err := NewOpenIDConnectProvider(&schema.OpenIDConnectConfiguration{ provider, err := NewOpenIDConnectProvider(&schema.OpenIDConnectConfiguration{
IssuerPrivateKey: exampleIssuerPrivateKey, IssuerCertificateChain: schema.X509CertificateChain{},
IssuerPrivateKey: mustParseRSAPrivateKey(exampleIssuerPrivateKey),
HMACSecret: "asbdhaaskmdlkamdklasmdlkams", HMACSecret: "asbdhaaskmdlkamdklasmdlkams",
Clients: []schema.OpenIDConnectClientConfiguration{ Clients: []schema.OpenIDConnectClientConfiguration{
{ {
@ -102,7 +99,8 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GoodConfiguration(t *tes
func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOpenIDConnectWellKnownConfiguration(t *testing.T) { func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOpenIDConnectWellKnownConfiguration(t *testing.T) {
provider, err := NewOpenIDConnectProvider(&schema.OpenIDConnectConfiguration{ provider, err := NewOpenIDConnectProvider(&schema.OpenIDConnectConfiguration{
IssuerPrivateKey: exampleIssuerPrivateKey, IssuerCertificateChain: schema.X509CertificateChain{},
IssuerPrivateKey: mustParseRSAPrivateKey(exampleIssuerPrivateKey),
HMACSecret: "asbdhaaskmdlkamdklasmdlkams", HMACSecret: "asbdhaaskmdlkamdklasmdlkams",
Clients: []schema.OpenIDConnectClientConfiguration{ Clients: []schema.OpenIDConnectClientConfiguration{
{ {
@ -193,7 +191,8 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOpenIDConnectWellKnow
func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOAuth2WellKnownConfiguration(t *testing.T) { func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOAuth2WellKnownConfiguration(t *testing.T) {
provider, err := NewOpenIDConnectProvider(&schema.OpenIDConnectConfiguration{ provider, err := NewOpenIDConnectProvider(&schema.OpenIDConnectConfiguration{
IssuerPrivateKey: exampleIssuerPrivateKey, IssuerCertificateChain: schema.X509CertificateChain{},
IssuerPrivateKey: mustParseRSAPrivateKey(exampleIssuerPrivateKey),
HMACSecret: "asbdhaaskmdlkamdklasmdlkams", HMACSecret: "asbdhaaskmdlkamdklasmdlkams",
Clients: []schema.OpenIDConnectClientConfiguration{ Clients: []schema.OpenIDConnectClientConfiguration{
{ {
@ -270,7 +269,8 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOAuth2WellKnownConfig
func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOpenIDConnectWellKnownConfigurationWithPlainPKCE(t *testing.T) { func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOpenIDConnectWellKnownConfigurationWithPlainPKCE(t *testing.T) {
provider, err := NewOpenIDConnectProvider(&schema.OpenIDConnectConfiguration{ provider, err := NewOpenIDConnectProvider(&schema.OpenIDConnectConfiguration{
IssuerPrivateKey: exampleIssuerPrivateKey, IssuerCertificateChain: schema.X509CertificateChain{},
IssuerPrivateKey: mustParseRSAPrivateKey(exampleIssuerPrivateKey),
HMACSecret: "asbdhaaskmdlkamdklasmdlkams", HMACSecret: "asbdhaaskmdlkamdklasmdlkams",
EnablePKCEPlainChallenge: true, EnablePKCEPlainChallenge: true,
Clients: []schema.OpenIDConnectClientConfiguration{ Clients: []schema.OpenIDConnectClientConfiguration{
@ -293,3 +293,21 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOpenIDConnectWellKnow
assert.Equal(t, "S256", disco.CodeChallengeMethodsSupported[0]) assert.Equal(t, "S256", disco.CodeChallengeMethodsSupported[0])
assert.Equal(t, "plain", disco.CodeChallengeMethodsSupported[1]) assert.Equal(t, "plain", disco.CodeChallengeMethodsSupported[1])
} }
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")
}
if block.Type != "RSA PRIVATE KEY" {
panic("not private key")
}
key, err := x509.ParsePKCS1PrivateKey(block.Bytes)
if err != nil {
panic(err)
}
return key
}

View File

@ -13,7 +13,8 @@ import (
func TestOpenIDConnectStore_GetClientPolicy(t *testing.T) { func TestOpenIDConnectStore_GetClientPolicy(t *testing.T) {
s := NewOpenIDConnectStore(&schema.OpenIDConnectConfiguration{ s := NewOpenIDConnectStore(&schema.OpenIDConnectConfiguration{
IssuerPrivateKey: exampleIssuerPrivateKey, IssuerCertificateChain: schema.X509CertificateChain{},
IssuerPrivateKey: mustParseRSAPrivateKey(exampleIssuerPrivateKey),
Clients: []schema.OpenIDConnectClientConfiguration{ Clients: []schema.OpenIDConnectClientConfiguration{
{ {
ID: "myclient", ID: "myclient",
@ -44,7 +45,8 @@ func TestOpenIDConnectStore_GetClientPolicy(t *testing.T) {
func TestOpenIDConnectStore_GetInternalClient(t *testing.T) { func TestOpenIDConnectStore_GetInternalClient(t *testing.T) {
s := NewOpenIDConnectStore(&schema.OpenIDConnectConfiguration{ s := NewOpenIDConnectStore(&schema.OpenIDConnectConfiguration{
IssuerPrivateKey: exampleIssuerPrivateKey, IssuerCertificateChain: schema.X509CertificateChain{},
IssuerPrivateKey: mustParseRSAPrivateKey(exampleIssuerPrivateKey),
Clients: []schema.OpenIDConnectClientConfiguration{ Clients: []schema.OpenIDConnectClientConfiguration{
{ {
ID: "myclient", ID: "myclient",
@ -76,7 +78,8 @@ func TestOpenIDConnectStore_GetInternalClient_ValidClient(t *testing.T) {
} }
s := NewOpenIDConnectStore(&schema.OpenIDConnectConfiguration{ s := NewOpenIDConnectStore(&schema.OpenIDConnectConfiguration{
IssuerPrivateKey: exampleIssuerPrivateKey, IssuerCertificateChain: schema.X509CertificateChain{},
IssuerPrivateKey: mustParseRSAPrivateKey(exampleIssuerPrivateKey),
Clients: []schema.OpenIDConnectClientConfiguration{c1}, Clients: []schema.OpenIDConnectClientConfiguration{c1},
}, nil) }, nil)
@ -103,7 +106,8 @@ func TestOpenIDConnectStore_GetInternalClient_InvalidClient(t *testing.T) {
} }
s := NewOpenIDConnectStore(&schema.OpenIDConnectConfiguration{ s := NewOpenIDConnectStore(&schema.OpenIDConnectConfiguration{
IssuerPrivateKey: exampleIssuerPrivateKey, IssuerCertificateChain: schema.X509CertificateChain{},
IssuerPrivateKey: mustParseRSAPrivateKey(exampleIssuerPrivateKey),
Clients: []schema.OpenIDConnectClientConfiguration{c1}, Clients: []schema.OpenIDConnectClientConfiguration{c1},
}, nil) }, nil)
@ -114,7 +118,8 @@ func TestOpenIDConnectStore_GetInternalClient_InvalidClient(t *testing.T) {
func TestOpenIDConnectStore_IsValidClientID(t *testing.T) { func TestOpenIDConnectStore_IsValidClientID(t *testing.T) {
s := NewOpenIDConnectStore(&schema.OpenIDConnectConfiguration{ s := NewOpenIDConnectStore(&schema.OpenIDConnectConfiguration{
IssuerPrivateKey: exampleIssuerPrivateKey, IssuerCertificateChain: schema.X509CertificateChain{},
IssuerPrivateKey: mustParseRSAPrivateKey(exampleIssuerPrivateKey),
Clients: []schema.OpenIDConnectClientConfiguration{ Clients: []schema.OpenIDConnectClientConfiguration{
{ {
ID: "myclient", ID: "myclient",

View File

@ -1,7 +1,6 @@
package oidc package oidc
import ( import (
"crypto/rsa"
"time" "time"
"github.com/ory/fosite" "github.com/ory/fosite"
@ -21,21 +20,21 @@ func NewSession() (session *model.OpenIDSession) {
return &model.OpenIDSession{ return &model.OpenIDSession{
DefaultSession: &openid.DefaultSession{ DefaultSession: &openid.DefaultSession{
Claims: &jwt.IDTokenClaims{ Claims: &jwt.IDTokenClaims{
Extra: map[string]interface{}{}, Extra: map[string]any{},
}, },
Headers: &jwt.Headers{ Headers: &jwt.Headers{
Extra: map[string]interface{}{}, Extra: map[string]any{},
}, },
}, },
Extra: map[string]interface{}{}, Extra: map[string]any{},
} }
} }
// NewSessionWithAuthorizeRequest uses details from an AuthorizeRequester to generate an OpenIDSession. // NewSessionWithAuthorizeRequest uses details from an AuthorizeRequester to generate an OpenIDSession.
func NewSessionWithAuthorizeRequest(issuer, kid, username string, amr []string, extra map[string]interface{}, func NewSessionWithAuthorizeRequest(issuer, kid, username string, amr []string, extra map[string]any,
authTime time.Time, consent *model.OAuth2ConsentSession, requester fosite.AuthorizeRequester) (session *model.OpenIDSession) { authTime time.Time, consent *model.OAuth2ConsentSession, requester fosite.AuthorizeRequester) (session *model.OpenIDSession) {
if extra == nil { if extra == nil {
extra = make(map[string]interface{}) extra = map[string]any{}
} }
session = &model.OpenIDSession{ session = &model.OpenIDSession{
@ -53,14 +52,14 @@ func NewSessionWithAuthorizeRequest(issuer, kid, username string, amr []string,
AuthenticationMethodsReferences: amr, AuthenticationMethodsReferences: amr,
}, },
Headers: &jwt.Headers{ Headers: &jwt.Headers{
Extra: map[string]interface{}{ Extra: map[string]any{
"kid": kid, "kid": kid,
}, },
}, },
Subject: consent.Subject.UUID.String(), Subject: consent.Subject.UUID.String(),
Username: username, Username: username,
}, },
Extra: map[string]interface{}{}, Extra: map[string]any{},
ClientID: requester.GetClient().GetID(), ClientID: requester.GetClient().GetID(),
ChallengeID: consent.ChallengeID, ChallengeID: consent.ChallengeID,
} }
@ -122,10 +121,8 @@ type Client struct {
// KeyManager keeps track of all of the active/inactive rsa keys and provides them to services requiring them. // 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. // It additionally allows us to add keys for the purpose of key rotation in the future.
type KeyManager struct { type KeyManager struct {
activeKeyID string jwk *JWK
keys map[string]*rsa.PrivateKey jwks *jose.JSONWebKeySet
keySet *jose.JSONWebKeySet
strategy *RS256JWTStrategy
} }
// PlainTextHasher implements the fosite.Hasher interface without an actual hashing algo. // PlainTextHasher implements the fosite.Hasher interface without an actual hashing algo.

View File

@ -182,7 +182,7 @@ func (ekb ECDSAKeyBuilder) Build() (interface{}, error) {
} }
// ParseX509FromPEM parses PEM bytes and returns a PKCS key. // ParseX509FromPEM parses PEM bytes and returns a PKCS key.
func ParseX509FromPEM(data []byte) (key interface{}, err error) { func ParseX509FromPEM(data []byte) (key any, err error) {
block, _ := pem.Decode(data) block, _ := pem.Decode(data)
if block == nil { if block == nil {
return nil, errors.New("failed to parse PEM block containing the key") return nil, errors.New("failed to parse PEM block containing the key")