1 /* zxidoauth.c  -  Handwritten nitty-gritty functions for constructing OAUTH URLs
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  * RFC6749 OAuth2 Core
18  * RFC6750 OAuth2 Bearer Token Usage
19  *
20  * 11.12.2011, created --Sampo
21  * 9.10.2014, UMA related addtionas, JWK, dynamic client registration, etc. --Sampo
22  */
23 
24 #include "platform.h"
25 #include "errmac.h"
26 #include "zx.h"
27 #include "zxid.h"
28 #include "zxidpriv.h"
29 #include "zxidutil.h"
30 #include "zxidconf.h"
31 #include "saml2.h"   /* for bindings like OAUTH2_REDIR */
32 #include "c/zx-data.h"
33 #include "c/zxidvers.h"
34 #include <openssl/bn.h>
35 #include <openssl/rsa.h>
36 #include <openssl/x509.h>
37 
38 #if 1
39 
zxid_bn2b64(zxid_conf * cf,BIGNUM * bn)40 char* zxid_bn2b64(zxid_conf* cf, BIGNUM* bn)
41 {
42   char* bin;
43   char* b64;
44   char* e;
45   int len;
46 
47   if (!bn)
48     return zx_dup_cstr(cf->ctx, "");
49   bin = ZX_ALLOC(cf->ctx, BN_num_bytes(bn));
50   len = BN_bn2bin(bn, (unsigned char*)bin);
51   b64 = ZX_ALLOC(cf->ctx, SIMPLE_BASE64_LEN(len)+1);
52   e = base64_fancy_raw(bin, len, b64, safe_basis_64, 1000000, 0, "", '=');
53   *e = 0;
54   ZX_FREE(cf->ctx, bin);
55   return b64;
56 }
57 
58 /*() Create JWK (json-web-key) document
59  * See: https://tools.ietf.org/html/draft-ietf-jose-json-web-key-33 */
60 
61 /* Called by:  */
zxid_mk_jwk(zxid_conf * cf,char * pem,int enc_use)62 char* zxid_mk_jwk(zxid_conf* cf, char* pem, int enc_use)
63 {
64   char derbuf[4096];
65   X509* x = 0;  /* Forces d2i_X509() to alloc the memory. */
66   RSA* rsa;
67   char* buf;
68   char* p;
69   char* e;
70   char* n_b64;
71   char* e_b64;
72 
73   p = derbuf;
74   e = unbase64_raw(pem, pem+strlen(pem), p, zx_std_index_64);
75   OpenSSL_add_all_algorithms();
76   if (!d2i_X509(&x, (const unsigned char**)&p /* *** compile warning */, e-p) || !x) {
77     ERR("DER decoding of X509 certificate failed.\n%d", enc_use);
78     return 0;
79   }
80 
81   zx_zap_inplace_raw(pem, "\n\r \t");
82   rsa = zx_get_rsa_pub_from_cert(x, "mk_jwk");
83   n_b64 = zxid_bn2b64(cf, rsa?rsa->n:0);
84   e_b64 = zxid_bn2b64(cf, rsa?rsa->e:0);
85   X509_free(x);
86 
87   buf = zx_alloc_sprintf(cf->ctx, 0,
88 			 "{\"kty\":\"RSA\""
89 			 ",\"use\":\"%s\""
90 			 //",\"key_ops\":[%s]"
91 			 //",\"alg\":\"%s\""
92 			 ",\"n\":\"%s\""  /* modulus */
93 			 ",\"e\":\"%s\""  /* exponent */
94 			 ",\"x5c\":[\"%s\"]}",
95 			 enc_use?"enc":"sig",
96 			 //enc_use?"\"encrypt\",\"decrypt\"":"\"sign\",\"verify\"",
97 			 n_b64,
98 			 e_b64,
99 			 pem);
100   ZX_FREE(cf->ctx, n_b64);
101   ZX_FREE(cf->ctx, e_b64);
102   return buf;
103 }
104 
105 /*() Create JWKS (json-web-key-set) document
106  * See: https://tools.ietf.org/html/draft-ietf-jose-json-web-key-33 */
107 
zxid_mk_jwks(zxid_conf * cf)108 char* zxid_mk_jwks(zxid_conf* cf)
109 {
110   char  pem_buf[4096];
111   char* pem;
112   char* sig_jwk;
113   char* enc_jwk;
114   char* buf;
115   pem = zxid_read_cert_pem(cf, "sign-nopw-cert.pem", sizeof(pem_buf), pem_buf);
116   sig_jwk = zxid_mk_jwk(cf, pem, 0);
117   pem = zxid_read_cert_pem(cf, "enc-nopw-cert.pem", sizeof(pem_buf), pem_buf);
118   enc_jwk = zxid_mk_jwk(cf, pem, 1);
119 
120   buf = zx_alloc_sprintf(cf->ctx, 0, "{\"keys\":[%s,%s]}", sig_jwk, enc_jwk);
121   ZX_FREE(cf->ctx, sig_jwk);
122   ZX_FREE(cf->ctx, enc_jwk);
123   return buf;
124 }
125 
126 /*() Create OAUTH2 Dynamic Client Registration request.
127  * See: https://tools.ietf.org/html/draft-ietf-oauth-dyn-reg-20 */
128 
129 /* Called by:  */
zxid_mk_oauth2_dyn_cli_reg_req(zxid_conf * cf)130 char* zxid_mk_oauth2_dyn_cli_reg_req(zxid_conf* cf)
131 {
132   char* jwks;
133   char* buf;
134   jwks = zxid_mk_jwks(cf);
135   buf = zx_alloc_sprintf(cf->ctx, 0,
136 			 "{\"redirect_uris\":[\"%s%co=oauth_redir\"]"
137 			 ",\"token_endpoint_auth_method\":\"client_secret_post\""
138 			 ",\"grant_types\":[\"authorization_code\",\"implicit\",\"password\",\"client_credentials\",\"refresh_token\",\"urn:ietf:params:oauth:grant-type:jwt-bearer\",\"urn:ietf:params:oauth:grant-type:saml2-bearer\"]"
139 			 ",\"response_types\":[\"code\",\"token\"]"
140 			 ",\"client_name\":\"%s\""
141 			 ",\"client_uri\":\"%s\""
142 			 ",\"logo_uri\":\"%s\""
143 			 ",\"scope\":\"read read-write\""
144 			 ",\"contacts\":[\"%s\",\"%s\",\"%s\",\"%s\"]"
145 			 ",\"tos_uri\":\"%s\""
146 			 ",\"policy_uri\":\"%s\""
147 			 ",\"jwks_uri\":\"%s%co=jwks\""
148 			 ",\"jwks\":%s"
149 			 ",\"software_id\":\"ZXID\""
150 			 ",\"software_version\":\"" ZXID_REL "\""
151 			 ",\"zxid_rev\":\"" ZXID_REV "\"}",
152 			 cf->burl, strchr(cf->burl, '?')?'&':'?',
153 			 cf->nice_name,
154 			 "client_uri",
155 			 cf->button_url,
156 			 cf->contact_org, cf->contact_name, cf->contact_email, cf->contact_tel,
157 			 "tos_uri",
158 			 "policy_uri",
159 			 cf->burl, strchr(cf->burl, '?')?'&':'?',
160 			 jwks);
161   ZX_FREE(cf->ctx, jwks);
162   return buf;
163 }
164 
165 /*() Perform the registration and create OAUTH2 Dynamic Client Registration Response.
166  * The unparsed JSON for request is in the cgi->post field.
167  * See: https://tools.ietf.org/html/draft-ietf-oauth-dyn-reg-20 */
168 
zxid_mk_oauth2_dyn_cli_reg_res(zxid_conf * cf,zxid_cgi * cgi)169 char* zxid_mk_oauth2_dyn_cli_reg_res(zxid_conf* cf, zxid_cgi* cgi)
170 {
171   char* buf;
172   char* iat;
173   struct zx_str* client_id;
174   struct zx_str* client_secret;
175   int secs = time(0);
176 
177   /* *** check for valid IAT */
178 
179   if (!cgi->post) {
180     ERR("Missing POST content %d",0);
181     return 0;
182   }
183 
184   client_id = zxid_mk_id(cf, "CI", ZXID_ID_BITS);
185   client_secret = zxid_mk_id(cf, "CS", ZXID_ID_BITS);
186   iat = getenv("HTTP_AUTHORIZATION");
187 
188   buf = zx_alloc_sprintf(cf->ctx, 0,
189 			 "{\"client_id\":\"%.*s\""
190 			 ",\"client_secret\":\"%.*s\""
191 			 ",\"client_id_issued_at\":%d"
192 			 ",\"client_secret_expires_at\":%d"
193 			 ",\"client_src_ip\":\"%s\""
194 			 ",\"client_iat\":\"%s\""
195 			 ",%s",
196 			 client_id->len, client_id->s,
197 			 client_secret->len, client_secret->s,
198 			 secs,
199 			 secs+86400,
200 			 cf->ipport,
201 			 STRNULLCHK(iat),
202 			 cgi->post+1);
203 
204   if (!write_all_path("dyn_cli_reg", "%s" ZXID_DCR_DIR "%s", cf->cpath, client_id->s, -1, buf)) {
205     zxlog(cf, 0, 0, 0, 0, 0, 0, 0, "N", "S", "DCR", client_id->s, "writing dyn cli reg fail, permissions?");
206   } else
207     zxlog(cf, 0, 0, 0, 0, 0, 0, 0, "N", "K", "DCR", client_id->s, "ip(%s)", cf->ipport);
208   ZX_FREE(cf->ctx, client_id);
209   ZX_FREE(cf->ctx, client_secret);
210   return buf;
211 }
212 
213 /*() Create OAUTH2 Resource Set Registration request.
214  * See: https://tools.ietf.org/html/draft-hardjono-oauth-resource-reg-03
215  * The scope URL should point to scope description (created by hand
216  * and put to the server in right place), e.g. at https://server/scope/scope.json
217  * {"name":"Human Readable Scope Name","icon_uri":"https://server/scope/scope.png"}
218  * N.B. If you want to pass more than one scope, you have to include "," in middle, e.g.
219  * "https://server/scope/read.json\",\"https://server/scope/write.json" */
220 
221 /* Called by:  */
zxid_mk_oauth2_rsrc_reg_req(zxid_conf * cf,const char * rsrc_name,const char * rsrc_icon_uri,const char * rsrc_scope_url,const char * rsrc_type)222 char* zxid_mk_oauth2_rsrc_reg_req(zxid_conf* cf, const char* rsrc_name, const char* rsrc_icon_uri, const char* rsrc_scope_url, const char* rsrc_type)
223 {
224   char* buf;
225   buf = zx_alloc_sprintf(cf->ctx, 0,
226 			 "{\"name\":\"%s\""
227 			 ",\"icon_uri\":\"%s\""
228 			 ",\"scopes\":[\"%s\"]"
229 			 ",\"type\":\"%s\"}",
230 			 rsrc_name,
231 			 rsrc_icon_uri,
232 			 rsrc_scope_url,
233 			 rsrc_type);
234   return buf;
235 }
236 
237 /*() Perform the registration and create OAUTH2 Resource Set Registration Response.
238  * The unparsed JSON for request is in the cgi->post field.
239  * See: https://tools.ietf.org/html/draft-hardjono-oauth-resource-reg-03 */
240 
zxid_mk_oauth2_rsrc_reg_res(zxid_conf * cf,zxid_cgi * cgi,char * rev)241 char* zxid_mk_oauth2_rsrc_reg_res(zxid_conf* cf, zxid_cgi* cgi, char* rev)
242 {
243   char* buf;
244   char* pat;
245   struct zx_str* rs_id;
246 
247   /* *** check for IAT */
248 
249   if (!cgi->post) {
250     ERR("Missing POST content %d",0);
251     return 0;
252   }
253 
254   rs_id = zxid_mk_id(cf, "RS", ZXID_ID_BITS);
255   pat = getenv("HTTP_AUTHORIZATION");
256   strcpy(rev, "r1");
257   D("rs_id(%.*s) rev(%s) pat(%s)", rs_id->len, rs_id->s, rev, pat);
258 
259   // *** TODO: Check PAT
260   // *** TODO: Add registerer's (usually resource server) identity to path
261   if (!write_all_path("rsrc_reg", "%s" ZXID_RSR_DIR "%s", cf->cpath, rs_id->s, -1, cgi->post)) {
262     zxlog(cf, 0, 0, 0, 0, 0, 0, 0, "N", "S", "RSR", rs_id->s, "writing resource reg fail, permissions?");
263   } else
264     zxlog(cf, 0, 0, 0, 0, 0, 0, 0, "N", "K", "RSR", rs_id->s, "ip(%s)", cf->ipport);
265 
266   buf = zx_alloc_sprintf(cf->ctx, 0,
267 			 "{\"status\":\"created\""
268 			 ",\"_id\":\"%.*s\""
269 			 ",\"_rev\":\"%s\"",
270 			 ",\"policy_uri\":\"%s%co=consent\"}",
271 			 rs_id->len, rs_id->s,
272 			 rev,
273 			 cf->burl, strchr(cf->burl, '?')?'&':'?');
274   ZX_FREE(cf->ctx, rs_id);
275   return buf;
276 }
277 
278 #endif
279 
280 /*() Interpret ZXID standard form fields to construct an OAuth2 Authorization request,
281  * Which is a redirection URL */
282 
283 /* Called by:  zxid_start_sso_url */
zxid_mk_oauth_az_req(zxid_conf * cf,zxid_cgi * cgi,struct zx_str * loc,char * relay_state)284 struct zx_str* zxid_mk_oauth_az_req(zxid_conf* cf, zxid_cgi* cgi, struct zx_str* loc, char* relay_state)
285 {
286   struct zx_str* ss;
287   struct zx_str* nonce;
288   struct zx_str* eid;
289   char* eid_url_enc;
290   char* redir_url_enc;
291   char* state_b64;
292   char* prompt;
293   char* display;
294 
295   if (!loc) {
296     ERR("Redirection location URL missing. %d", 0);
297     return 0;
298   }
299 
300   redir_url_enc = zx_url_encode(cf->ctx, strlen(cf->burl), cf->burl, 0);
301   eid = zxid_my_ent_id(cf);
302   eid_url_enc = zx_url_encode(cf->ctx, eid->len, eid->s, 0);
303   zx_str_free(cf->ctx, eid);
304 
305   if (relay_state)
306     state_b64 = zxid_deflate_safe_b64_raw(cf->ctx, strlen(relay_state), relay_state);
307   else
308     state_b64 = 0;
309   nonce = zxid_mk_id(cf, "OA", ZXID_ID_BITS);
310   prompt = BOOL_STR_TEST(cgi->force_authn) ? "login" : 0;
311   prompt = BOOL_STR_TEST(cgi->consent && cgi->consent[0]) ? (prompt?"login+consent":"consent") : prompt;
312   display = BOOL_STR_TEST(cgi->ispassive) ? "none" : 0;
313 
314   ss = zx_strf(cf->ctx,
315 	       "%.*s%cresponse_type=%s"
316 	       "&client_id=%s"
317 	       "&scope=openid+profile+email+address"
318 	       "&redirect_uri=%s%%3fo=O"
319 	       "&nonce=%.*s"
320 	       "%s%s"           /* &state= */
321 	       "%s%s"           /* &display= */
322 	       "%s%s",          /* &prompt= */
323 	       loc->len, loc->s, (memchr(loc->s, '?', loc->len)?'&':'?'),
324 	       cgi->pr_ix == ZXID_OIDC1_CODE ? "code" : "id_token+token",
325 	       eid_url_enc,
326 	       redir_url_enc,
327 	       nonce->len, nonce->s,
328 	       state_b64?"&state=":"", STRNULLCHK(state_b64),
329 	       display?"&display=":"", STRNULLCHK(display),
330 	       prompt?"&prompt=":"", STRNULLCHK(prompt)
331 	       );
332   D("OAUTH2 AZ REQ(%.*s)", ss->len, ss->s);
333   if (errmac_debug & ERRMAC_INOUT) INFO("%.*s", ss->len, ss->s);
334   zx_str_free(cf->ctx, nonce);
335   ZX_FREE(cf->ctx, state_b64);
336   ZX_FREE(cf->ctx, eid_url_enc);
337   ZX_FREE(cf->ctx, redir_url_enc);
338   return ss;
339 }
340 
341 /*() Decode JWT */
342 
zxid_decode_jwt(zxid_conf * cf,zxid_cgi * cgi,zxid_ses * ses,const char * jwt)343 char* zxid_decode_jwt(zxid_conf* cf, zxid_cgi* cgi, zxid_ses* ses, const char* jwt)
344 {
345   int len;
346   char* buf;
347   char* p;
348 
349   if (!jwt) {
350     ERR("Missing JWT %d", 0);
351     return 0;
352   }
353   p = strchr(jwt, '.');
354   if (!p) {
355     ERR("Malformed JWT (missing period separating header and claims) jwt(%s)", jwt);
356     return 0;
357   }
358   len = strlen(p);
359   buf = ZX_ALLOC(cf->ctx, SIMPLE_BASE64_PESSIMISTIC_DECODE_LEN(len));
360   p = unbase64_raw(p, p+len, buf, zx_std_index_64);
361   *p = 0;
362   return buf;
363 }
364 
365 /*() Construct OAUTH2 / OpenID-Connect1 id_token. */
366 
367 /* Called by:  zxid_sso_issue_jwt */
zxid_mk_jwt(zxid_conf * cf,int claims_len,const char * claims)368 char* zxid_mk_jwt(zxid_conf* cf, int claims_len, const char* claims)
369 {
370   char hash[64 /*EVP_MAX_MD_SIZE*/];
371   char* jwt_hdr;
372   int hdr_len;
373   char* b64;
374   char* p;
375   int len = SIMPLE_BASE64_LEN(claims_len);
376 
377   switch (cf->oaz_jwt_sigenc_alg) {
378   case 'n':
379     jwt_hdr = "{\"typ\":\"JWT\",\"alg\":\"none\"}";
380     hdr_len = strlen(jwt_hdr);
381     len += SIMPLE_BASE64_LEN(hdr_len) + 1 + 1;
382     break;
383   case 'h':
384     jwt_hdr = "{\"typ\":\"JWT\",\"alg\":\"HS256\"}";
385     hdr_len = strlen(jwt_hdr);
386     len += SIMPLE_BASE64_LEN(hdr_len) + 1 + 1 + 86 /* siglen conservative estimate */;
387     break;
388   case 'r':
389     jwt_hdr = "{\"typ\":\"JWT\",\"alg\":\"RS256\"}";
390     hdr_len = strlen(jwt_hdr);
391     len += SIMPLE_BASE64_LEN(hdr_len) + 1 + 1 + 500 /* siglen conservative estimate */;
392     break;
393   default:
394     ERR("Unrecognized OAZ_JWT_SIGENC_ALG spec(%c). See zxid-conf.pd or zxidconf.h for documentation.", cf->oaz_jwt_sigenc_alg);
395     return 0;
396   }
397 
398   b64 = ZX_ALLOC(cf->ctx, len+1);
399   p = base64_fancy_raw(jwt_hdr, hdr_len, b64, safe_basis_64, 1<<31, 0, 0, 0);
400   *p++ = '.';
401   p = base64_fancy_raw(claims, claims_len, p, safe_basis_64, 1<<31, 0, 0, 0);
402   *p = 0;
403 
404   switch (cf->oaz_jwt_sigenc_alg) {
405   case 'n':
406     *p++ = '.';
407     *p = 0;
408     break;
409   case 'h':
410     if (!cf->hmac_key[0])
411       zx_get_symkey(cf, "hmac.key", cf->hmac_key);
412     zx_hmac_sha256(cf->ctx, ZX_SYMKEY_LEN, cf->hmac_key, p-b64, b64, hash, &len);
413     *p++ = '.';
414     p = base64_fancy_raw(hash, len, p, safe_basis_64, 1<<31, 0, 0, 0);
415     *p = 0;
416     break;
417   case 'r':
418     ERR("*** RSA not implemented yet %d",0);
419     zx_hmac_sha256(cf->ctx, ZX_SYMKEY_LEN, cf->hmac_key, p-b64, b64, hash, &len);
420     *p++ = '.';
421     p = base64_fancy_raw(hash, len, p, safe_basis_64, 1<<31, 0, 0, 0);
422     *p = 0;
423     break;
424   }
425   D("JWT(%s)", b64);
426   return b64;
427 }
428 
429 /*() Issue OAUTH2 / OpenID-Connect1 (OIDC1) id_token. logpathp is used
430  * to return the path to the token so it can be remembered by AZC */
431 
432 /* 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,struct zx_str ** logpathp)433 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, struct zx_str** logpathp)
434 {
435   int rawlen;
436   char* buf;
437   char* jwt;
438   char* jwt_id; /* sha1 hash of the jwt, taken from log_path */
439   struct zx_str issuer;
440   struct zx_str* affil;
441   char* eid;
442   struct zx_str ss;
443   struct zx_str nn;
444   struct zx_str id;
445   zxid_nid* tmpnameid;
446   char sp_name_buf[ZXID_MAX_SP_NAME_BUF];
447 
448   *logpathp = 0;
449   D("sp_eid(%s)", sp_meta->eid);
450   if (!nameid)
451     nameid = &tmpnameid;
452 
453   //if (ar && ar->IssueInstant && ar->IssueInstant->g.len && ar->IssueInstant->g.s)
454   //  srcts->tv_sec = zx_date_time_to_secs(ar->IssueInstant->g.s);
455 
456   if (!cgi->allow_create)
457     cgi->allow_create = '1';
458   if (!cgi->nid_fmt || !cgi->nid_fmt[0])
459     cgi->nid_fmt = "prstnt";  /* Persistent is the default implied by the specs. */
460 
461   /* Check for federation. */
462 
463   issuer.s = cgi->client_id; issuer.len = strlen(cgi->client_id);
464   affil = &issuer;
465   zxid_nice_sha1(cf, sp_name_buf, sizeof(sp_name_buf), affil, affil, 7);
466   D("sp_name_buf(%s)  allow_create=%d", sp_name_buf, cgi->allow_create);
467 
468   *nameid = zxid_get_fed_nameid(cf, &issuer, affil, ses->uid, sp_name_buf, cgi->allow_create,
469 				(cgi->nid_fmt && !strcmp(cgi->nid_fmt, "trnsnt")),
470 				srcts, 0, logop);
471   if (logop) { logop[3]='S';  logop[4]='S';  logop[5]='O';  logop[6]=0;  /* Patch in SSO */ }
472   if (!*nameid) {
473     ERR("get_fed_nameid() client_id(%s) returned NULL", cgi->client_id);
474     return 0;
475   }
476 
477   eid = zxid_my_ent_id_cstr(cf);
478   // ,\"\":\"\"
479   buf = zx_alloc_sprintf(cf->ctx, &rawlen,
480 		       "{\"iss\":\"%s\""
481 		       ",\"user_id\":\"%.*s\""
482 		       ",\"aud\":\"%s\""
483 		       ",\"exp\":%d"
484 		       ",\"nonce\":\"%s\"}",
485 		       eid,
486 		       ZX_GET_CONTENT_LEN(*nameid), ZX_GET_CONTENT_S(*nameid),
487 		       cgi->client_id,
488 		       time(0) + cf->timeskew + cf->a7nttl,
489 		       cgi->nonce);
490   ZX_FREE(cf->ctx, eid);
491   jwt = zxid_mk_jwt(cf, rawlen, buf);
492   ZX_FREE(cf->ctx, buf);
493 
494   /* Log the issued JWT */
495 
496   ss.s = jwt; ss.len = strlen(jwt);
497   *logpathp = zxlog_path(cf, &issuer, &ss, ZXLOG_ISSUE_DIR, ZXLOG_JWT_KIND, 1);
498   if (!*logpathp) {
499     ERR("Could not generate logpath for aud(%s) JWT(%s)", cgi->client_id, jwt);
500     ZX_FREE(cf->ctx, jwt);
501     return 0;
502   }
503 
504   /* Since JWT does not have explicit ID attribute, we use the sha1 hash of the
505    * contents of JWT as an ID. Since this is what logpath also does, we just
506    * use the last component of the logpath. */
507   for (jwt_id = (*logpathp)->s + (*logpathp)->len; jwt_id > (*logpathp)->s && jwt_id[-1] != '/'; --jwt_id) ;
508 
509   if (cf->log_issue_a7n) {
510     if (zxlog_dup_check(cf, *logpathp, "sso_issue_jwt")) {
511       ERR("Duplicate JWT ID(%s)", jwt_id);
512       if (cf->dup_a7n_fatal) {
513 	ERR("FATAL (by configuration): Duplicate JWT ID(%s)", jwt_id);
514 	zxlog_blob(cf, 1, *logpathp, &ss, "sso_issue_JWT dup");
515 	zx_str_free(cf->ctx, *logpathp);
516 	ZX_FREE(cf->ctx, jwt);
517 	return 0;
518       }
519     }
520     zxlog_blob(cf, 1, *logpathp, &ss, "sso_issue_JWT");
521   }
522 
523   nn.s = cgi->nonce; nn.len = strlen(cgi->nonce);
524   id.s = jwt_id; id.len = strlen(jwt_id);
525 
526   if (cf->loguser)
527     zxlogusr(cf, ses->uid, 0, 0, 0, &issuer, &nn, &id,
528 	     ZX_GET_CONTENT(*nameid),
529 	     (cf->oaz_jwt_sigenc_alg!='n'?"U":"N"), "K", logop, 0, 0);
530 
531   zxlog(cf, 0, 0, 0, &issuer, &nn, &id,
532 	ZX_GET_CONTENT(*nameid),
533 	(cf->oaz_jwt_sigenc_alg!='n'?"U":"N"), "K", logop, 0, 0);
534 
535   return jwt;
536 }
537 
538 /*() Issue OAUTH2 / OpenID-Connect1 (OIDC1) Authorization Code.
539  * The code will be stored in /var/zxid/log/issue/azc/SHA1AZC
540  * and contains pointers to actual tokens (they can be retrieved later using AZC).
541  * The buffer at azc will be modified in place. */
542 
543 /* Called by:  zxid_oauth2_az_server_sso */
zxid_sso_issue_azc(zxid_conf * cf,zxid_cgi * cgi,zxid_ses * ses,zxid_nid * nameid,const char * id_token_path,char * azc)544 int zxid_sso_issue_azc(zxid_conf* cf, zxid_cgi* cgi, zxid_ses* ses, zxid_nid* nameid, const char* id_token_path, char* azc)
545 {
546   int rawlen;
547   char* buf;
548   char* azc_id; /* sha1 hash of the azc, taken from logpath */
549   struct zx_str* logpath;
550   struct zx_str sp;
551   struct zx_str ss;
552   //struct zx_str id;
553 
554   /* Authorization code points to a file that contains paths to tokens, query string format */
555 
556   buf = zx_alloc_sprintf(cf->ctx, &rawlen,
557 			 "id_token_path=%s",
558 			 id_token_path);
559 
560   /* Log the issued Authorization Code */
561 
562 #if 0
563   sp.s = cgi->client_id;
564 #else
565   sp.s = "fixed";  // *** since we have difficulty knowing client_id in token_endpoint, we just use fixed value
566 #endif
567   sp.len = strlen(sp.s);
568   ss.s = buf; ss.len = rawlen;
569   logpath = zxlog_path(cf, &sp, &ss, ZXLOG_ISSUE_DIR, ZXLOG_AZC_KIND, 1);
570   if (!logpath) {
571     ERR("Could not generate logpath for aud(%s) AZC(%s)", cgi->client_id, buf);
572     ZX_FREE(cf->ctx, buf);
573     return 0;
574   }
575 
576   /* Since AZC does not have explicit ID attribute, we use the sha1 hash of the
577    * contents of AZC as an ID. Since this is what logpath also does, we just
578    * use the last component of the logpath. */
579   for (azc_id = logpath->s + logpath->len; azc_id > logpath->s && azc_id[-1] != '/'; --azc_id) ;
580 
581 #if 1
582   /* *** does it make sense to duplicate check Authorization Codes? */
583   if (cf->log_issue_a7n) {
584     if (zxlog_dup_check(cf, logpath, "sso_issue_azc")) {
585       ERR("Duplicate AZC ID(%s)", azc_id);
586       if (cf->dup_a7n_fatal) {
587 	ERR("FATAL (by configuration): Duplicate AZC ID(%s)", azc_id);
588 	zxlog_blob(cf, 1, logpath, &ss, "issue_azc dup");
589 	zx_str_free(cf->ctx, logpath);
590 	ZX_FREE(cf->ctx, buf);
591 	return 0;
592       }
593     }
594     zxlog_blob(cf, 1, logpath, &ss, "issue_azc");
595   }
596 #endif
597 
598   //id.s = azc_id; id.len = strlen(azc_id);
599   if (cf->loguser)
600     zxlogusr(cf, ses->uid, 0, 0, 0, 0, 0, 0, 0, "N", "K", "azc", 0, 0);
601 
602   zxlog(cf, 0, 0, 0, 0, 0, 0, 0, "N", "K", "azc", 0, 0);
603   strcpy(azc, azc_id);
604   zx_str_free(cf->ctx, logpath);
605   ZX_FREE(cf->ctx, buf);
606   return 1;
607 }
608 
609 /*(i) Generate SSO assertion and ship it to SP by OAUTH2 Az redir binding. User has already
610  * logged in by the time this is called. See also zxid_ssos_anreq() and zxid_idp_sso(). */
611 
612 /* Called by:  zxid_idp_dispatch */
zxid_oauth2_az_server_sso(zxid_conf * cf,zxid_cgi * cgi,zxid_ses * ses)613 struct zx_str* zxid_oauth2_az_server_sso(zxid_conf* cf, zxid_cgi* cgi, zxid_ses* ses)
614 {
615   zxid_entity* sp_meta;
616   struct zx_str* acsurl = 0;
617   struct timeval srcts = {0,501000};
618   zxid_nid* nameid;
619   char* idtok;
620   struct zx_str* jwt_logpath = 0;
621   char  azc[1024];
622   char logop[8];
623   strcpy(logop, "OAZxxxx");
624 
625   if (!cgi->client_id || !cgi->redirect_uri || !cgi->nonce) {
626     ERR("Missing mandatory OAUTH2 field client_id=%p redirect_uri=%p nonce=%p", cgi->client_id, cgi->redirect_uri, cgi->nonce);
627     goto err;
628   }
629 
630   if (!cgi->response_type) {
631     ERR("Missing mandatory OAUTH2 field response_type %d", 0);
632     goto err;
633   }
634 
635   if (!cgi->scope || !strstr(cgi->scope, "openid")) {
636     ERR("Missing mandatory OAUTH2 field scope=%p or the scope does not contain `openid'", cgi->scope);
637     goto err;
638   }
639 
640   sp_meta = zxid_get_ent(cf, cgi->client_id);
641   if (!sp_meta) {
642     ERR("The metadata for client_id(%s) of the Az Req could not be found or fetched", cgi->client_id);
643     goto err;
644   }
645   D("sp_eid(%s)", sp_meta->eid);
646 
647   /* Figure out the binding and url */
648 
649   acsurl = zxid_sp_loc_raw(cf, cgi, sp_meta, ZXID_ACS_SVC, OAUTH2_REDIR, 0);
650   if (!acsurl) {
651     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);
652     goto err;
653   }
654   if (strlen(cgi->redirect_uri) != acsurl->len || memcmp(cgi->redirect_uri, acsurl->s, acsurl->len)) {
655     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);
656     goto err;
657   }
658 
659   if (!cf->log_issue_a7n) {
660     INFO("LOG_ISSUE_A7N must be turned on in IdP configuration for artifact profile to work. Turning on now automatically. %d", 0);
661     cf->log_issue_a7n = 1;
662   }
663 
664   /* User ses->uid is already logged in, now check for federation with sp */
665 
666   idtok = zxid_sso_issue_jwt(cf, cgi, ses, &srcts, sp_meta, acsurl, &nameid, logop, &jwt_logpath);
667   if (!idtok) {
668     ERR("Issuing JWT Failed %s", logop);
669     goto err;
670   }
671 
672   /* *** check that cgi->redirect_uri is authorized in metadata */
673 
674   if (strstr(cgi->response_type, "code")) {
675     D("OAUTH2-ART ep(%.*s)", acsurl->len, acsurl->s);
676 
677     /* Assign az_code and store the tokens for future retrieval */
678 
679     if (!zxid_sso_issue_azc(cf, cgi, ses, nameid, jwt_logpath->s, azc)) {
680       ERR("Issuing AZC Failed %s", logop);
681       goto err;
682     }
683 
684     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 code=%s", azc);
685 
686     /* Formulate OAUTH2 / OpenID-Connect1 Az Redir Response containing Authorization Code
687      * which will later need to be dereferenced to obtain the actual tokens. */
688 
689     if (jwt_logpath)
690       zx_str_free(cf->ctx, jwt_logpath);
691     return zx_strf(cf->ctx, "Location: %s%c"
692 		   "code=%s"
693 		   "%s%s" CRLF  /* state */
694 		   "%s%s%s",    /* Set-Cookie */
695 		   cgi->redirect_uri, (strchr(cgi->redirect_uri, '?') ? '&' : '?'),
696 		   azc,
697 		   cgi->state?"&state=":"", STRNULLCHK(cgi->state),
698 		   ses->setcookie?"Set-Cookie: ":"", ses->setcookie?ses->setcookie:"", ses->setcookie?CRLF:"");
699 
700   }
701 
702   if (strstr(cgi->response_type, "token") && strstr(cgi->response_type, "id_token")) {
703     D("OAUTH2-IMPL ep(%.*s)", acsurl->len, acsurl->s);
704     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-IMPL");
705 
706     /* Formulate OAUTH2 / OpenID-Connect1 Az Redir Response directly containing tokens */
707 
708     if (jwt_logpath)
709       zx_str_free(cf->ctx, jwt_logpath);
710     return zx_strf(cf->ctx, "Location: %s%c"
711 		   "access_token=%s"
712 		   "&token_type=bearer"
713 		   "&id_token=%s"
714 		   "&expires_in=%d" CRLF
715 		   "%s%s%s",   /* Set-Cookie */
716 		   cgi->redirect_uri, (strchr(cgi->redirect_uri, '?') ? '&' : '?'),
717 		   idtok,
718 		   idtok,
719 		   cf->a7nttl,
720 		   ses->setcookie?"Set-Cookie: ":"", ses->setcookie?ses->setcookie:"", ses->setcookie?CRLF:"");
721   }
722 
723   ERR("OAUTH2 field response_type(%s) missing or does not contain `code' or `token id_token'", STRNULLCHKD(cgi->response_type));
724  err:
725   if (jwt_logpath)
726     zx_str_free(cf->ctx, jwt_logpath);
727   return zx_dup_str(cf->ctx, "* ERR");
728 }
729 
730 #define UMA_WELL_KNOWN "/.well-known/uma-configuration"
731 
732 /*() Extract a metadata item from well known location.
733  *
734  * cf:: ZXID configuration object, for memory allocation
735  * base_uri:: scheme, domain name and port of server whose metadata we are looking for.
736  *     For example, the value of as_uri returned in WWW-Authenticate HTTP response header.
737  * key:: Name of the metadata item, must include double quoted, e.g. "\"rpt_endpoint\""
738  * return:: c string, zx allocated. Caller must free.
739  *
740  * N.B. This function is very simplistic as it does not cache the metadata in any way.
741  */
742 
zxid_oauth_get_well_known_item(zxid_conf * cf,const char * base_uri,const char * key)743 char* zxid_oauth_get_well_known_item(zxid_conf* cf, const char* base_uri, const char* key)
744 {
745   int len;
746   char* p;
747   char endpoint[4096];
748   struct zx_str* res;
749 
750   len = strlen(base_uri);
751   if (len + sizeof(UMA_WELL_KNOWN) > sizeof(endpoint)-1) {
752     ERR("base_uri too long %d", len);
753     return 0;
754   }
755   memcpy(endpoint, base_uri, len);
756   p = endpoint + len -1;
757   if (*p != '/')
758     ++p;
759   strcpy(p, UMA_WELL_KNOWN);
760   res = zxid_http_cli(cf, -1, endpoint, -1, 0, 0, 0, 0);
761   D("base_uri(%s) endpoint(%s) res(%.*s) key(%s)", base_uri, endpoint, res?res->len:0, res?res->s:"", key);
762 
763   return zx_json_extract_dup(cf->ctx, res->s, key);
764 }
765 
766 char* iat = 0;
767 char* _uma_authn = 0;
768 
zxid_oauth_dynclireg_client(zxid_conf * cf,zxid_cgi * cgi,zxid_ses * ses,const char * as_uri)769 struct zx_str* zxid_oauth_dynclireg_client(zxid_conf* cf, zxid_cgi* cgi, zxid_ses* ses, const char* as_uri)
770 {
771   struct zx_str* res;
772   char* azhdr;
773   char* req = zxid_mk_oauth2_dyn_cli_reg_req(cf);
774   char* url = zxid_oauth_get_well_known_item(cf, as_uri, "\"dynamic_client_endpoint\"");
775   char* p;
776   if (iat) {
777     azhdr = zx_alloc_sprintf(cf->ctx, 0, "Authorization: Bearer %s", iat);
778   } else
779     azhdr = 0;
780   DD("url(%s) req(%s) iat(%s)", url, req, STRNULLCHKD(azhdr));
781   if (_uma_authn) {
782     p = url;
783     url = zx_alloc_sprintf(cf->ctx, 0, "%s%c_uma_authn=%s", url, strchr(url,'?')?'&':'?', _uma_authn);
784     ZX_FREE(cf->ctx, p);
785   }
786   D("url(%s) req(%s) iat(%s)", url, req, STRNULLCHKD(azhdr));
787   res = zxid_http_cli(cf, -1, url, -1, req, ZXID_JSON_CONTENT_TYPE, azhdr, 0);
788   ZX_FREE(cf->ctx, url);
789   if (azhdr) ZX_FREE(cf->ctx, azhdr);
790   ZX_FREE(cf->ctx, req);
791   D("%.*s", res->len, res->s);
792   ses->client_id = zx_json_extract_dup(cf->ctx, res->s, "\"client_id\"");
793   ses->client_secret = zx_json_extract_dup(cf->ctx, res->s, "\"client_secret\"");
794   return res;
795 }
796 
zxid_oauth_rsrcreg_client(zxid_conf * cf,zxid_cgi * cgi,zxid_ses * ses,const char * as_uri,const char * rsrc_name,const char * rsrc_icon_uri,const char * rsrc_scope_url,const char * rsrc_type)797 void zxid_oauth_rsrcreg_client(zxid_conf* cf, zxid_cgi* cgi, zxid_ses* ses, const char* as_uri, const char* rsrc_name, const char* rsrc_icon_uri, const char* rsrc_scope_url, const char* rsrc_type)
798 {
799   struct zx_str* res;
800   char* restful_url;
801   char* azhdr;
802   char* b64;
803   char* req = zxid_mk_oauth2_rsrc_reg_req(cf, rsrc_name, rsrc_icon_uri, rsrc_scope_url, rsrc_type);
804   char* url = zxid_oauth_get_well_known_item(cf, as_uri, "\"resource_set_registration_endpoint\"");
805   if (ses->access_token) {
806     azhdr = zx_alloc_sprintf(cf->ctx, 0, "Authorization: Bearer %s", ses->access_token);
807   } else if (iat) {
808     azhdr = zx_alloc_sprintf(cf->ctx, 0, "Authorization: Bearer %s", iat);
809   } else if (ses->client_id && ses->client_secret) {
810     b64 = zx_mk_basic_auth_b64(cf->ctx, ses->client_id, ses->client_secret);
811     azhdr = zx_alloc_sprintf(cf->ctx, 0, "Authorization: Basic %s", b64);
812     ZX_FREE(cf->ctx, b64);
813   } else
814     azhdr = 0;
815   D("req(%s) azhdr(%s)", req, STRNULLCHKD(azhdr));
816 
817   restful_url = zx_alloc_sprintf(cf->ctx, 0, "%s/resource_set/%s", url, rsrc_name);
818   ZX_FREE(cf->ctx, url);
819   res = zxid_http_cli(cf, -1, restful_url, -1, req, ZXID_JSON_CONTENT_TYPE, azhdr, 0);
820   ZX_FREE(cf->ctx, restful_url);
821   if (azhdr) ZX_FREE(cf->ctx, azhdr);
822   ZX_FREE(cf->ctx, req);
823   D("%.*s", res->len, res->s);
824 }
825 
826 /*() Call OAUTH2 / UMA1 Resource Protection Token Endpoint and return a token
827  * *** still needs a lot of work to turn more generic */
828 
zxid_oauth_call_rpt_endpoint(zxid_conf * cf,zxid_ses * ses,const char * host_id,const char * as_uri)829 char* zxid_oauth_call_rpt_endpoint(zxid_conf* cf, zxid_ses* ses, const char* host_id, const char* as_uri)
830 {
831   struct zx_str* res;
832   char* azhdr;
833   char* b64;
834   char* rpt_endpoint = zxid_oauth_get_well_known_item(cf, as_uri, "\"rpt_endpoint\"");
835 
836   if (ses->access_token) {
837     azhdr = zx_alloc_sprintf(cf->ctx, 0, "Authorization: Bearer %s", ses->access_token);
838   } else if (iat) {
839     azhdr = zx_alloc_sprintf(cf->ctx, 0, "Authorization: Bearer %s", iat);
840   } else if (ses->client_id && ses->client_secret) {
841     b64 = zx_mk_basic_auth_b64(cf->ctx, ses->client_id, ses->client_secret);
842     azhdr = zx_alloc_sprintf(cf->ctx, 0, "Authorization: Basic %s", b64);
843     ZX_FREE(cf->ctx, b64);
844   } else
845     azhdr = 0;
846 
847   //if (!ses->client_id || !ses->client_secret)
848   //  zxid_oauth_dynclireg_client(cf, cgi, ses, as_uri);
849   // *** Client acquires AAT
850 
851 #if 0
852   snprintf(req, sizeof(buf),
853 	   "client_id=%s&client_secret=%s",
854 	   ses->client_id, ses->client_secret);
855 #endif
856   D("azhdr(%s)", STRNULLCHKD(azhdr));
857 
858   res = zxid_http_cli(cf, -1, rpt_endpoint, -1, "", 0, azhdr, 0);
859   D("%.*s", res->len, res->s);
860 
861   /* Extract the fields as if it had been implicit mode SSO */
862   ses->rpt = zx_json_extract_dup(cf->ctx, res->s, "\"rpt\"");
863   // *** check validity
864   return "OK";
865 }
866 
867 /*() Call OAUTH2 / UMA1 Resource Protection Token Endpoint and return a token
868  * *** still needs a lot of work to turn more generic */
869 
zxid_oauth_call_az_endpoint(zxid_conf * cf,zxid_ses * ses,const char * host_id,const char * as_uri,const char * ticket)870 char* zxid_oauth_call_az_endpoint(zxid_conf* cf, zxid_ses* ses, const char* host_id, const char* as_uri, const char* ticket)
871 {
872   char* req;
873   struct zx_str* res;
874   char* azhdr;
875   char* b64;
876   char* az_endpoint = zxid_oauth_get_well_known_item(cf, as_uri, "\"authorization_request_endpoint\"");
877 
878   if (ses->access_token) {
879     azhdr = zx_alloc_sprintf(cf->ctx, 0, "Authorization: Bearer %s", ses->access_token);
880   } else if (iat) {
881     azhdr = zx_alloc_sprintf(cf->ctx, 0, "Authorization: Bearer %s", iat);
882   } else if (ses->client_id && ses->client_secret) {
883     b64 = zx_mk_basic_auth_b64(cf->ctx, ses->client_id, ses->client_secret);
884     azhdr = zx_alloc_sprintf(cf->ctx, 0, "Authorization: Basic %s", b64);
885     ZX_FREE(cf->ctx, b64);
886   } else
887     azhdr = 0;
888 
889   //if (!ses->client_id || !ses->client_secret)
890   //  zxid_oauth_dynclireg_client(cf, cgi, ses, as_uri);
891   // *** Client acquires AAT
892 
893 #if 0
894   snprintf(req, sizeof(buf),
895 	   "client_id=%s&client_secret=%s",
896 	   ses->client_id, ses->client_secret);
897 #endif
898   req = zx_alloc_sprintf(cf->ctx, 0, "{\"rpt\":\"%s\",\"ticket\":\"%s\"}", ses->rpt, ticket);
899   D("req(%s) azhdr(%s)", req, STRNULLCHKD(azhdr));
900 
901   res = zxid_http_cli(cf, -1, az_endpoint, -1, req, 0, azhdr, 0);
902   ZX_FREE(cf->ctx, req);
903   D("%.*s", res->len, res->s);
904 
905   /* Extract the fields as if it had been implicit mode SSO */
906   ses->rpt = zx_json_extract_dup(cf->ctx, res->s, "\"rpt\"");
907   // *** check validity
908   return "OK";
909 }
910 
zxid_oidc_as_call(zxid_conf * cf,zxid_ses * ses,zxid_entity * idp_meta,const char * _uma_authn)911 int zxid_oidc_as_call(zxid_conf* cf, zxid_ses* ses, zxid_entity* idp_meta, const char* _uma_authn)
912 {
913   struct zx_md_SingleSignOnService_s* sso_svc;
914   struct zx_str* ss;
915   struct zx_str* req;
916   struct zx_str* res;
917   struct zxid_cgi* cgi;
918   struct zxid_cgi scgi;
919   ZERO(&scgi, sizeof(scgi));
920   cgi = &scgi;
921 
922   if (!idp_meta->ed->IDPSSODescriptor) {
923     ERR("Entity(%s) does not have IdP SSO Descriptor (OAUTH2) (metadata problem)", cgi->eid);
924     zxlog(cf, 0, 0, 0, 0, 0, 0, 0, "N", "B", "ERR", cgi->eid, "No IDPSSODescriptor (OAUTH2)");
925     cgi->err = "Bad IdP metadata (OAUTH). Try different IdP.";
926     D_DEDENT("start_sso: ");
927     return 0;
928   }
929   for (sso_svc = idp_meta->ed->IDPSSODescriptor->SingleSignOnService;
930        sso_svc;
931        sso_svc = (struct zx_md_SingleSignOnService_s*)sso_svc->gg.g.n) {
932     if (sso_svc->gg.g.tok != zx_md_SingleSignOnService_ELEM)
933       continue;
934     if (sso_svc->Binding && !memcmp(OAUTH2_REDIR,sso_svc->Binding->g.s,sso_svc->Binding->g.len))
935       break;
936   }
937   if (!sso_svc) {
938     ERR("IdP Entity(%s) does not have any IdP SSO Service with " OAUTH2_REDIR " binding (metadata problem)", cgi->eid);
939     zxlog(cf, 0, 0, 0, 0, 0, 0, 0, "N", "B", "ERR", cgi->eid, "No OAUTH2 redir binding");
940     cgi->err = "Bad IdP metadata. Try different IdP.";
941     D_DEDENT("start_sso: ");
942     return 0;
943   }
944   ss = &sso_svc->Location->g;
945   if (_uma_authn)
946     ss = zx_strf(cf->ctx, "%.*s%c_uma_authn=%s", ss->len, ss->s, (memchr(ss->s, '?', ss->len)?'&':'?'), _uma_authn);
947   cgi->pr_ix = ZXID_OIDC1_ID_TOK_TOK; //"id_token token";
948   D("loc(%.*s)", ss->len, ss->s);
949   req = zxid_mk_oauth_az_req(cf, cgi, ss, 0);
950   D("req(%.*s)", req->len, req->s);
951   res = zxid_http_cli(cf, req->len, req->s, 0,0, 0, 0, 0x03);  /* do not follow redir */
952   zx_str_free(cf->ctx, req);
953   D("res(%.*s)", res->len, res->s);
954   // *** extract token and AAT from the response
955   ses->access_token = zx_qs_extract_dup(cf->ctx, res->s, "access_token=");
956   ses->id_token = zx_qs_extract_dup(cf->ctx, res->s, "id_token=");
957   ses->token_type = zx_qs_extract_dup(cf->ctx, res->s, "token_type=");
958   //ses->expires = zx_qs_extract_dup(cf->ctx, res->s, "access_token=");
959   return 1;
960 }
961 
962 /*() Call OAUTH2 / UMA1 / OIDC1 Token Endpoint and return a token
963  * *** still needs a lot of work to turn more generic */
964 
zxid_oauth_call_token_endpoint(zxid_conf * cf,zxid_cgi * cgi,zxid_ses * ses)965 static int zxid_oauth_call_token_endpoint(zxid_conf* cf, zxid_cgi* cgi, zxid_ses* ses)
966 {
967   char* endpoint = "http://idp.tas3.pt:8081/zxididp?o=T";  // *** proper metadata lookup
968   char buf[4096];
969   struct zx_str* res;
970   char* azhdr;
971 #if 0
972   if (iat) {
973     azhdr = zx_alloc_sprintf(cf->ctx, 0, "Authorization: Bearer %s", client_secret);
974   } else
975 #endif
976     azhdr = 0;
977 
978   snprintf(buf, sizeof(buf),
979 	   "grant_type=authorization_code&code=%s&redirect_uri=%s",
980 	   cgi->code, cgi->redirect_uri);
981   res = zxid_http_cli(cf, -1, endpoint, -1, buf, 0, azhdr, 0);
982   D("%.*s", res->len, res->s);
983 
984   /* Extract the fields as if it had been implicit mode SSO */
985   ses->access_token = zx_json_extract_dup(cf->ctx, res->s, "\"access_token\"");
986   ses->refresh_token = zx_json_extract_dup(cf->ctx, res->s, "\"refresh_token\"");
987   ses->token_type = zx_json_extract_dup(cf->ctx, res->s, "\"token_type\"");
988   ses->expires_in = zx_json_extract_int(res->s, "\"expires_in\"");
989   ses->id_token = zx_json_extract_dup(cf->ctx, res->s, "\"id_token\"");
990   // *** check validity
991   return 1;
992 }
993 
994 /*() Finalize JWT based SSO, create session from the fields available in the JWT
995  * See also: zxid_sp_sso_finalize() in zxidsso.c */
996 
zxid_sp_sso_finalize_jwt(zxid_conf * cf,zxid_cgi * cgi,zxid_ses * ses,const char * jwt)997 int zxid_sp_sso_finalize_jwt(zxid_conf* cf, zxid_cgi* cgi, zxid_ses* ses, const char* jwt)
998 {
999   char* err = "S"; /* See: RES in zxid-log.pd, section "ZXID Log Format" */
1000   struct zx_str ss;
1001   struct timeval ourts;
1002   struct timeval srcts = {0,501000};
1003   struct zx_str* logpath;
1004   char* p;
1005   char* claims;
1006 
1007   ses->jwt = (char*)jwt;
1008   //ses->rs = ;
1009   ses->ssores = 1;
1010   GETTIMEOFDAY(&ourts, 0);
1011 
1012   D_INDENT("ssof: ");
1013 
1014   claims = zxid_decode_jwt(cf, cgi, ses, jwt);
1015   if (!claims) {
1016     ERR("JWT decode error jwt(%s)", STRNULLCHKD(jwt));
1017     goto erro;
1018   }
1019 
1020   //ses->nid = zx_json_extract_dup(cf->ctx, claims, "\"sub\"");
1021   ses->nid = zx_json_extract_dup(cf->ctx, claims, "\"user_id\"");
1022   if (!ses->nid) {
1023     ERR("JWT decode: no user_id found in jwt(%s)", STRNULLCHKD(jwt));
1024     goto erro;
1025   }
1026   ses->nidfmt = 1;  /* Assume federation */
1027 
1028   ses->tgtjwt = ses->jwt;
1029   ses->tgt = ses->nid;
1030   ses->tgtfmt = 1;  /* Assume federation */
1031 
1032   p = zx_json_extract_dup(cf->ctx, claims, "\"iss\"");
1033   ses->issuer = zx_ref_str(cf->ctx, p);
1034   if (!p) {
1035     ERR("JWT decode: no iss found in jwt(%s)", STRNULLCHKD(jwt));
1036     goto erro;
1037   }
1038 
1039   D("SSOJWT received. NID(%s) FMT(%d)", ses->nid, ses->nidfmt);
1040 
1041   // *** should some signature validation happen here, using issuer (idp) meta?
1042   cgi->sigval = "N";
1043   cgi->sigmsg = "Assertion was not signed.";
1044   ses->sigres = ZXSIG_NO_SIG;
1045 
1046   if (cf->log_rely_a7n) {
1047     DD("Logging rely... %d", 0);
1048     ss.s = (char*)jwt; ss.len = strlen(jwt);
1049     logpath = zxlog_path(cf, ses->issuer, &ss, ZXLOG_RELY_DIR, ZXLOG_JWT_KIND, 1);
1050     if (logpath) {
1051       ses->sso_a7n_path = ses->tgt_a7n_path = zx_str_to_c(cf->ctx, logpath);
1052       if (zxlog_dup_check(cf, logpath, "SSO JWT")) {
1053 	if (cf->dup_a7n_fatal) {
1054 	  err = "C";
1055 	  zxlog_blob(cf, cf->log_rely_a7n, logpath, &ss, "sp_sso_finalize_jwt dup err");
1056 	  goto erro;
1057 	}
1058       }
1059       zxlog_blob(cf, cf->log_rely_a7n, logpath, &ss, "sp_sso_finalize_jwt");
1060     }
1061   }
1062   DD("Creating session... %d", 0);
1063   ses->ssores = 0;
1064   zxid_put_ses(cf, ses);
1065   //*** zxid_snarf_eprs_from_ses(cf, ses);  /* Harvest attributes and bootstrap(s) */
1066   cgi->msg = "SSO completed and session created.";
1067   cgi->op = '-';  /* Make sure management screen does not try to redispatch. */
1068   zxid_put_user(cf, 0, 0, 0, zx_ref_str(cf->ctx, ses->nid), 0);
1069   DD("Logging... %d", 0);
1070   ss.s = ses->nid; ss.len = strlen(ss.s);
1071   zxlog(cf, &ourts, &srcts, 0, ses->issuer, 0, 0, &ss,
1072 	cgi->sigval, "K", "NEWSESJWT", ses->sid, "sesix(%s)", STRNULLCHKD(ses->sesix));
1073   zxlog(cf, &ourts, &srcts, 0, ses->issuer, 0, 0, &ss,
1074 	cgi->sigval, "K", ses->nidfmt?"FEDSSOJWT":"TMPSSOJWT", STRNULLCHKD(ses->sesix), 0);
1075 
1076 #if 0
1077   if (cf->idp_ena) {  /* (PXY) Middle IdP of Proxy IdP flow */
1078     if (cgi->rs && cgi->rs[0]) {
1079       D("ProxyIdP got RelayState(%s) ar(%s)", cgi->rs, STRNULLCHK(cgi->ssoreq));
1080       cgi->saml_resp = 0;  /* Clear Response to prevent re-interpretation. We want Request. */
1081       cgi->ssoreq = cgi->rs;
1082       zxid_decode_ssoreq(cf, cgi);
1083       cgi->op = 'V';
1084       D_DEDENT("ssof: ");
1085       return ZXID_IDP_REQ; /* Cause zxid_simple_idp_an_ok_do_rest() to be called from zxid_sp_dispatch(); */
1086     } else {
1087       INFO("Middle IdP of Proxy IdP flow did not receive RelayState from upstream IdP %p", cgi->rs);
1088     }
1089   }
1090 #endif
1091   D_DEDENT("ssof: ");
1092   return ZXID_SSO_OK;
1093 
1094 erro:
1095   ERR("SSOJWT fail (%s)", err);
1096   cgi->msg = "SSO failed. This could be due to signature, timeout, etc., technical failures, or by policy.";
1097   zxlog(cf, &ourts, &srcts, 0, ses->issuer, 0, 0, 0,
1098 	cgi->sigval, err, ses->nidfmt?"FEDSSOJWT":"TMPSSOJWT", STRNULLCHKD(ses->sesix), "Error.");
1099   D_DEDENT("ssof: ");
1100   return 0;
1101 }
1102 
1103 /*() Extract an assertion from OAUTH2 Az response, and perform SSO */
1104 
1105 /* Called by:  zxid_sp_oauth2_dispatch */
zxid_sp_dig_oauth_sso_a7n(zxid_conf * cf,zxid_cgi * cgi,zxid_ses * ses,char * jwt)1106 static int zxid_sp_dig_oauth_sso_a7n(zxid_conf* cf, zxid_cgi* cgi, zxid_ses* ses, char* jwt)
1107 {
1108   if (jwt)
1109     return zxid_sp_sso_finalize_jwt(cf, cgi, ses, jwt);
1110   if (cf->anon_ok && cgi->rs && !strcmp(cf->anon_ok, cgi->rs))  /* Prefix match */
1111     return zxid_sp_anon_finalize(cf, cgi, ses);
1112   ERR("No Assertion found and not anon_ok in OAUTH Response %d", 0);
1113   zxlog(cf, 0, 0, 0, 0, 0, 0, 0, "N", "C", "ERR", 0, "sid(%s) No JWT", STRNULLCHK(ses->sid));
1114   return 0;
1115 }
1116 
1117 /*() Dispatch, on RP/SP side, OAUTH2 redir or artifact binding requests.
1118  *
1119  * return:: a string (such as Location: header) and let the caller output it.
1120  *     Sometimes a dummy string is just output to indicate status, e.g.
1121  *     "O" for SSO OK, "K" for normal OK no further action needed,
1122  *     "M" show management screen, "I" forward to IdP dispatch, or
1123  *     "* ERR" for error situations. These special strings
1124  *     are allocated from static storage and MUST NOT be freed. Other
1125  *     strings such as "Location: ..." should be freed by caller. */
1126 
1127 /* Called by:  zxid_simple_no_ses_cf */
zxid_sp_oauth2_dispatch(zxid_conf * cf,zxid_cgi * cgi,zxid_ses * ses)1128 struct zx_str* zxid_sp_oauth2_dispatch(zxid_conf* cf, zxid_cgi* cgi, zxid_ses* ses)
1129 {
1130   int ret;
1131 
1132   if (cgi->code) {  /* OAUTH2 artifact / Authorization Code biding, aka OpenID-Connect1 */
1133     D("Dereference code(%s)", cgi->code);
1134     zxid_oauth_call_token_endpoint(cf, cgi, ses);  /* populates cgi->id_token */
1135   }
1136 
1137   if (cgi->id_token) {  /* OAUTH2 implicit binding (token inline in redir), aka OpenID-Connect1 */
1138     ret = zxid_sp_dig_oauth_sso_a7n(cf, cgi, ses, cgi->id_token);
1139     D("ret=%d ses=%p", ret, ses);
1140     switch (ret) {
1141     case ZXID_OK:      return zx_dup_str(cf->ctx, "K");
1142     case ZXID_SSO_OK:  return zx_dup_str(cf->ctx, "O");
1143     case ZXID_IDP_REQ: /* (PXY) Middle IdP of IdP Proxy flow */
1144       return zx_dup_str(cf->ctx, zxid_simple_ses_active_cf(cf, cgi, ses, 0, 0x1fff));
1145     case ZXID_FAIL:
1146       D("*** FAIL, should send back to IdP select %d", 0);
1147       return zx_dup_str(cf->ctx, "* ERR");
1148     }
1149     return zx_dup_str(cf->ctx, "M");  /* Management screen, please. */
1150   }
1151 
1152   if (cf->log_level > 0)
1153     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));
1154   ERR("Unknown request or response %d", 0);
1155   return zx_dup_str(cf->ctx, "* ERR");
1156 }
1157 
1158 /*() Handle, on IdP side, OAUTH2 / OpenID-Connect1 check_id and token requests.
1159  * This function is called by AS (IdP) in response to ?o=T
1160  *
1161  * return:: a string (such as Location: header) and let the caller output it.
1162  *     Sometimes a dummy string is just output to indicate status, e.g.
1163  *     "O" for SSO OK, "K" for normal OK no further action needed,
1164  *     "M" show management screen, "I" forward to IdP dispatch, or
1165  *     "* ERR" for error situations. These special strings
1166  *     are allocated from static storage and MUST NOT be freed. Other
1167  *     strings such as "Location: ..." should be freed by caller. */
1168 
1169 /* Called by:  zxid_simple_no_ses_cf */
zxid_idp_oauth2_token_and_check_id(zxid_conf * cf,zxid_cgi * cgi,zxid_ses * ses,int * res_len,int auto_flags)1170 char* zxid_idp_oauth2_token_and_check_id(zxid_conf* cf, zxid_cgi* cgi, zxid_ses* ses, int* res_len, int auto_flags)
1171 {
1172   char  sha_buf[28];
1173   char* buf;
1174   char* azc_data;
1175   char* id_token;
1176 
1177   /* *** to find the azc we need to know the requester. Presumably this
1178    * would be available from Authorization header, or perhaps Client-TLS */
1179   char* sp_eid = "fixed"; // "http://sp.tas3.pt:8081/zxidhlo?=o=B";
1180 
1181   if (cgi->grant_type && !strcmp(cgi->grant_type, "authorization_code") && cgi->code) {
1182     /* OAUTH2 / OIDC1 Authorization Code / artifact binding */
1183 
1184     sha1_safe_base64(sha_buf, -2, sp_eid);
1185     sha_buf[27] = 0;
1186     azc_data = read_all_alloc(cf->ctx, "azc-resolve", 1, 0,
1187 			      "%slog/" ZXLOG_ISSUE_DIR "%s" ZXLOG_AZC_KIND "%s",
1188 			      cf->cpath, sha_buf, cgi->code);
1189     if (!azc_data) {
1190       ERR("Could not find azc_data for sp(%s) AZC(%s)", sp_eid, cgi->code);
1191       goto invalid_req;
1192     }
1193 
1194     if (memcmp(azc_data, "id_token_path=", sizeof("id_token_path=")-1)) {
1195       ERR("Bad azc_data for sp(%s) AZC(%s)", sp_eid, cgi->code);
1196     invalid_req:
1197       return zxid_simple_show_json(cf, "{\"error\":\"invalid_request\"}", res_len, auto_flags,
1198 				   "Cache-Control: no-store" CRLF
1199 				   "Pragma: no-cache" CRLF);
1200     }
1201     buf = azc_data + sizeof("id_token_path=")-1;
1202 
1203     id_token = read_all_alloc(cf->ctx, "azc-resolve-id_token", 1, 0, "%s", buf);
1204     ZX_FREE(cf->ctx, azc_data);
1205 
1206     buf = zx_alloc_sprintf(cf->ctx, 0,
1207 			   "{\"access_token\":\"%s\""
1208 			   ",\"token_type\":\"Bearer\""
1209 			   ",\"refresh_token\":\"%s\""
1210 			   ",\"expires_in\":3600"
1211 			   ",\"id_token\":\"%s\"}",
1212 			   cgi->code,
1213 			   cgi->code,
1214 			   id_token);
1215     if (cf->log_level > 0)
1216       zxlog(cf, 0, 0, 0, 0, 0, 0, 0, "N", "K", "AZC-TOK", 0, "azc(%s)", cgi->code);
1217 
1218     return zxid_simple_show_json(cf, buf, res_len, auto_flags,
1219 				 "Cache-Control: no-store" CRLF
1220 				 "Pragma: no-cache" CRLF);
1221   }
1222 
1223   if (cgi->id_token) {  /* OAUTH2 Implicit Binding, aka OpenID-Connect1 */
1224     /* The id_token is directly the local filename of the corresponsing assertion. */
1225 
1226     D("check_id ses=%p", ses);
1227 
1228     // *** TODO
1229     //return zxid_simple_show_page(cf, ss, ZXID_AUTO_METAC, ZXID_AUTO_METAH, "b", "text/xml", res_len, auto_flags, 0);
1230   }
1231 
1232   if (cf->log_level > 0)
1233     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));
1234   ERR("Unknown request or response %d", 0);
1235   return "* ERR";
1236 }
1237 
1238 /* EOF  --  zxidoauth.c */
1239