1 /* zxidoidc.c  -  Handwritten nitty-gritty functions for OpenID Connect 1.0 (openid-connect oidc)
2  * Copyright (c) 2011-2014 Sampo Kellomaki (sampo@iki.fi), All Rights Reserved.
3  * This is confidential unpublished proprietary source code of the author.
4  * NO WARRANTY, not even implied warranties. Contains trade secrets.
5  * Distribution prohibited unless authorized in writing.
6  * Licensed under Apache License 2.0, see file COPYING.
7  * $Id$
8  *
9  * While this file contains some protocol encoders and decoders for OAUTH2,
10  * the main logic of the flows is integrated to other parts, such as zxidsimp.c
11  *
12  * http://openid.net/specs/openid-connect-basic-1_0.html
13  * http://openid.net/specs/openid-connect-session-1_0.html
14  * http://openid.net/specs/openid-connect-messages-1_0.html
15  * http://tools.ietf.org/html/draft-ietf-oauth-v2-22
16  * http://tools.ietf.org/html/draft-jones-json-web-encryption-01
17  *
18  * 11.12.2011, created --Sampo
19  * 9.10.2014, adapted from zxidoauth.c --Sampo
20  */
21 
22 #include "platform.h"
23 #include "errmac.h"
24 #include "zx.h"
25 #include "zxid.h"
26 #include "zxidpriv.h"
27 #include "zxidutil.h"
28 #include "zxidconf.h"
29 #include "saml2.h"   /* for bindings like OAUTH2_REDIR */
30 #include "c/zx-data.h"
31 
32 /*() Interpret ZXID standard form fields to construct a XML structure for AuthnRequest */
33 
34 /* Called by:  zxid_start_sso_url */
zxid_mk_oauth_az_req(zxid_conf * cf,zxid_cgi * cgi,struct zx_str * loc,char * relay_state)35 struct zx_str* zxid_mk_oauth_az_req(zxid_conf* cf, zxid_cgi* cgi, struct zx_str* loc, char* relay_state)
36 {
37   struct zx_str* ss;
38   struct zx_str* nonce;
39   struct zx_str* eid;
40   char* eid_url_enc;
41   char* redir_url_enc;
42   char* state_b64;
43   char* prompt;
44   char* display;
45 
46   if (!loc) {
47     ERR("Redirection location URL missing. %d", 0);
48     return 0;
49   }
50 
51   redir_url_enc = zx_url_encode(cf->ctx, strlen(cf->burl), cf->burl, 0);
52   eid = zxid_my_ent_id(cf);
53   eid_url_enc = zx_url_encode(cf->ctx, eid->len, eid->s, 0);
54   zx_str_free(cf->ctx, eid);
55 
56   if (relay_state)
57     state_b64 = zxid_deflate_safe_b64_raw(cf->ctx, strlen(relay_state), relay_state);
58   else
59     state_b64 = 0;
60   nonce = zxid_mk_id(cf, "OA", ZXID_ID_BITS);
61   prompt = BOOL_STR_TEST(cgi->force_authn) ? "login" : 0;
62   prompt = BOOL_STR_TEST(cgi->consent && cgi->consent[0]) ? (prompt?"login+consent":"consent") : prompt;
63   display = BOOL_STR_TEST(cgi->ispassive) ? "none" : 0;
64 
65   ss = zx_strf(cf->ctx,
66 	       "%.*s%cresponse_type=token+id_token"
67 	       "&client_id=%s"
68 	       "&scope=openid+profile+email+address"
69 	       "&redirect_uri=%s%%3fo=O"
70 	       "&nonce=%.*s"
71 	       "%s%s"           /* &state= */
72 	       "%s%s"           /* &display= */
73 	       "%s%s"           /* &prompt= */
74 	       CRLF2,
75 	       loc->len, loc->s, (memchr(loc->s, '?', loc->len)?'&':'?'),
76 	       eid_url_enc,
77 	       redir_url_enc,
78 	       nonce->len, nonce->s,
79 	       state_b64?"&state=":"", STRNULLCHK(state_b64),
80 	       display?"&display=":"", STRNULLCHK(display),
81 	       prompt?"&prompt=":"", STRNULLCHK(prompt)
82 	       );
83   D("OAUTH2 AZ REQ(%.*s)", ss->len, ss->s);
84   if (errmac_debug & ERRMAC_INOUT) INFO("%.*s", ss->len, ss->s);
85   zx_str_free(cf->ctx, nonce);
86   ZX_FREE(cf->ctx, state_b64);
87   ZX_FREE(cf->ctx, eid_url_enc);
88   ZX_FREE(cf->ctx, redir_url_enc);
89   return ss;
90 }
91 
92 /*() Construct OAUTH2 / OpenID-Connect1 id_token. */
93 
94 /* Called by:  zxid_sso_issue_jwt */
zxid_mk_jwt(zxid_conf * cf,int claims_len,char * claims)95 char* zxid_mk_jwt(zxid_conf* cf, int claims_len, char* claims)
96 {
97   char hash[64 /*EVP_MAX_MD_SIZE*/];
98   char* jwt_hdr;
99   int hdr_len;
100   char* b64;
101   char* p;
102   int len = SIMPLE_BASE64_LEN(claims_len);
103 
104   switch (cf->oaz_jwt_sigenc_alg) {
105   case 'n':
106     jwt_hdr = "{\"typ\":\"JWT\",\"alg\":\"none\"}";
107     hdr_len = strlen(jwt_hdr);
108     len += SIMPLE_BASE64_LEN(hdr_len) + 1 + 1;
109     break;
110   case 'h':
111     jwt_hdr = "{\"typ\":\"JWT\",\"alg\":\"HS256\"}";
112     hdr_len = strlen(jwt_hdr);
113     len += SIMPLE_BASE64_LEN(hdr_len) + 1 + 1 + 86 /* siglen conservative estimate */;
114     break;
115   case 'r':
116     jwt_hdr = "{\"typ\":\"JWT\",\"alg\":\"RS256\"}";
117     hdr_len = strlen(jwt_hdr);
118     len += SIMPLE_BASE64_LEN(hdr_len) + 1 + 1 + 500 /* siglen conservative estimate */;
119     break;
120   default:
121     ERR("Unrecognized OAZ_JWT_SIGENC_ALG spec(%c). See zxid-conf.pd or zxidconf.h for documentation.", cf->oaz_jwt_sigenc_alg);
122     return 0;
123   }
124 
125   b64 = ZX_ALLOC(cf->ctx, len+1);
126   p = base64_fancy_raw(jwt_hdr, hdr_len, b64, safe_basis_64, 1<<31, 0, 0, 0);
127   *p++ = '.';
128   p = base64_fancy_raw(claims, claims_len, p, safe_basis_64, 1<<31, 0, 0, 0);
129   *p = 0;
130 
131   switch (cf->oaz_jwt_sigenc_alg) {
132   case 'n':
133     *p++ = '.';
134     *p = 0;
135     break;
136   case 'h':
137     if (!cf->hmac_key[0])
138       zx_get_symkey(cf, "hmac.key", cf->hmac_key);
139     zx_hmac_sha256(cf->ctx, ZX_SYMKEY_LEN, cf->hmac_key, p-b64, b64, hash, &len);
140     *p++ = '.';
141     p = base64_fancy_raw(hash, len, p, safe_basis_64, 1<<31, 0, 0, 0);
142     *p = 0;
143     break;
144   case 'r':
145     ERR("RSA not implemented yet %d",0);
146     zx_hmac_sha256(cf->ctx, ZX_SYMKEY_LEN, cf->hmac_key, p-b64, b64, hash, &len);
147     *p++ = '.';
148     p = base64_fancy_raw(hash, len, p, safe_basis_64, 1<<31, 0, 0, 0);
149     *p = 0;
150     break;
151   }
152   D("JWT(%s)", b64);
153   return b64;
154 }
155 
156 /*() Issue OAUTH2 / OpenID-Connect1 id_token. */
157 
158 /* Called by:  zxid_oauth2_az_server_sso */
zxid_sso_issue_jwt(zxid_conf * cf,zxid_cgi * cgi,zxid_ses * ses,struct timeval * srcts,zxid_entity * sp_meta,struct zx_str * acsurl,zxid_nid ** nameid,char * logop)159 char* zxid_sso_issue_jwt(zxid_conf* cf, zxid_cgi* cgi, zxid_ses* ses, struct timeval* srcts, zxid_entity* sp_meta, struct zx_str* acsurl, zxid_nid** nameid, char* logop)
160 {
161   int rawlen;
162   char* buf;
163   char* jwt;
164   char* jwt_id; /* sha1 hash of the jwt, taken from log_path */
165   struct zx_str issuer;
166   struct zx_str* affil;
167   char* eid;
168   struct zx_str* logpath;
169   struct zx_str ss;
170   struct zx_str nn;
171   struct zx_str id;
172   zxid_nid* tmpnameid;
173   char sp_name_buf[ZXID_MAX_SP_NAME_BUF];
174   D("sp_eid(%s)", sp_meta->eid);
175   if (!nameid)
176     nameid = &tmpnameid;
177 
178   //if (ar && ar->IssueInstant && ar->IssueInstant->g.len && ar->IssueInstant->g.s)
179   //  srcts->tv_sec = zx_date_time_to_secs(ar->IssueInstant->g.s);
180 
181   if (!cgi->allow_create)
182     cgi->allow_create = '1';
183   if (!cgi->nid_fmt || !cgi->nid_fmt[0])
184     cgi->nid_fmt = "prstnt";  /* Persistent is the default implied by the specs. */
185 
186   /* Check for federation. */
187 
188   issuer.s = cgi->client_id; issuer.len = strlen(cgi->client_id);
189   affil = &issuer;
190   zxid_nice_sha1(cf, sp_name_buf, sizeof(sp_name_buf), affil, affil, 7);
191   D("sp_name_buf(%s)  allow_create=%d", sp_name_buf, cgi->allow_create);
192 
193   *nameid = zxid_get_fed_nameid(cf, &issuer, affil, ses->uid, sp_name_buf, cgi->allow_create,
194 				(cgi->nid_fmt && !strcmp(cgi->nid_fmt, "trnsnt")),
195 				srcts, 0, logop);
196   if (logop) { logop[3]='S';  logop[4]='S';  logop[5]='O';  logop[6]=0;  /* Patch in SSO */ }
197   if (!*nameid) {
198     ERR("get_fed_nameid() client_id(%s) returned NULL", cgi->client_id);
199     return 0;
200   }
201 
202   eid = zxid_my_ent_id_cstr(cf);
203   // ,\"\":\"\"
204   buf = zx_alloc_sprintf(cf->ctx, &rawlen,
205 		       "{\"iss\":\"%s\""
206 		       ",\"user_id\":\"%.*s\""
207 		       ",\"aud\":\"%s\""
208 		       ",\"exp\":%d"
209 		       ",\"nonce\":\"%s\"}",
210 		       eid,
211 		       ZX_GET_CONTENT_LEN(*nameid), ZX_GET_CONTENT_S(*nameid),
212 		       cgi->client_id,
213 		       time(0) + cf->timeskew + cf->a7nttl,
214 		       cgi->nonce);
215   ZX_FREE(cf->ctx, eid);
216   jwt = zxid_mk_jwt(cf, rawlen, buf);
217   ZX_FREE(cf->ctx, buf);
218 
219   /* Log the issued JWT */
220 
221   ss.s = jwt; ss.len = strlen(jwt);
222   logpath = zxlog_path(cf, &issuer, &ss, ZXLOG_ISSUE_DIR, ZXLOG_JWT_KIND, 1);
223   if (!logpath) {
224     ERR("Could not generate logpath for aud(%s) JWT(%s)", cgi->client_id, jwt);
225     ZX_FREE(cf->ctx, jwt);
226     return 0;
227   }
228 
229   /* Since JWT does not have explicit ID attribute, we use the sha1 hash of the
230    * contents of JWT as an ID. Since this is what logpath also does, we just
231    * use the last component of the logpath. */
232   for (jwt_id = logpath->s + logpath->len; jwt_id > logpath->s && jwt_id[-1] != '/'; --jwt_id) ;
233 
234   if (cf->log_issue_a7n) {
235     if (zxlog_dup_check(cf, logpath, "sso_issue_jwt")) {
236       ERR("Duplicate JWT ID(%s)", jwt_id);
237       if (cf->dup_a7n_fatal) {
238 	ERR("FATAL (by configuration): Duplicate JWT ID(%s)", jwt_id);
239 	zxlog_blob(cf, 1, logpath, &ss, "sso_issue_JWT dup");
240 	zx_str_free(cf->ctx, logpath);
241 	ZX_FREE(cf->ctx, jwt);
242 	return 0;
243       }
244     }
245     zxlog_blob(cf, 1, logpath, &ss, "sso_issue_JWT");
246   }
247 
248   nn.s = cgi->nonce; nn.len = strlen(cgi->nonce);
249   id.s = jwt_id; id.len = strlen(jwt_id);
250 
251   if (cf->loguser)
252     zxlogusr(cf, ses->uid, 0, 0, 0, &issuer, &nn, &id,
253 	     ZX_GET_CONTENT(*nameid),
254 	     (cf->oaz_jwt_sigenc_alg!='n'?"U":"N"), "K", logop, 0, 0);
255 
256   zxlog(cf, 0, 0, 0, &issuer, &nn, &id,
257 	ZX_GET_CONTENT(*nameid),
258 	(cf->oaz_jwt_sigenc_alg!='n'?"U":"N"), "K", logop, 0, 0);
259 
260   zx_str_free(cf->ctx, logpath);
261   return jwt;
262 }
263 
264 /*(i) Generate SSO assertion and ship it to SP by OAUTH2 Az redir binding. User has already
265  * logged in by the time this is called. See also zxid_ssos_anreq() and zxid_idp_sso(). */
266 
267 /* Called by:  zxid_idp_dispatch */
zxid_oauth2_az_server_sso(zxid_conf * cf,zxid_cgi * cgi,zxid_ses * ses)268 struct zx_str* zxid_oauth2_az_server_sso(zxid_conf* cf, zxid_cgi* cgi, zxid_ses* ses)
269 {
270   zxid_entity* sp_meta;
271   struct zx_str* acsurl = 0;
272   struct timeval srcts = {0,501000};
273   zxid_nid* nameid;
274   char* idtok;
275   char logop[8];
276   strcpy(logop, "OAZxxxx");
277 
278   if (!cgi->client_id || !cgi->redirect_uri || !cgi->nonce) {
279     ERR("Missing mandatory OAUTH2 field client_id=%p redirect_uri=%p nonce=%p", cgi->client_id, cgi->redirect_uri, cgi->nonce);
280     return zx_dup_str(cf->ctx, "* ERR");
281   }
282 
283   if (!cgi->response_type || !strstr(cgi->response_type, "token") || !strstr(cgi->response_type, "id_token")) {
284     ERR("Missing mandatory OAUTH2 field response_type(%s) missing or does not contain `token id_token'", STRNULLCHKD(cgi->response_type));
285     return zx_dup_str(cf->ctx, "* ERR");
286   }
287 
288   if (!cgi->scope || !strstr(cgi->scope, "openid")) {
289     ERR("Missing mandatory OAUTH2 field scope=%p or the scope does not contain `openid'", STRNULLCHKD(cgi->scope));
290     return zx_dup_str(cf->ctx, "* ERR");
291   }
292 
293   sp_meta = zxid_get_ent(cf, cgi->client_id);
294   if (!sp_meta) {
295     ERR("The metadata for client_id(%s) of the Az Req could not be found or fetched", cgi->client_id);
296     return zx_dup_str(cf->ctx, "* ERR");
297   }
298   D("sp_eid(%s)", sp_meta->eid);
299 
300   /* Figure out the binding and url */
301 
302   acsurl = zxid_sp_loc_raw(cf, cgi, sp_meta, ZXID_ACS_SVC, OAUTH2_REDIR, 0);
303   if (!acsurl) {
304     ERR("sp(%s) metadata does not have SPSSODescriptor/AssertionConsumerService with Binding=\"" OAUTH2_REDIR "\". Pre-registering the SP at IdP is mandatory. redirect_uri(%s) will be ignored. (remote SP metadata problem)", sp_meta->eid, cgi->redirect_uri);
305     return zx_dup_str(cf->ctx, "* ERR");
306   }
307   if (strlen(cgi->redirect_uri) != acsurl->len || memcmp(cgi->redirect_uri, acsurl->s, acsurl->len)) {
308     ERR("sp(%s) metadata has SPSSODescriptor/AssertionConsumerService with Binding=\"" OAUTH2_REDIR "\" has value(%.*s), which is different from redirect_uri(%s). (remote SP problem)", sp_meta->eid, acsurl->len, acsurl->s, cgi->redirect_uri);
309     return zx_dup_str(cf->ctx, "* ERR");
310   }
311 
312   if (!cf->log_issue_a7n) {
313     INFO("LOG_ISSUE_A7N must be turned on in IdP configuration for artifact profile to work. Turning on now automatically. %d", 0);
314     cf->log_issue_a7n = 1;
315   }
316 
317   /* User ses->uid is already logged in, now check for federation with sp */
318 
319   idtok = zxid_sso_issue_jwt(cf, cgi, ses, &srcts, sp_meta, acsurl, &nameid, logop);
320   if (!idtok) {
321     ERR("Issuing JWT Failed %s", logop);
322     return zx_dup_str(cf->ctx, "* ERR");
323   }
324 
325   D("OAUTH2-ART ep(%.*s)", acsurl->len, acsurl->s);
326   zxlog(cf, 0, &srcts, 0, sp_meta->ed?&sp_meta->ed->entityID->g:0, 0, 0, ZX_GET_CONTENT(nameid), "N", "K", logop, ses->uid, "OAUTH2-ART");
327 
328   /* Formulate OAUTH2 / OpenID-Connect1 Az Redir Response */
329 
330   return zx_strf(cf->ctx, "Location: %s%c"
331 		 "access_token=%s"
332 		 "&token_type=bearer"
333 		 "&id_token=%s"
334 		 "&expires_in=%d" CRLF
335 		 "%s%s%s",   /* Set-Cookie */
336 		 cgi->redirect_uri, (strchr(cgi->redirect_uri, '?') ? '&' : '?'),
337 		 idtok,
338 		 idtok,
339 		 cf->a7nttl,
340 		 ses->setcookie?"Set-Cookie: ":"", ses->setcookie?ses->setcookie:"", ses->setcookie?CRLF:"");
341 }
342 
343 /*() Extract an assertion from OAUTH Az response, and perform SSO */
344 
345 /* Called by:  zxid_sp_oauth2_dispatch */
zxid_sp_dig_oauth_sso_a7n(zxid_conf * cf,zxid_cgi * cgi,zxid_ses * ses)346 static int zxid_sp_dig_oauth_sso_a7n(zxid_conf* cf, zxid_cgi* cgi, zxid_ses* ses)
347 {
348   //if (!zxid_chk_sig(cf, cgi, ses, &resp->gg, resp->Signature, resp->Issuer, 0, "Response")) return 0;
349 
350   //p = zxid_http_get(cf, url, &lim);
351 
352   ERR("*** process JWT %d", 0);
353 
354   //a7n = zxid_dec_a7n(cf, resp->Assertion, resp->EncryptedAssertion);
355   //if (a7n) {
356   //  zx_see_elem_ns(cf->ctx, &pop_seen, &resp->gg);
357   //  return zxid_sp_sso_finalize(cf, cgi, ses, a7n, pop_seen);
358   //}
359   if (cf->anon_ok && cgi->rs && !strcmp(cf->anon_ok, cgi->rs))  /* Prefix match */
360     return zxid_sp_anon_finalize(cf, cgi, ses);
361   ERR("No Assertion found and not anon_ok in OAUTH Response %d", 0);
362   zxlog(cf, 0, 0, 0, 0, 0, 0, ZX_GET_CONTENT(ses->nameid), "N", "C", "ERR", 0, "sid(%s) No assertion", ses->sid?ses->sid:"");
363   return 0;
364 }
365 
366 /*() Dispatch, on RP/SP side, OAUTH redir or artifact binding requests.
367  *
368  * return:: a string (such as Location: header) and let the caller output it.
369  *     Sometimes a dummy string is just output to indicate status, e.g.
370  *     "O" for SSO OK, "K" for normal OK no further action needed,
371  *     "M" show management screen, "I" forward to IdP dispatch, or
372  *     "* ERR" for error situations. These special strings
373  *     are allocated from static storage and MUST NOT be freed. Other
374  *     strings such as "Location: ..." should be freed by caller. */
375 
376 /* Called by:  zxid_simple_no_ses_cf */
zxid_sp_oauth2_dispatch(zxid_conf * cf,zxid_cgi * cgi,zxid_ses * ses)377 struct zx_str* zxid_sp_oauth2_dispatch(zxid_conf* cf, zxid_cgi* cgi, zxid_ses* ses)
378 {
379   int ret;
380 
381   if (cgi->id_token) {  /* OAUTH2 artifact / redir biding, aka OpenID-Connect1 */
382     ret = zxid_sp_dig_oauth_sso_a7n(cf, cgi, ses);
383     D("ret=%d ses=%p", ret, ses);
384     switch (ret) {
385     case ZXID_OK:      return zx_dup_str(cf->ctx, "K");
386     case ZXID_SSO_OK:  return zx_dup_str(cf->ctx, "O");
387     case ZXID_IDP_REQ: /* (PXY) Middle IdP of IdP Proxy flow */
388       return zx_dup_str(cf->ctx, zxid_simple_ses_active_cf(cf, cgi, ses, 0, 0x1fff));
389     case ZXID_FAIL:
390       D("*** FAIL, should send back to IdP select %d", 0);
391       return zx_dup_str(cf->ctx, "* ERR");
392     }
393     return zx_dup_str(cf->ctx, "M");  /* Management screen, please. */
394   }
395 
396   if (cf->log_level > 0)
397     zxlog(cf, 0, 0, 0, 0, 0, 0, ZX_GET_CONTENT(ses->nameid), "N", "C", "SPOADISP", 0, "sid(%s) unknown req or resp", STRNULLCHK(ses->sid));
398   ERR("Unknown request or response %d", 0);
399   return zx_dup_str(cf->ctx, "* ERR");
400 }
401 
402 /*() Handle, on IdP side, OAUTH2 / OpenID-Connect1 check_id requests.
403  *
404  * return:: a string (such as Location: header) and let the caller output it.
405  *     Sometimes a dummy string is just output to indicate status, e.g.
406  *     "O" for SSO OK, "K" for normal OK no further action needed,
407  *     "M" show management screen, "I" forward to IdP dispatch, or
408  *     "* ERR" for error situations. These special strings
409  *     are allocated from static storage and MUST NOT be freed. Other
410  *     strings such as "Location: ..." should be freed by caller. */
411 
412 /* Called by:  zxid_simple_no_ses_cf */
zxid_idp_oauth2_check_id(zxid_conf * cf,zxid_cgi * cgi,zxid_ses * ses,int auto_flags)413 char* zxid_idp_oauth2_check_id(zxid_conf* cf, zxid_cgi* cgi, zxid_ses* ses, int auto_flags)
414 {
415   int ret = 0;
416 
417   if (cgi->id_token) {  /* OAUTH2 artifact / redir biding, aka OpenID-Connect1 */
418     /* The id_token is directly the local filename of the corresponsing assertion. */
419 
420     D("ret=%d ses=%p", ret, ses);
421 
422     //return zxid_simple_show_page(cf, ss, ZXID_AUTO_METAC, ZXID_AUTO_METAH, "b", "text/xml", res_len, auto_flags, 0);
423   }
424 
425   if (cf->log_level > 0)
426     zxlog(cf, 0, 0, 0, 0, 0, 0, ZX_GET_CONTENT(ses->nameid), "N", "C", "IDPOACI", 0, "sid(%s) unknown req or resp", STRNULLCHK(ses->sid));
427   ERR("Unknown request or response %d", 0);
428   return 0;
429 }
430 
431 /* EOF  --  zxiduma.c */
432