Branch data Line data Source code
1 : : /* zxiddec.c - Handwritten functions for Decoding Redirect or POST bindings
2 : : * Copyright (c) 2010 Sampo Kellomaki (sampo@iki.fi), All Rights Reserved.
3 : : * Copyright (c) 2006-2009 Symlabs (symlabs@symlabs.com), All Rights Reserved.
4 : : * Author: Sampo Kellomaki (sampo@iki.fi)
5 : : * This is confidential unpublished proprietary source code of the author.
6 : : * NO WARRANTY, not even implied warranties. Contains trade secrets.
7 : : * Distribution prohibited unless authorized in writing.
8 : : * Licensed under Apache License 2.0, see file COPYING.
9 : : * $Id: zxiddec.c,v 1.10 2010-01-08 02:10:09 sampo Exp $
10 : : *
11 : : * 12.8.2006, created --Sampo
12 : : * 12.10.2007, tweaked for signing SLO and MNI --Sampo
13 : : * 14.4.2008, added SimpleSign --Sampo
14 : : * 7.10.2008, added documentation --Sampo
15 : : * 10.3.2010, added predecode support --Sampo
16 : : */
17 : :
18 : : #include "platform.h" /* needed on Win32 for pthread_mutex_lock() et al. */
19 : :
20 : : #include "errmac.h"
21 : : #include "zxid.h"
22 : : #include "zxidpriv.h"
23 : : #include "zxidutil.h"
24 : : #include "zxidconf.h"
25 : : #include "saml2.h"
26 : : #include "c/zx-const.h"
27 : : #include "c/zx-ns.h"
28 : : #include "c/zx-data.h"
29 : :
30 : : /*() Look for issuer in all messages we support. */
31 : :
32 : : /* Called by: zxid_decode_redir_or_post, zxid_simple_idp_show_an */
33 : : struct zx_sa_Issuer_s* zxid_extract_issuer(zxid_conf* cf, zxid_cgi* cgi, zxid_ses* ses, struct zx_root_s* r)
34 : 14 : {
35 : 14 : struct zx_sa_Issuer_s* issuer = 0;
36 [ + + ]: 14 : if (r->Response) issuer = r->Response->Issuer;
37 [ + + ]: 10 : else if (r->AuthnRequest) issuer = r->AuthnRequest->Issuer;
38 [ + + ]: 4 : else if (r->LogoutRequest) issuer = r->LogoutRequest->Issuer;
39 [ + - ]: 2 : else if (r->LogoutResponse) issuer = r->LogoutResponse->Issuer;
40 [ # # ]: 0 : else if (r->ManageNameIDRequest) issuer = r->ManageNameIDRequest->Issuer;
41 [ # # ]: 0 : else if (r->ManageNameIDResponse) issuer = r->ManageNameIDResponse->Issuer;
42 : : else {
43 : 0 : ERR("Unknown message type in redirect, post, or simple sign binding %d", 0);
44 : 0 : cgi->sigval = "I";
45 : 0 : cgi->sigmsg = "Unknown message type (SimpleSign, Redir, or POST).";
46 : 0 : ses->sigres = ZXSIG_NO_SIG;
47 : 0 : return 0;
48 : : }
49 [ - + ]: 14 : if (!issuer) {
50 : 0 : ERR("Missing issuer in redirect, post, or simple sign binding %d", 0);
51 : 0 : cgi->sigval = "I";
52 : 0 : cgi->sigmsg = "Issuer not found (SimpleSign, Redir, or POST).";
53 : 0 : ses->sigres = ZXSIG_NO_SIG;
54 : 0 : return 0;
55 : : }
56 : 14 : return issuer;
57 : : }
58 : :
59 : : /*(i) Decode redirect or POST binding message. zxid_saml2_redir_enc()
60 : : * performs the opposite operation. chk_dup is really flags
61 : : * 0x01 = Check dup
62 : : * 0x02 = Avoid sig check and logging */
63 : :
64 : : /* Called by: zxid_idp_dispatch, zxid_simple_idp_show_an, zxid_sp_dispatch */
65 : : struct zx_root_s* zxid_decode_redir_or_post(zxid_conf* cf, zxid_cgi* cgi, zxid_ses* ses, int chk_dup)
66 : 20 : {
67 : 20 : struct zx_sa_Issuer_s* issuer = 0;
68 : : zxid_entity* meta;
69 : : struct zx_str* ss;
70 : : struct zx_str* logpath;
71 : 20 : struct zx_root_s* r = 0;
72 : : struct zx_str id_ss;
73 : : char id_buf[28];
74 : : char sigbuf[512]; /* 192 should be large enough for 1024bit RSA keys */
75 : 20 : int simplesig = 0;
76 : : int msglen, len;
77 : : char* p;
78 : : char* m2;
79 : : char* p2;
80 : : char* msg;
81 : : char* b64msg;
82 : : char* field;
83 : :
84 [ + + + + ]: 26 : if (cgi->saml_resp && *cgi->saml_resp) {
85 : 6 : field = "SAMLResponse";
86 : 6 : b64msg = cgi->saml_resp;
87 [ + + + - ]: 22 : } else if (cgi->saml_req && *cgi->saml_req) {
88 : 8 : field = "SAMLRequest";
89 : 8 : b64msg = cgi->saml_req;
90 : : } else {
91 : 6 : ERR("No SAMLRequest or SAMLResponse field?! %p", cgi);
92 : 6 : return 0;
93 : : }
94 : :
95 : 14 : msglen = strlen(b64msg);
96 : 14 : msg = ZX_ALLOC(cf->ctx, SIMPLE_BASE64_PESSIMISTIC_DECODE_LEN(msglen));
97 : 14 : p = unbase64_raw(b64msg, b64msg + msglen, msg, zx_std_index_64);
98 : 14 : *p = 0;
99 : : DD("Msg(%s) x=%x", msg, *msg);
100 : :
101 : : /* Skip whitespace in the beginning and end of the payload to help correct POST detection. */
102 [ + - ]: 14 : for (m2 = msg; m2 < p; ++m2)
103 [ + - + - : 14 : if (!ONE_OF_4(*m2, ' ', '\t', '\015', '\012'))
+ - + - ]
104 : 14 : break;
105 [ + - ]: 14 : for (p2 = p-1; m2 < p2; --p2)
106 [ + - + - : 14 : if (!ONE_OF_4(*p2, ' ', '\t', '\015', '\012'))
+ - + - ]
107 : 14 : break;
108 : : DD("Msg_sans_ws(%.*s) start=%x end=%x", p2-m2+1, m2, *m2, *p2);
109 : :
110 [ + + + - ]: 14 : if (!(chk_dup & 0x02) && cf->log_level > 1)
111 [ + + - + : 13 : zxlog(cf, 0, 0, 0, 0, 0, 0, ZX_GET_CONTENT(ses->nameid), "N", "W", "REDIRDEC", 0, "sid(%s) len=%d", STRNULLCHK(ses->sid), msglen);
# # # # ]
112 : :
113 [ + + + - ]: 18 : if (*m2 == '<' && *p2 == '>') { /* POST profiles do not compress the payload */
114 : 4 : len = p2 - m2 + 1;
115 : 4 : p = m2;
116 : 4 : simplesig = 1;
117 : : } else {
118 [ + - - + ]: 10 : D("Detected compressed payload. [[m2(%c) %x p2(%c) %x]]", *m2, *m2, *p2, *p2);
119 : 10 : p = zx_zlib_raw_inflate(cf->ctx, p-msg, msg, &len); /* Redir uses compressed payload. */
120 : 10 : ZX_FREE(cf->ctx, msg);
121 : : }
122 : :
123 : 14 : r = zx_dec_zx_root(cf->ctx, len, p, "decode redir or post");
124 [ - + ]: 14 : if (!r) {
125 : 0 : ERR("Failed to parse redir buf(%.*s)", len, p);
126 [ # # # # : 0 : zxlog(cf, 0, 0, 0, 0, 0, 0, ZX_GET_CONTENT(ses->nameid), "N", "C", "BADXML", 0, "sid(%s) bad redir", STRNULLCHK(ses->sid));
# # # # ]
127 : 0 : return 0;
128 : : }
129 : :
130 [ + + ]: 14 : if (chk_dup & 0x02)
131 : 1 : return r;
132 : :
133 : 13 : issuer = zxid_extract_issuer(cf, cgi, ses, r);
134 [ - + ]: 13 : if (!issuer)
135 : 0 : return 0;
136 : :
137 [ + + - + ]: 13 : if (!cgi->sig || !*cgi->sig) {
138 [ + - - + ]: 4 : D("Redirect or POST was not signed at binding level %d", 0);
139 : 4 : log_msg:
140 [ + - ]: 4 : if (cf->log_rely_msg) {
141 : : DD("Logging... %d", 0);
142 : : /* Path will be composed of sha1 hash of the data in p, i.e. the unbase64 data. */
143 : 4 : sha1_safe_base64(id_buf, len, p);
144 : 4 : id_buf[27] = 0;
145 : 4 : id_ss.len = 27;
146 : 4 : id_ss.s = id_buf;
147 [ + - + - : 4 : logpath = zxlog_path(cf, ZX_GET_CONTENT(issuer), &id_ss, ZXLOG_RELY_DIR, ZXLOG_WIR_KIND, 1);
+ - ]
148 [ + - ]: 4 : if (logpath) {
149 [ + - ]: 4 : if (chk_dup & 0x01) {
150 [ - + ]: 4 : if (zxlog_dup_check(cf, logpath, "Redirect or POST assertion (unsigned)")) {
151 [ # # ]: 0 : if (cf->dup_msg_fatal) {
152 : 0 : cgi->err = "C Duplicate message";
153 : 0 : r = 0;
154 : : }
155 : : }
156 : : }
157 : 4 : id_ss.len = len;
158 : 4 : id_ss.s = p;
159 : 4 : zxlog_blob(cf, cf->log_rely_msg, logpath, &id_ss, "dec_redir_post nosig");
160 : : }
161 : : }
162 : 4 : return r;
163 : : }
164 : :
165 [ + - + - : 9 : meta = zxid_get_ent_ss(cf, ZX_GET_CONTENT(issuer));
+ - ]
166 [ - + ]: 9 : if (!meta) {
167 [ # # # # : 0 : ERR("Unable to find metadata for Issuer(%.*s) in Redir or SimpleSign POST binding", ZX_GET_CONTENT_LEN(issuer), ZX_GET_CONTENT_S(issuer));
# # # # #
# # # ]
168 : 0 : cgi->sigval = "I";
169 : 0 : cgi->sigmsg = "Issuer unknown - metadata exchange may be needed (SimpleSign, Redir, POST).";
170 : 0 : ses->sigres = ZXSIG_NO_SIG;
171 : 0 : goto log_msg;
172 : : }
173 : :
174 : : /* ----- Signed at binding level ----- */
175 : :
176 [ - + ]: 9 : if (simplesig) {
177 : : /* In SimpleSign the signature is over data inside base64. */
178 : 0 : p2 = p = cgi->sigalg;
179 [ # # # # : 0 : URL_DECODE(p, p2, cgi->sigalg + strlen(cgi->sigalg));
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# # # # #
# ]
180 : 0 : *p = 0;
181 : : #if 1
182 : : /* Original SimpleSign specification was ambiguous about handling of missing
183 : : * relay state. Literal reading of the spec seemed to say that empty relay state
184 : : * should be part of the signature computation. This was reported by yours
185 : : * truly to SSTC, which has since issued errata clarifying that if the relay
186 : : * state is empty, then the RelayState label is omitted from signature
187 : : * computation. This is also consistent with how the redirect binding works. */
188 [ # # # # ]: 0 : if (cgi->rs && *cgi->rs)
189 [ # # # # ]: 0 : ss = zx_strf(cf->ctx, "%s=%s&RelayState=%s&SigAlg=%s&Signature=%s",
190 : : field, msg, cgi->rs, STRNULLCHK(cgi->sigalg), STRNULLCHK(cgi->sig));
191 : : else
192 [ # # # # ]: 0 : ss = zx_strf(cf->ctx, "%s=%s&SigAlg=%s&Signature=%s",
193 : : field, msg, STRNULLCHK(cgi->sigalg), STRNULLCHK(cgi->sig));
194 : : #else
195 : : cgi->rs = "Fake";
196 : : ss = zx_strf(cf->ctx, "%s=%s&RelayState=%s&SigAlg=%s&Signature=%s",
197 : : field, msg, STRNULLCHK(cgi->rs), STRNULLCHK(cgi->sigalg), STRNULLCHK(cgi->sig));
198 : : #endif
199 : : } else {
200 : : /* In Redir binding, the signature is over base64 and url encoded data. This complicates
201 : : * life as we need to know what the URL looked like prior to CGI processing
202 : : * such as URL decoding. As such processing is done by default to all
203 : : * query string fields, this requires special processing. zxid_parse_cgi()
204 : : * has special case code to prevent URL decoding of SAMLRequest and SAMLResponse
205 : : * fields so the b64msg valiable actually has the URL encoding as well. The
206 : : * unbase64_raw() function is smart enough to unravel the URL decoding on
207 : : * the fly, so it all ends up working fine. */
208 [ + + + - ]: 12 : if (cgi->rs && *cgi->rs)
209 [ + - + - ]: 3 : ss = zx_strf(cf->ctx, "%s=%s&RelayState=%s&SigAlg=%s&Signature=%s",
210 : : field, b64msg, cgi->rs /* *** should be URL encoded or preserved? */,
211 : : STRNULLCHK(cgi->sigalg), STRNULLCHK(cgi->sig));
212 : : else
213 [ + - + - ]: 6 : ss = zx_strf(cf->ctx, "%s=%s&SigAlg=%s&Signature=%s",
214 : : field, b64msg, STRNULLCHK(cgi->sigalg), STRNULLCHK(cgi->sig));
215 : : }
216 : :
217 : : DD("Signed data(%.*s) len=%d sig(%s)", ss->len, ss->s, ss->len, cgi->sig);
218 : 9 : p2 = unbase64_raw(cgi->sig, cgi->sig + strlen(cgi->sig), sigbuf, zx_std_index_64);
219 [ - + # # ]: 9 : ASSERTOP(p2-sigbuf, <, sizeof(sigbuf));
220 : :
221 : : /* strcmp(cgi->sigalg, SIG_ALGO_RSA_SHA1) would be the right test, but as
222 : : * SigAlg can be arbitrarily URL encoded, we make the match fuzzier. */
223 [ + - + - ]: 18 : if (cgi->sigalg && strstr(cgi->sigalg, "rsa-sha1")) {
224 : 9 : ses->sigres = zxsig_verify_data(ss->len /* Adjust for Signature= which we log */
225 : : - (sizeof("&Signature=")-1 + strlen(cgi->sig)),
226 : : ss->s, p2-sigbuf, sigbuf,
227 : : meta->sign_cert, "Simple or Redir SigVfy");
228 : 9 : zxid_sigres_map(ses->sigres, &cgi->sigval, &cgi->sigmsg);
229 : : } else {
230 [ # # ]: 0 : ERR("Unsupported or bad signature algorithm(%s).", STRNULLCHK(cgi->sigalg));
231 : 0 : cgi->sigval = "I";
232 : 0 : cgi->sigmsg = "Unsupported or bad signature algorithm (in SimpleSign, Redir, or POST).";
233 : 0 : ses->sigres = ZXSIG_NO_SIG;
234 : : }
235 : :
236 : : DD("Signed data(%.*s) len=%d", ss->len, ss->s, ss->len);
237 [ + - ]: 9 : if (cf->log_rely_msg) {
238 : : DD("Logging... %d", 0);
239 : 9 : sha1_safe_base64(id_buf, ss->len, ss->s);
240 : 9 : id_buf[27] = 0;
241 : 9 : id_ss.len = 27;
242 : 9 : id_ss.s = id_buf;
243 [ + - + - : 9 : logpath = zxlog_path(cf, ZX_GET_CONTENT(issuer), &id_ss, ZXLOG_RELY_DIR, ZXLOG_WIR_KIND, 1);
+ - ]
244 [ + - ]: 9 : if (logpath) {
245 [ - + ]: 9 : if (zxlog_dup_check(cf, logpath, "Redirect or POST assertion")) {
246 [ # # ]: 0 : if (cf->dup_msg_fatal) {
247 : 0 : cgi->err = "C Duplicate message";
248 : 0 : r = 0;
249 : : }
250 : : }
251 : 9 : zxlog_blob(cf, cf->log_rely_msg, logpath, ss, "dec_redir_post sig");
252 : : }
253 : : }
254 : 9 : zx_str_free(cf->ctx, ss);
255 : 9 : return r;
256 : : }
257 : :
258 : : /* EOF -- zxiddec.c */
|