1 /* zxdecode.c - SAML Decoding tool
2 * Copyright (c) 2012 Synergetics SA (sampo@synergetics.be), All Rights Reserved.
3 * Copyright (c) 2008-2011 Sampo Kellomaki (sampo@iki.fi), All Rights Reserved.
4 * This is confidential unpublished proprietary source code of the author.
5 * NO WARRANTY, not even implied warranties. Contains trade secrets.
6 * Distribution prohibited unless authorized in writing.
7 * Licensed under Apache License 2.0, see file COPYING.
8 * $Id: zxdecode.c,v 1.8 2009-11-29 12:23:06 sampo Exp $
9 *
10 * 25.11.2008, created --Sampo
11 * 4.10.2010, added -s and ss modes, as well as -i N selector --Sampo
12 * 25.1.2011, added -wsc and -wsp validation options --Sampo
13 * 7.2.2012, improved decoding encrypted SAML responses --Sampo
14 */
15
16 #include <string.h>
17 #include <stdio.h>
18 #include <stdlib.h>
19 #include <errno.h>
20
21 #include "platform.h"
22 #include "errmac.h"
23 #include "zx.h"
24 #include "zxid.h"
25 #include "zxidpriv.h"
26 #include "zxidutil.h"
27 #include "zxidconf.h"
28 #include "c/zxidvers.h"
29 #include "c/zx-ns.h"
30 #include "c/zx-const.h"
31 #include "c/zx-data.h"
32
33 char* help =
34 "zxdecode - Decode SAML Redirect and POST Messages R" ZXID_REL "\n\
35 Copyright (c) 2012 Synergetics SA (sampo@synergetics.be), All Rights Reserved.\n\
36 Copyright (c) 2008-2011 Sampo Kellomaki (sampo@iki.fi), All Rights Reserved.\n\
37 NO WARRANTY, not even implied warranties. Licensed under Apache License v2.0\n\
38 See http://www.apache.org/licenses/LICENSE-2.0\n\
39 Send well researched bug reports to the author. Home: zxid.org\n\
40 \n\
41 Usage: zxdecode [options] <message >decoded\n\
42 -b -B Prevent or force decode base64 step (default auto detects)\n\
43 -z -Z Prevent or force inflate step (default auto detects)\n\
44 -i N Pick Nth detected decodable structure, default: 1=first\n\
45 -s Enable signature validation step (reads config from -c, see below)\n\
46 -s -s Only validate hashes (check canon), do not fetch meta or check RSA\n\
47 -r Decode and validate already decoded SAML2 reponse, e.g. from audit trail\n\
48 -c CONF For -s, optional configuration string (default -c CPATH=/var/zxid/)\n\
49 Most of the configuration is read from " ZXID_CONF_PATH "\n\
50 -wscp Call zxid_wsc_prepare_call() on SOAP request\n\
51 -wspv Call zxid_wsp_validate() on SOAP request\n\
52 -wspd Call zxid_wsp_decorate() on SOAP response\n\
53 -wscv Call zxid_wsc_valid_resp() on SOAP response\n\
54 -sha1 Compute sha1 over input and print as base64. For debugging canon.\n\
55 -v Verbose messages.\n\
56 -q Be extra quiet.\n\
57 -d Turn on debugging.\n\
58 -h This help message\n\
59 -- End of options\n\
60 \n\
61 Will attempt to detect many layers of encoding. Will hunt for the\n\
62 relevant input such as SAMLRequest or SAMLResponse in, e.g., log file.\n";
63
64 int b64_flag = 2; /* Auto */
65 int inflate_flag = 2; /* Auto */
66 int verbose = 1;
67 int ix = 1;
68 int sig_flag = 0; /* No sig checking by default. */
69 int sha1_flag = 0;
70 int resp_flag = 0;
71 char valid_opt = 0;
72 zxid_conf* cf = 0;
73 char buf[256*1024];
74
75 /* Called by: main x8, zxbusd_main, zxbuslist_main, zxbustailf_main, zxcall_main, zxcot_main, zxdecode_main */
opt(int * argc,char *** argv,char *** env)76 static void opt(int* argc, char*** argv, char*** env)
77 {
78 if (*argc <= 1) return;
79
80 while (1) {
81 ++(*argv); --(*argc);
82
83 if (!(*argc) || ((*argv)[0][0] != '-')) break; /* normal exit from options loop */
84
85 switch ((*argv)[0][1]) {
86 case '-': if ((*argv)[0][2]) break;
87 ++(*argv); --(*argc);
88 DD("End of options by --");
89 return; /* -- ends the options */
90
91 case 'c':
92 switch ((*argv)[0][2]) {
93 case '\0':
94 ++(*argv); --(*argc);
95 if ((*argc) < 1) break;
96 if (!cf)
97 cf = zxid_new_conf_to_cf(0);
98 zxid_parse_conf(cf, (*argv)[0]);
99 continue;
100 }
101 break;
102
103 case 'd':
104 switch ((*argv)[0][2]) {
105 case '\0':
106 ++errmac_debug;
107 continue;
108 }
109 break;
110
111 case 'i':
112 switch ((*argv)[0][2]) {
113 case '\0':
114 ++(*argv); --(*argc);
115 if ((*argc) < 1) break;
116 sscanf((*argv)[0], "%i", &ix);
117 continue;
118 }
119 break;
120
121 case 's':
122 switch ((*argv)[0][2]) {
123 case '\0':
124 ++sig_flag;
125 if (!cf)
126 cf = zxid_new_conf_to_cf(0);
127 continue;
128 case 'h':
129 ++sha1_flag;
130 continue;
131 }
132 break;
133
134 case 'r':
135 switch ((*argv)[0][2]) {
136 case '\0':
137 ++resp_flag;
138 continue;
139 }
140 break;
141
142 case 'b':
143 switch ((*argv)[0][2]) {
144 case '\0':
145 b64_flag = 0;
146 continue;
147 }
148 break;
149 case 'B':
150 switch ((*argv)[0][2]) {
151 case '\0':
152 b64_flag = 1;
153 continue;
154 }
155 break;
156
157 case 'z':
158 switch ((*argv)[0][2]) {
159 case '\0':
160 inflate_flag = 0;
161 continue;
162 }
163 break;
164 case 'Z':
165 switch ((*argv)[0][2]) {
166 case '\0':
167 inflate_flag = 1;
168 continue;
169 }
170 break;
171
172 case 'w':
173 switch ((*argv)[0][2]) {
174 case 's':
175 switch ((*argv)[0][3]) {
176 case 'c':
177 switch ((*argv)[0][4]) {
178 case 'p':
179 valid_opt = 'P';
180 continue;
181 case 'v':
182 valid_opt = 'V';
183 continue;
184 }
185 break;
186 case 'p':
187 switch ((*argv)[0][4]) {
188 case 'v':
189 valid_opt = 'v';
190 continue;
191 case 'd':
192 valid_opt = 'd';
193 continue;
194 }
195 break;
196 }
197 break;
198 }
199 break;
200
201 #if 0
202 case 'l':
203 switch ((*argv)[0][2]) {
204 case 'i':
205 if (!strcmp((*argv)[0],"-license")) {
206 extern char* license;
207 fprintf(stderr, license);
208 exit(0);
209 }
210 break;
211 }
212 break;
213 #endif
214
215 case 'q':
216 switch ((*argv)[0][2]) {
217 case '\0':
218 verbose = 0;
219 continue;
220 }
221 break;
222
223 case 'v':
224 switch ((*argv)[0][2]) {
225 case '\0':
226 ++verbose;
227 continue;
228 }
229 break;
230
231 }
232 /* fall thru means unrecognized flag */
233 if (*argc)
234 fprintf(stderr, "Unrecognized flag `%s'\n", (*argv)[0]);
235 if (verbose>1) {
236 printf("%s", help);
237 exit(0);
238 }
239 fprintf(stderr, "%s", help);
240 /*fprintf(stderr, "version=0x%06x rel(%s)\n", zxid_version(), zxid_version_str());*/
241 exit(3);
242 }
243 }
244
245 /* Called by: zxdecode_main */
ws_validations()246 static int ws_validations()
247 {
248 int ret;
249 char* nid;
250 struct zx_str* ss;
251 zxid_ses sess;
252 ZERO(&sess, sizeof(sess));
253 if (!cf)
254 cf = zxid_new_conf_to_cf(0);
255
256 switch (valid_opt) {
257 case 'P':
258 ss = zxid_wsc_prepare_call(cf, &sess, 0 /*epr*/, "", buf);
259 if (!ss)
260 return 1;
261 if (verbose)
262 printf("WSC_PREPARE_CALL(%.*s)\n", ss->len, ss->s);
263 return 0;
264 case 'v':
265 nid = zxid_wsp_validate(cf, &sess, "", buf);
266 if (!nid)
267 return 1;
268 if (verbose)
269 printf("WSP_VALIDATE OK nid(%s)\n", nid);
270 return 0;
271 case 'd':
272 ss = zxid_wsp_decorate(cf, &sess, "", buf);
273 if (!ss)
274 return 1;
275 if (verbose)
276 printf("WSP_DECORATE(%.*s)\n", ss->len, ss->s);
277 return 0;
278 case 'V':
279 ret = zxid_wsc_valid_resp(cf, &sess, "", buf);
280 if (verbose)
281 printf("WSC_VALID_RESP(%d)\n", ret);
282 if (ret == 1)
283 return 0; /* Success */
284 return 1;
285 }
286 return 2;
287 }
288
289 /* Called by: sig_validate */
wsse_sec_validate(struct zx_e_Envelope_s * env)290 static int wsse_sec_validate(struct zx_e_Envelope_s* env)
291 {
292 int ret;
293 int n_refs = 0;
294 struct zxsig_ref refs[ZXID_N_WSF_SIGNED_HEADERS];
295 struct zx_wsse_Security_s* sec = env->Header->Security;
296
297 if (!sec || !sec->Signature) {
298 ERR("Missing signature on <wsse:Security> %p", sec);
299 return 8;
300 }
301 if (!sec->Signature->SignedInfo || !sec->Signature->SignedInfo->Reference) {
302 ERR("Malformed signature, missing mandatory SignedInfo(%p) or Reference", sec->Signature->SignedInfo);
303 return 9;
304 }
305
306 ZERO(refs, sizeof(refs));
307 n_refs = zxid_hunt_sig_parts(cf, n_refs, refs, sec->Signature->SignedInfo->Reference, env->Header, env->Body);
308 /*zx_see_elem_ns(cf->ctx, &refs.pop_seen, &resp->gg); *** */
309 ret = zxsig_validate(cf->ctx, 0, sec->Signature, n_refs, refs);
310 if (ret == ZXSIG_BAD_CERT) {
311 INFO("Canon sha1 of <wsse:Security> verified OK %d", ret);
312 if (verbose)
313 printf("\nCanon sha1 if <wsse:Security> verified OK %d\n", ret);
314 } else {
315 ERR("Response Signature hash validation error. Bad canonicalization? ret=%d",ret);
316 return 10;
317 }
318
319 if (ret && verbose)
320 printf("\nSIG Verified OK, zxid_sp_sso_finalize() returned %d\n", ret);
321 return ret?0:6;
322 }
323
324 /*() Process SAML2 response */
325
326 /* Called by: decode, zxdecode_main */
sig_validate(int len,char * p)327 static int sig_validate(int len, char* p)
328 {
329 int ret;
330 zxid_cgi cgi;
331 zxid_ses ses;
332 struct zx_root_s* r;
333 struct zx_sp_Response_s* resp;
334 struct zx_ns_s* pop_seen = 0;
335 struct zxsig_ref refs;
336 zxid_a7n* a7n;
337
338 ZERO(&cgi, sizeof(cgi));
339 ZERO(&ses, sizeof(ses));
340
341 r = zx_dec_zx_root(cf->ctx, len, p, "decode");
342 if (!r) {
343 ERR("Failed to parse buf(%.*s)", len, p);
344 return 2;
345 }
346
347 if (r->Response)
348 resp = r->Response; /* Normal SAML2 Response, e.g. from POST */
349 else if (r->Envelope && r->Envelope->Body) {
350 if (r->Envelope->Body->Response)
351 resp = r->Envelope->Body->Response;
352 else if (r->Envelope->Body->ArtifactResponse && r->Envelope->Body->ArtifactResponse->Response)
353 resp = r->Envelope->Body->ArtifactResponse->Response;
354 else if (r->Envelope->Header && r->Envelope->Header->Security)
355 return wsse_sec_validate(r->Envelope);
356 else {
357 ERR("<e:Envelope> found, but no <sp:Response> element in it %d",0);
358 return 3;
359 }
360 } else {
361 a7n = zxid_dec_a7n(cf, r->Assertion, r->EncryptedAssertion);
362 if (a7n) {
363 INFO("Bare Assertion without Response wrapper detected %p", r->Assertion);
364 goto got_a7n;
365 }
366 ERR("No <sp:Response>, <sa:Assertion>, or <sa:EncryptedAssertion> found buf(%.*s)", len, p);
367 return 3;
368 }
369
370 /* See zxid_sp_dig_sso_a7n() for similar code. */
371
372 if (sig_flag == 2) {
373 if (!resp->Signature) {
374 INFO("No signature in Response %d", 0);
375 } else {
376 if (!resp->Signature->SignedInfo || !resp->Signature->SignedInfo->Reference) {
377 ERR("Malformed signature, missing mandatory SignedInfo(%p) or Reference", resp->Signature->SignedInfo);
378 return 9;
379 }
380
381 ZERO(&refs, sizeof(refs));
382 refs.sref = resp->Signature->SignedInfo->Reference;
383 refs.blob = &resp->gg;
384 refs.pop_seen = pop_seen;
385 zx_see_elem_ns(cf->ctx, &refs.pop_seen, &resp->gg);
386 ret = zxsig_validate(cf->ctx, 0, resp->Signature, 1, &refs);
387 if (ret == ZXSIG_BAD_CERT) {
388 INFO("Canon sha1 of Response verified OK %d", ret);
389 if (verbose)
390 printf("\nCanon sha1 of Response verified OK %d\n", ret);
391 } else {
392 ERR("Response Signature hash validation error. Bad canonicalization? ret=%d",ret);
393 if (sig_flag < 3)
394 return 10;
395 }
396 }
397 } else {
398 if (!zxid_chk_sig(cf, &cgi, &ses, &resp->gg, resp->Signature, resp->Issuer, 0, "Response"))
399 return 4;
400 }
401
402 a7n = zxid_dec_a7n(cf, resp->Assertion, resp->EncryptedAssertion);
403 if (!a7n) {
404 ERR("No Assertion found and not anon_ok in SAML Response %d", 0);
405 return 5;
406 }
407 zx_see_elem_ns(cf->ctx, &pop_seen, &resp->gg);
408 got_a7n:
409 if (sig_flag == 2) {
410 if (a7n->Signature && a7n->Signature->SignedInfo && a7n->Signature->SignedInfo->Reference) {
411 zx_reset_ns_ctx(cf->ctx);
412 ZERO(&refs, sizeof(refs));
413 refs.sref = a7n->Signature->SignedInfo->Reference;
414 refs.blob = &a7n->gg;
415 refs.pop_seen = pop_seen;
416 zx_see_elem_ns(cf->ctx, &refs.pop_seen, &a7n->gg);
417 ret = zxsig_validate(cf->ctx, 0, a7n->Signature, 1, &refs);
418 if (ret == ZXSIG_BAD_CERT) {
419 INFO("Canon sha1 of Assertion verified OK %d", ret);
420 if (ret && verbose)
421 printf("\nCanon sha1 of Assertion verified OK %d\n", ret);
422 return 0;
423 }
424 ERR("Canon sha1 of Assertion failed to verify ret=%d", ret);
425 return 11;
426 } else {
427 ERR("Assertion does not contain a signature %p", a7n->Signature);
428 return 7;
429 }
430 } else {
431 ret = zxid_sp_sso_finalize(cf, &cgi, &ses, a7n, pop_seen);
432 INFO("zxid_sp_sso_finalize() returned %d", ret);
433 }
434 if (ret && verbose)
435 printf("\nSIG Verified OK, zxid_sp_sso_finalize() returned %d\n", ret);
436 return ret?0:6;
437 }
438
439 /* Called by: zxdecode_main x4 */
decode(char * msg,char * q)440 static int decode(char* msg, char* q)
441 {
442 int len;
443 char* p;
444 char* m2;
445 char* p2;
446
447 *q = 0;
448 D("Original Msg(%s) x=%x", msg, *msg);
449
450 if (strchr(msg, '%')) {
451 p = p2 = msg;
452 URL_DECODE(p, p2, q);
453 q = p;
454 *q = 0;
455 D("URL Decoded Msg(%s) x=%x", msg, *msg);
456 } else
457 p = q;
458
459 switch (b64_flag) {
460 case 0:
461 D("decode_base64 skipped at user request %d",0);
462 break;
463 case 1:
464 D("decode_base64 forced at user request %d",0);
465 b64_dec:
466 /* msglen = q - msg; */
467 p = unbase64_raw(msg, q, msg, zx_std_index_64); /* inplace */
468 *p = 0;
469 D("Unbase64 Msg(%s) x=%x len=%d (n.b. message data may be binary at this point)", msg, *msg, ((int)(p-msg)));
470 break;
471 case 2:
472 if (*msg == '<') {
473 D("decode_base64 auto detect: no decode due to initial < %p %p", msg, p);
474 } else {
475 D("decode_base64 auto detect: decode due to initial 0x%x", *msg);
476 goto b64_dec;
477 }
478 break;
479 }
480
481 switch (inflate_flag) {
482 case 0:
483 len = p-msg;
484 p = msg;
485 D("No decompression by user choice len=%d", len);
486 break;
487 case 1:
488 D("Decompressing... (force) %d",0);
489 decompress:
490 p = zx_zlib_raw_inflate(0, p-msg, msg, &len); /* Redir uses compressed payload. */
491 break;
492 case 2:
493 /* Skip whitespace in the beginning and end of the payload to help correct POST detection. */
494 for (m2 = msg; m2 < p; ++m2)
495 if (!ONE_OF_4(*m2, ' ', '\t', '\015', '\012'))
496 break;
497 for (p2 = p-1; m2 < p2; --p2)
498 if (!ONE_OF_4(*p2, ' ', '\t', '\015', '\012'))
499 break;
500 D("Msg_minus_whitespace(%.*s) start=%x end=%x", ((int)(p2-m2+1)), m2, *m2, *p2);
501
502 if (*m2 == '<' && *p2 == '>') { /* POST profiles do not compress the payload */
503 len = p2 - m2 + 1;
504 p = m2;
505 } else {
506 D("Decompressing... (auto) %d",0);
507 goto decompress;
508 }
509 break;
510 }
511 fwrite(p, 1, len, stdout);
512
513 if (sig_flag)
514 return sig_validate(len, p);
515 return 0;
516 }
517
518 #ifndef zxdecode_main
519 #define zxdecode_main main
520 #endif
521
522 /* Called by: */
zxdecode_main(int argc,char ** argv,char ** env)523 int zxdecode_main(int argc, char** argv, char** env)
524 {
525 int got;
526 char* pp;
527 char* p;
528 char* q;
529 char* lim;
530
531 strcpy(errmac_instance, "\tzxdec");
532 opt(&argc, &argv, &env);
533
534 read_all_fd(fdstdin, buf, sizeof(buf)-1, &got);
535 buf[got] = 0;
536 lim = buf+got;
537
538 if (sha1_flag) {
539 p = sha1_safe_base64(buf, got, buf);
540 *p = 0;
541 printf("%s\n", buf);
542 return 0;
543 }
544
545 if (resp_flag)
546 return sig_validate(got, buf);
547
548 if (valid_opt)
549 return ws_validations();
550
551 /* Try to detect relevant input, iterating if -i N was specified.
552 * The detection is supposed to pick SAMLRequest or SAMLResponse from
553 * middle of HTML form, or from log output. Whatever is convenient. */
554
555 for (pp = buf; pp && pp < lim; pp = p+1) {
556 p = strstr(pp, "SAMLRequest=");
557 if (p) {
558 if (--ix) continue;
559 q = strchr(p, '&');
560 return decode(p + sizeof("SAMLRequest=")-1, q?q:lim);
561 }
562 p = strstr(pp, "SAMLResponse=");
563 if (p) {
564 if (--ix) continue;
565 q = strchr(p, '&');
566 return decode(p + sizeof("SAMLResponse=")-1, q?q:lim);
567 }
568 if (*pp == '<') { /* HTML for POST */
569 p = strstr(pp, "SAMLRequest");
570 if (p) {
571 p += sizeof("SAMLRequest")-1;
572 } else {
573 p = strstr(pp, "SAMLResponse");
574 if (p)
575 p += sizeof("SAMLResponse")-1;
576 }
577 if (p) {
578 p = strstr(p, "value=");
579 if (p) {
580 if (--ix) continue;
581 p += sizeof("value=")-1;
582 if (*p == '"') {
583 ++p;
584 q = strchr(p, '"');
585 } else {
586 q = p+strcspn(p, "\" >");
587 }
588 return decode(p, q?q:lim);
589 }
590 }
591 }
592 if (--ix) { p = pp; continue; }
593 return decode(pp, lim); /* Decode the object identified above. */
594 }
595 ERR("No SAMLRequest or SAMLResponse found to decode %p %p %p", buf, pp, lim);
596 return 1;
597 }
598
599 /* EOF -- zxdecode.c */
600