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