Projet

Général

Profil

0001-WIP-oidc-support-ec-signature-for-rp-and-idp-modules.patch

Paul Marillonnet, 29 mars 2019 18:18

Télécharger (9,67 ko)

Voir les différences:

Subject: [PATCH] [WIP] oidc: support ec signature for rp and idp modules
 (#26253)

 src/authentic2_auth_oidc/backends.py |  9 ++++++
 src/authentic2_auth_oidc/models.py   |  4 ++-
 src/authentic2_auth_oidc/utils.py    |  3 ++
 src/authentic2_idp_oidc/models.py    |  2 ++
 src/authentic2_idp_oidc/utils.py     | 28 ++++++++++++-----
 src/authentic2_idp_oidc/views.py     |  2 +-
 tests/test_idp_oidc.py               | 45 ++++++++++++++++++++++++----
 7 files changed, 78 insertions(+), 15 deletions(-)
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
-