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