99from cryptojwt .jwt import utc_time_sans_frac
1010from cryptojwt .utils import as_bytes
1111from cryptojwt .utils import as_unicode
12+ from oidcmsg .oidc import JsonWebToken
1213from oidcmsg .oidc import verified_claim_name
1314
1415from oidcendpoint import JWT_BEARER
1718from oidcendpoint .exception import MultipleUsage
1819from oidcendpoint .exception import NotForMe
1920from oidcendpoint .exception import UnknownClient
21+ from oidcendpoint .util import importer
2022
2123logger = logging .getLogger (__name__ )
2224
@@ -53,7 +55,17 @@ def verify(self, **kwargs):
5355 :param kwargs:
5456 :return:
5557 """
56- raise NotImplementedError
58+ raise NotImplementedError ()
59+
60+ def is_usable (self , request = None , authorization_info = None ):
61+ """
62+ Verify that this authentication method is applicable.
63+
64+ :param request: The request
65+ :param authorization_info: Other authorization information
66+ :return: True/False
67+ """
68+ raise NotImplementedError ()
5769
5870
5971def basic_authn (authn ):
@@ -76,14 +88,17 @@ class ClientSecretBasic(ClientAuthnMethod):
7688 Server, authenticate with the Authorization Server in accordance with
7789 Section 3.2.1 of OAuth 2.0 [RFC6749] using HTTP Basic authentication scheme.
7890 """
91+ tag = "client_secret_basic"
7992
80- def verify (self , request , authorization_info , ** kwargs ):
93+ def is_usable (self , request = None , authorization_info = None ):
94+ if authorization_info is not None and authorization_info .startswith ("Basic " ):
95+ return True
96+ return False
97+
98+ def verify (self , authorization_info , ** kwargs ):
8199 client_info = basic_authn (authorization_info )
82100
83- if (
84- self .endpoint_context .cdb [client_info ["id" ]]["client_secret" ]
85- == client_info ["secret" ]
86- ):
101+ if self .endpoint_context .cdb [client_info ["id" ]]["client_secret" ] == client_info ["secret" ]:
87102 return {"client_id" : client_info ["id" ]}
88103 else :
89104 raise AuthnFailure ()
@@ -96,12 +111,18 @@ class ClientSecretPost(ClientSecretBasic):
96111 Section 3.2.1 of OAuth 2.0 [RFC6749] by including the Client Credentials in
97112 the request body.
98113 """
114+ tag = "client_secret_post"
115+
116+ def is_usable (self , request = None , authorization_info = None ):
117+ if request is None :
118+ return False
119+ if "client_id" in request and "client_secret" in request :
120+ return True
121+ return False
99122
100123 def verify (self , request , ** kwargs ):
101- if (
102- self .endpoint_context .cdb [request ["client_id" ]]["client_secret" ]
103- == request ["client_secret" ]
104- ):
124+ if self .endpoint_context .cdb [request ["client_id" ]]["client_secret" ] == request [
125+ "client_secret" ]:
105126 return {"client_id" : request ["client_id" ]}
106127 else :
107128 raise AuthnFailure ("secrets doesn't match" )
@@ -110,38 +131,70 @@ def verify(self, request, **kwargs):
110131class BearerHeader (ClientSecretBasic ):
111132 """
112133 """
134+ tag = "bearer_header"
113135
114- def verify (self , request , authorization_info , ** kwargs ):
115- if not authorization_info .startswith ("Bearer " ):
116- raise AuthnFailure ("Wrong type of authorization token" )
136+ def is_usable (self , request = None , authorization_info = None ):
137+ if authorization_info is not None and authorization_info .startswith ("Bearer " ):
138+ return True
139+ return False
117140
141+ def verify (self , authorization_info , ** kwargs ):
118142 return {"token" : authorization_info .split (" " , 1 )[1 ]}
119143
120144
121145class BearerBody (ClientSecretPost ):
122146 """
123147 Same as Client Secret Post
124148 """
149+ tag = "bearer_body"
150+
151+ def is_usable (self , request = None , authorization_info = None ):
152+ if request is not None and "access_token" in request :
153+ return True
154+ return False
125155
126156 def verify (self , request , ** kwargs ):
127- try :
128- return {"token" : request ["access_token" ]}
129- except KeyError :
157+ _token = request .get ("access_token" )
158+ if _token is None :
130159 raise AuthnFailure ("No access token" )
131160
161+ res = {"token" : _token }
162+ _client_id = request .get ("client_id" )
163+ if _client_id :
164+ res ["client_id" ] = _client_id
165+ return res
166+
132167
133168class JWSAuthnMethod (ClientAuthnMethod ):
134- def verify (self , request , ** kwargs ):
135- _jwt = JWT (self .endpoint_context .keyjar )
169+ def is_usable (self , request = None , authorization_info = None ):
170+ if request is None :
171+ return False
172+ if "client_assertion" in request :
173+ return True
174+ return False
175+
176+ def verify (self , request , key_type , ** kwargs ):
177+ _jwt = JWT (self .endpoint_context .keyjar , msg_cls = JsonWebToken )
136178 try :
137179 ca_jwt = _jwt .unpack (request ["client_assertion" ])
138180 except (Invalid , MissingKey , BadSignature ) as err :
139181 logger .info ("%s" % sanitize (err ))
140182 raise AuthnFailure ("Could not verify client_assertion." )
141183
142- authtoken = sanitize (ca_jwt )
143- if hasattr (ca_jwt , "to_dict" ) and callable (ca_jwt , "to_dict" ):
144- authtoken = sanitize (ca_jwt .to_dict ())
184+ _sign_alg = ca_jwt .jws_header .get ("alg" )
185+ if _sign_alg and _sign_alg .startswith ("HS" ):
186+ if key_type == "private_key" :
187+ raise AttributeError ("Wrong key type" )
188+ keys = self .endpoint_context .keyjar .get ("sig" , 'oct' , ca_jwt ["iss" ],
189+ ca_jwt .jws_header .get ("kid" ))
190+ _secret = self .endpoint_context .cdb [ca_jwt ["iss" ]].get ('client_secret' )
191+ if _secret and keys [0 ].key != as_bytes (_secret ):
192+ raise AttributeError ("Oct key used for signing not client_secret" )
193+ else :
194+ if key_type == "client_secret" :
195+ raise AttributeError ("Wrong key type" )
196+
197+ authtoken = sanitize (ca_jwt .to_dict ())
145198 logger .debug ("authntoken: {}" .format (authtoken ))
146199
147200 _endpoint = kwargs .get ("endpoint" )
@@ -179,12 +232,28 @@ class ClientSecretJWT(JWSAuthnMethod):
179232 The HMAC (Hash-based Message Authentication Code) is calculated using the
180233 bytes of the UTF-8 representation of the client_secret as the shared key.
181234 """
235+ tag = "client_secret_jwt"
236+
237+ def verify (self , request = None , ** kwargs ):
238+ res = JWSAuthnMethod .verify (self , request , key_type = "client_secret" ,
239+ ** kwargs )
240+ # Verify that a HS alg was used
241+ res ['method' ] = self .tag
242+ return res
182243
183244
184245class PrivateKeyJWT (JWSAuthnMethod ):
185246 """
186247 Clients that have registered a public key sign a JWT using that key.
187248 """
249+ tag = "private_key_jwt"
250+
251+ def verify (self , request = None , ** kwargs ):
252+ res = JWSAuthnMethod .verify (self , request , key_type = "private_key" ,
253+ ** kwargs )
254+ # Verify that an RS or ES alg was used
255+ res ['method' ] = self .tag
256+ return res
188257
189258
190259CLIENT_AUTHN_METHOD = {
@@ -208,9 +277,12 @@ def valid_client_info(cinfo):
208277
209278
210279def verify_client (
211- endpoint_context , request , authorization_info = None , get_client_id_from_token = None ,
212- endpoint = None , also_known_as = None
213- ):
280+ endpoint_context ,
281+ request ,
282+ authorization_info = None ,
283+ get_client_id_from_token = None ,
284+ endpoint = None ,
285+ also_known_as = None ):
214286 """
215287 Initiated Guessing !
216288
@@ -229,34 +301,28 @@ def verify_client(
229301 strings_parade = ("{} {}" .format (k , v ) for k , v in authorization_info .items ())
230302 authorization_info = " " .join (strings_parade )
231303
232- if authorization_info is None :
233- if "client_id" in request and "client_secret" in request :
234- auth_info = ClientSecretPost (endpoint_context ).verify (request )
235- auth_info ["method" ] = "client_secret_post"
236- elif "client_assertion" in request :
237- auth_info = JWSAuthnMethod (endpoint_context ).verify (request , endpoint = endpoint )
238- # If symmetric key was used
239- # auth_method = 'client_secret_jwt'
240- # If asymmetric key was used
241- auth_info ["method" ] = "private_key_jwt"
242- elif "access_token" in request :
243- auth_info = BearerBody (endpoint_context ).verify (request )
244- auth_info ["method" ] = "bearer_body"
245- else :
246- raise UnknownOrNoAuthnMethod ()
247- else :
248- if authorization_info .startswith ("Basic " ):
249- auth_info = ClientSecretBasic (endpoint_context ).verify (
250- request , authorization_info
251- )
252- auth_info ["method" ] = "client_secret_basic"
253- elif authorization_info .startswith ("Bearer " ):
254- auth_info = BearerHeader (endpoint_context ).verify (
255- request , authorization_info
256- )
257- auth_info ["method" ] = "bearer_header"
258- else :
259- raise UnknownOrNoAuthnMethod (authorization_info )
304+ auth_info = {}
305+ _methods = []
306+ if endpoint :
307+ try :
308+ _methods = endpoint_context .endpoint [endpoint ].client_authn_method
309+ except AttributeError :
310+ pass
311+
312+ for _method in _methods :
313+ if _method .is_usable (request , authorization_info ):
314+ try :
315+ auth_info = _method .verify (request = request , authorization_info = authorization_info ,
316+ endpoint = endpoint )
317+ except Exception as err :
318+ logger .warning ("Verifying auth using {} failed: {}" .format (_method .tag , err ))
319+ else :
320+ if "method" not in auth_info :
321+ auth_info ["method" ] = _method .tag
322+ break
323+
324+ if not auth_info :
325+ return auth_info
260326
261327 if also_known_as :
262328 client_id = also_known_as [auth_info .get ("client_id" )]
@@ -303,3 +369,16 @@ def verify_client(
303369 raise ValueError ("Unknown token" )
304370
305371 return auth_info
372+
373+
374+ def client_auth_setup (auth_set , endpoint_context ):
375+ res = []
376+
377+ for item in auth_set :
378+ _cls = CLIENT_AUTHN_METHOD .get (item )
379+ if _cls :
380+ res .append (_cls (endpoint_context ))
381+ else :
382+ res .append (importer (item )(endpoint_context ))
383+
384+ return res
0 commit comments