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