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