0001-WIP-oidc-support-ec-signature-for-rp-and-idp-modules.patch
src/authentic2_auth_oidc/backends.py | ||
---|---|---|
52 | 52 |
'no client_secret is defined on provider %s', id_token.iss) |
53 | 53 |
return None |
54 | 54 |
algs = ['HS256', 'HS384', 'HS512'] |
55 |
elif provider.idtoken_algo == models.OIDCProvider.ALGO_EC: |
|
56 |
key = provider.jwkset |
|
57 |
if not key: |
|
58 |
logger.warning('auth_oidc: idtoken signature algorithm is EC but ' |
|
59 |
'no JWKSet is defined on provider %s', id_token.iss) |
|
60 |
return None |
|
61 |
if len(key['keys']) == 1: |
|
62 |
key = list(key['keys'])[0] |
|
63 |
algs = ['ES256', 'ES384', 'ES512'] |
|
55 | 64 |
else: |
56 | 65 |
key = None |
57 | 66 |
src/authentic2_auth_oidc/models.py | ||
---|---|---|
37 | 37 |
ALGO_NONE = 0 |
38 | 38 |
ALGO_RSA = 1 |
39 | 39 |
ALGO_HMAC = 2 |
40 |
ALGO_EC = 3 |
|
40 | 41 |
ALGO_CHOICES = [ |
41 | 42 |
(ALGO_NONE, _('none')), |
42 | 43 |
(ALGO_RSA, _('RSA')), |
43 | 44 |
(ALGO_HMAC, _('HMAC')), |
45 |
(ALGO_EC, _('EC')), |
|
44 | 46 |
] |
45 | 47 | |
46 | 48 |
name = models.CharField( |
... | ... | |
138 | 140 |
def jwkset(self): |
139 | 141 |
from authentic2.crypto import base64url_encode |
140 | 142 | |
141 |
if self.idtoken_algo == self.ALGO_RSA:
|
|
143 |
if self.idtoken_algo in (self.ALGO_RSA, self.ALGO_EC):
|
|
142 | 144 |
if self.jwkset_json: |
143 | 145 |
return JWKSet.from_json(json.dumps(self.jwkset_json)) |
144 | 146 |
if self.idtoken_algo == self.ALGO_HMAC: |
src/authentic2_auth_oidc/utils.py | ||
---|---|---|
255 | 255 |
elif (set(['HS256', 'HS384', 'HS512']) & |
256 | 256 |
set(openid_configuration['id_token_signing_alg_values_supported'])): |
257 | 257 |
idtoken_algo = models.OIDCProvider.HMAC |
258 |
elif (set(['ES256', 'ES384', 'ES512']) & |
|
259 |
set(openid_configuration['id_token_signing_alg_values_supported'])): |
|
260 |
idtoken_algo = models.OIDCProvider.ALGO_ES |
|
258 | 261 |
else: |
259 | 262 |
raise ValueError(_('no common algorithm found for signing idtokens: %s') % |
260 | 263 |
openid_configuration['id_token_signing_alg_values_supported']) |
src/authentic2_idp_oidc/models.py | ||
---|---|---|
54 | 54 | |
55 | 55 |
ALGO_RSA = 1 |
56 | 56 |
ALGO_HMAC = 2 |
57 |
ALGO_EC = 3 |
|
57 | 58 |
ALGO_CHOICES = [ |
58 | 59 |
(ALGO_HMAC, _('HMAC')), |
59 | 60 |
(ALGO_RSA, _('RSA')), |
61 |
(ALGO_EC, _('EC')), |
|
60 | 62 |
] |
61 | 63 |
FLOW_AUTHORIZATION_CODE = 1 |
62 | 64 |
FLOW_IMPLICIT = 2 |
src/authentic2_idp_oidc/utils.py | ||
---|---|---|
35 | 35 |
return jwkset |
36 | 36 | |
37 | 37 | |
38 |
def get_first_rsa_sig_key(): |
|
39 |
for key in get_jwkset()['keys']: |
|
40 |
if key._params['kty'] != 'RSA': |
|
41 |
continue |
|
42 |
use = key._params.get('use') |
|
43 |
if use is None or use == 'sig': |
|
44 |
return key |
|
38 |
def get_first_sig_key_by_type(kty=None): |
|
39 |
if kty: |
|
40 |
for key in get_jwkset()['keys']: |
|
41 |
if key._params['kty'] != kty: |
|
42 |
continue |
|
43 |
use = key._params.get('use') |
|
44 |
if use is None or use == 'sig': |
|
45 |
return key |
|
45 | 46 |
return None |
46 | 47 | |
47 | 48 | |
49 |
def get_first_rsa_sig_key(): |
|
50 |
return get_first_sig_key_by_type('RSA') |
|
51 | ||
52 | ||
53 |
def get_first_ec_sig_key(): |
|
54 |
return get_first_sig_key_by_type('EC') |
|
55 | ||
56 | ||
48 | 57 |
def make_idtoken(client, claims): |
49 | 58 |
'''Make a serialized JWT targeted for this client''' |
50 | 59 |
if client.idtoken_algo == client.ALGO_HMAC: |
... | ... | |
56 | 65 |
header['kid'] = jwk.key_id |
57 | 66 |
if jwk is None: |
58 | 67 |
raise ImproperlyConfigured('no RSA key for signature operation in A2_IDP_OIDC_JWKSET') |
68 |
elif client.idtoken_algo == client.ALGO_EC: |
|
69 |
header = {'alg': 'ES256'} |
|
70 |
jwk = get_first_ec_sig_key() |
|
71 |
if jwk is None: |
|
72 |
raise ImproperlyConfigured('no EC key for signature operation in A2_IDP_OIDC_JWKSET') |
|
59 | 73 |
else: |
60 | 74 |
raise NotImplementedError |
61 | 75 |
jwt = JWT(header=header, claims=claims) |
src/authentic2_idp_oidc/views.py | ||
---|---|---|
37 | 37 |
'clien_secret_post', 'client_secret_basic', |
38 | 38 |
], |
39 | 39 |
'id_token_signing_alg_values_supported': [ |
40 |
'RS256', 'HS256', |
|
40 |
'RS256', 'HS256', 'ES256',
|
|
41 | 41 |
], |
42 | 42 |
'userinfo_endpoint': request.build_absolute_uri(reverse('oidc-user-info')), |
43 | 43 |
'frontchannel_logout_supported': True, |
tests/test_idp_oidc.py | ||
---|---|---|
42 | 42 |
"n": "0lN6CiJGFD8BSPV_azLoEl6Nq-WlHkU743D5rqvzw1sOaxstMGxAhVk2YIhWwfvapV6XjO_yvc4778VBTELOdjRw6BGUdBJepdwkL__TPyjEVhqMQj9MKhEU4GUy9w0Lsilb5D01kfrOKpmdcYw4jhcDvb0H4-LZgh1Vk84vF4WaQCUg_AX4drVDQOjoU8kuWIM8gz9w6zEsbIw-gtMRpFwS8ncA0zDX5VfyC77iMxzFftDIP2gM5GvdevMzvP9IRkRRBhP9vV4JchBFPHSA9OPJcnySjJJNW6aAJn6P6JasN1z68khjufM09J8UzmLAZYOq7gUG95Ox1KsV-g337Q", |
43 | 43 |
"e": "AQAB", |
44 | 44 |
"p": "-Nyj_Sw3f2HUqSssCZv84y7b3blOtGGAhfYN_JtGfcTQv2bOtxrIUzeonCi-Z_1W4hO10tqxJcOB0ibtDqkDlLhnLaIYOBfriITRFK83EJG5sC-0KTmFzUXFTA2aMc1QgP-Fu6gUfQpPqLgWxhx8EFhkBlBZshKU5-C-385Sco0" |
45 |
}, |
|
46 |
{ |
|
47 |
"kty": "EC", |
|
48 |
"d": "wwULaR9UYWZW6U2oEbkz3sO1lhPSj6DyA6e7PiUfhog", |
|
49 |
"use": "sig", |
|
50 |
"crv": "P-256", |
|
51 |
"x": "HZMHZkX-63heqA5pvWn-UR7bgcXZNEcQa5wfvG_BzTw", |
|
52 |
"y": "SUCuwjjiyKvGq5Odr0sjDqjha_CBqks0JQFrR7Ei5OQ", |
|
53 |
"alg": "ES256" |
|
45 | 54 |
} |
46 | 55 |
] |
47 | 56 |
} |
... | ... | |
223 | 232 |
access_token = query['access_token'][0] |
224 | 233 |
id_token = query['id_token'][0] |
225 | 234 | |
226 |
if oidc_client.idtoken_algo == oidc_client.ALGO_RSA: |
|
227 |
key = JWKSet.from_json(app.get(reverse('oidc-certs')).content) |
|
235 |
if oidc_client.idtoken_algo in (oidc_client.ALGO_RSA, oidc_client.ALGO_EC): |
|
236 |
keyset = JWKSet.from_json(app.get(reverse('oidc-certs')).content) |
|
237 |
for k in keyset.get('keys'): |
|
238 |
if { |
|
239 |
'RSA': oidc_client.ALGO_RSA, |
|
240 |
'EC': oidc_client.ALGO_EC |
|
241 |
}.get(k.key_type) == oidc_client.idtoken_algo: |
|
242 |
algs=[{ |
|
243 |
oidc_client.ALGO_RSA: 'RS256', |
|
244 |
oidc_client.ALGO_EC: 'ES256' |
|
245 |
}.get(oidc_client.idtoken_algo)] |
|
246 |
key = k |
|
247 |
break |
|
228 | 248 |
elif oidc_client.idtoken_algo == oidc_client.ALGO_HMAC: |
229 | 249 |
key = JWK(kty='oct', k=base64.b64encode(oidc_client.client_secret.encode('utf-8'))) |
250 |
algs = ['HS256'] |
|
230 | 251 |
else: |
231 | 252 |
raise NotImplementedError |
232 |
jwt = JWT(jwt=id_token, key=key) |
|
253 |
jwt = JWT(jwt=id_token, key=key, algs=algs)
|
|
233 | 254 |
claims = json.loads(jwt.claims) |
234 | 255 |
assert set(claims) >= set(['iss', 'sub', 'aud', 'exp', 'iat', 'nonce', 'auth_time', 'acr']) |
235 | 256 |
assert claims['nonce'] == 'yyy' |
... | ... | |
826 | 847 |
query = urlparse.parse_qs(location.fragment) |
827 | 848 |
id_token = query['id_token'][0] |
828 | 849 | |
829 |
if oidc_client.idtoken_algo == oidc_client.ALGO_RSA: |
|
830 |
key = JWKSet.from_json(app.get(reverse('oidc-certs')).content) |
|
850 |
if oidc_client.idtoken_algo in (oidc_client.ALGO_RSA, oidc_client.ALGO_EC): |
|
851 |
keyset = JWKSet.from_json(app.get(reverse('oidc-certs')).content) |
|
852 |
for k in keyset.get('keys'): |
|
853 |
if { |
|
854 |
'RSA': oidc_client.ALGO_RSA, |
|
855 |
'EC': oidc_client.ALGO_EC |
|
856 |
}.get(k.key_type) == oidc_client.idtoken_algo: |
|
857 |
algs=[{ |
|
858 |
oidc_client.ALGO_RSA: 'RS256', |
|
859 |
oidc_client.ALGO_EC: 'ES256' |
|
860 |
}.get(oidc_client.idtoken_algo)] |
|
861 |
key = k |
|
862 |
break |
|
831 | 863 |
elif oidc_client.idtoken_algo == oidc_client.ALGO_HMAC: |
832 | 864 |
key = JWK(kty='oct', k=base64.b64encode(oidc_client.client_secret.encode('utf-8'))) |
865 |
algs = ['HS256'] |
|
833 | 866 |
else: |
834 | 867 |
raise NotImplementedError |
835 |
jwt = JWT(jwt=id_token, key=key) |
|
868 |
jwt = JWT(jwt=id_token, key=key, algs=algs)
|
|
836 | 869 |
claims = json.loads(jwt.claims) |
837 | 870 |
if login_first: |
838 | 871 |
assert claims['acr'] == '0' |
839 |
- |