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