From df4a000b4c97378ccadbd1f94d9f930f87228b28 Mon Sep 17 00:00:00 2001 From: Adam Chlipala Date: Thu, 23 Apr 2009 16:13:02 -0400 Subject: Cookie signatures for RPCs --- CHANGELOG | 3 +++ include/urweb.h | 1 + lib/js/urweb.js | 21 +++++++++++++++------ src/c/urweb.c | 35 ++++++++++++++++++++++++++--------- src/cjr_print.sml | 7 +++++++ src/jscomp.sml | 12 ++++++++---- src/mono.sml | 8 ++++---- src/mono_print.sml | 10 +++++----- src/mono_reduce.sml | 2 +- src/mono_util.sml | 4 ++-- src/monoize.sml | 6 +++++- tests/cookieJsec.ur | 27 +++++++++++++++++++++++++++ tests/cookieJsec.urp | 5 +++++ tests/cookieJsec.urs | 1 + 14 files changed, 110 insertions(+), 32 deletions(-) create mode 100644 tests/cookieJsec.ur create mode 100644 tests/cookieJsec.urp create mode 100644 tests/cookieJsec.urs diff --git a/CHANGELOG b/CHANGELOG index ee860622..d57c52fc 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -5,6 +5,9 @@ Next - Reimplement constructor class resolution to be more general and Prolog-like - SQL table constraints - URLs, with configurable gatekeeper function Basis.bless +- Client-side error handling callbacks +- CSS +- Signing cookie values cryptographically to thwart cross site request forgery ======== 20090405 diff --git a/include/urweb.h b/include/urweb.h index e20533a0..99ae3e69 100644 --- a/include/urweb.h +++ b/include/urweb.h @@ -55,6 +55,7 @@ const char *uw_Basis_get_script(uw_context, uw_unit); uw_Basis_string uw_Basis_maybe_onload(uw_context, uw_Basis_string); void uw_set_needs_push(uw_context, int); +void uw_set_needs_sig(uw_context, int); char *uw_Basis_htmlifyInt(uw_context, uw_Basis_int); char *uw_Basis_htmlifyFloat(uw_context, uw_Basis_float); diff --git a/lib/js/urweb.js b/lib/js/urweb.js index 877ed77f..a29914b9 100644 --- a/lib/js/urweb.js +++ b/lib/js/urweb.js @@ -353,7 +353,9 @@ function getXHR(uri) } } -function requestUri(xhr, uri) { +var sig = null; + +function requestUri(xhr, uri, needsSig) { xhr.open("GET", uri, true); if (client_id != null) { @@ -361,10 +363,17 @@ function requestUri(xhr, uri) { xhr.setRequestHeader("UrWeb-Pass", client_pass.toString()); } + if (needsSig) { + if (sig == null) + whine("Missing cookie signature!"); + + xhr.setRequestHeader("UrWeb-Sig", sig); + } + xhr.send(null); } -function rc(uri, parse, k) { +function rc(uri, parse, k, needsSig) { uri = flattenLocal(uri); var xhr = getXHR(); @@ -389,7 +398,7 @@ function rc(uri, parse, k) { } }; - requestUri(xhr, uri); + requestUri(xhr, uri, needsSig); } function path_join(s1, s2) { @@ -438,7 +447,7 @@ function listener() { var connect = function () { xhr.onreadystatechange = orsc; tid = window.setTimeout(onTimeout, timeout * 500); - requestUri(xhr, uri); + requestUri(xhr, uri, false); } orsc = function() { @@ -490,8 +499,8 @@ function listener() { } else { try { - servError("Error querying remote server for messages: " + xhr.status); - } catch (e) { servError("Error querying remote server for messages"); } + servErr("Error querying remote server for messages: " + xhr.status); + } catch (e) { servErr("Error querying remote server for messages"); } } } }; diff --git a/src/c/urweb.c b/src/c/urweb.c index bd42352f..6266e12d 100644 --- a/src/c/urweb.c +++ b/src/c/urweb.c @@ -300,7 +300,7 @@ struct uw_context { const char *script_header, *url_prefix; - int needs_push; + int needs_push, needs_sig; size_t n_deltas, used_deltas; delta *deltas; @@ -336,6 +336,7 @@ uw_context uw_init() { ctx->script_header = ""; ctx->url_prefix = "/"; ctx->needs_push = 0; + ctx->needs_sig = 0; ctx->error_message[0] = 0; @@ -589,6 +590,10 @@ void uw_set_needs_push(uw_context ctx, int n) { ctx->needs_push = n; } +void uw_set_needs_sig(uw_context ctx, int n) { + ctx->needs_sig = n; +} + static void buf_check_ctx(uw_context ctx, buf *b, size_t extra, const char *desc) { if (b->back - b->front < extra) { @@ -717,16 +722,30 @@ uw_Basis_string uw_Basis_maybe_onload(uw_context ctx, uw_Basis_string s) { } } +extern uw_Basis_string uw_cookie_sig(uw_context); + const char *uw_Basis_get_settings(uw_context ctx, uw_unit u) { - if (ctx->client == NULL) - return ""; - else { - char *r = uw_malloc(ctx, 59 + 3 * INTS_MAX + strlen(ctx->url_prefix)); - sprintf(r, "client_id=%u;client_pass=%d;url_prefix=\"%s\";timeout=%d;listener();", + if (ctx->client == NULL) { + if (ctx->needs_sig) { + char *sig = uw_cookie_sig(ctx); + char *r = uw_malloc(ctx, strlen(sig) + 8); + sprintf(r, "sig=\"%s\";", sig); + return r; + } + else + return ""; + } else { + char *sig = ctx->needs_sig ? uw_cookie_sig(ctx) : ""; + char *r = uw_malloc(ctx, 59 + 3 * INTS_MAX + strlen(ctx->url_prefix) + + (ctx->needs_sig ? strlen(sig) + 7 : 0)); + sprintf(r, "client_id=%u;client_pass=%d;url_prefix=\"%s\";timeout=%d;%s%s%slistener();", ctx->client->id, ctx->client->pass, ctx->url_prefix, - ctx->timeout); + ctx->timeout, + ctx->needs_sig ? "sig=\"" : "", + sig, + ctx->needs_sig ? "\";" : ""); return r; } } @@ -1998,8 +2017,6 @@ uw_Basis_string uw_Basis_makeSigString(uw_context ctx, uw_Basis_string sig) { return r; } -extern uw_Basis_string uw_cookie_sig(uw_context); - uw_Basis_string uw_Basis_sigString(uw_context ctx, uw_unit u) { return uw_cookie_sig(ctx); } diff --git a/src/cjr_print.sml b/src/cjr_print.sml index a47bb587..69332b49 100644 --- a/src/cjr_print.sml +++ b/src/cjr_print.sml @@ -2497,6 +2497,13 @@ fun p_file env (ds, ps) = string (!Monoize.urlPrefix), string "\");", newline]), + string "uw_set_needs_sig(ctx, ", + string (if couldWrite ek then + "1" + else + "0"), + string ");", + newline, string "uw_login(ctx);", newline, box [string "{", diff --git a/src/jscomp.sml b/src/jscomp.sml index f839a67d..e6da3d4b 100644 --- a/src/jscomp.sml +++ b/src/jscomp.sml @@ -113,7 +113,7 @@ fun varDepth (e, _) = | ESignalReturn e => varDepth e | ESignalBind (e1, e2) => Int.max (varDepth e1, varDepth e2) | ESignalSource e => varDepth e - | EServerCall (e, ek, _) => Int.max (varDepth e, varDepth ek) + | EServerCall (e, ek, _, _) => Int.max (varDepth e, varDepth ek) | ERecv (e, ek, _) => Int.max (varDepth e, varDepth ek) | ESleep (e, ek) => Int.max (varDepth e, varDepth ek) @@ -156,7 +156,7 @@ fun closedUpto d = | ESignalReturn e => cu inner e | ESignalBind (e1, e2) => cu inner e1 andalso cu inner e2 | ESignalSource e => cu inner e - | EServerCall (e, ek, _) => cu inner e andalso cu inner ek + | EServerCall (e, ek, _, _) => cu inner e andalso cu inner ek | ERecv (e, ek, _) => cu inner e andalso cu inner ek | ESleep (e, ek) => cu inner e andalso cu inner ek in @@ -956,7 +956,7 @@ fun process file = st) end - | EServerCall (e, ek, t) => + | EServerCall (e, ek, t, eff) => let val (e, st) = jsE inner (e, st) val (ek, st) = jsE inner (ek, st) @@ -967,7 +967,11 @@ fun process file = str ("), function(s){var t=s.split(\"/\");var i=0;return " ^ unurl ^ "},"), ek, - str ")"], + str ("," + ^ (case eff of + ReadCookieWrite => "true" + | _ => "false") + ^ ")")], st) end diff --git a/src/mono.sml b/src/mono.sml index dedb41ea..94314774 100644 --- a/src/mono.sml +++ b/src/mono.sml @@ -62,6 +62,9 @@ datatype javascript_mode = | Script | Source of typ +datatype effect = datatype Export.effect +datatype export_kind = datatype Export.export_kind + datatype exp' = EPrim of Prim.t | ERel of int @@ -109,15 +112,12 @@ datatype exp' = | ESignalBind of exp * exp | ESignalSource of exp - | EServerCall of exp * exp * typ + | EServerCall of exp * exp * typ * effect | ERecv of exp * exp * typ | ESleep of exp * exp withtype exp = exp' located -datatype effect = datatype Export.effect -datatype export_kind = datatype Export.export_kind - datatype decl' = DDatatype of string * int * (string * int * typ option) list | DVal of string * int * typ * exp * string diff --git a/src/mono_print.sml b/src/mono_print.sml index 9e819e5f..b01442e8 100644 --- a/src/mono_print.sml +++ b/src/mono_print.sml @@ -308,11 +308,11 @@ fun p_exp' par env (e, _) = p_exp env e, string ")"] - | EServerCall (n, e, _) => box [string "Server(", - p_exp env n, - string ")[", - p_exp env e, - string "]"] + | EServerCall (n, e, _, _) => box [string "Server(", + p_exp env n, + string ")[", + p_exp env e, + string "]"] | ERecv (n, e, _) => box [string "Recv(", p_exp env n, string ")[", diff --git a/src/mono_reduce.sml b/src/mono_reduce.sml index 4c337e14..c124a7b4 100644 --- a/src/mono_reduce.sml +++ b/src/mono_reduce.sml @@ -371,7 +371,7 @@ fun reduce file = | ESignalBind (e1, e2) => summarize d e1 @ summarize d e2 | ESignalSource e => summarize d e - | EServerCall (e, ek, _) => summarize d e @ summarize d ek @ [Unsure] + | EServerCall (e, ek, _, _) => summarize d e @ summarize d ek @ [Unsure] | ERecv (e, ek, _) => summarize d e @ summarize d ek @ [Unsure] | ESleep (e, ek) => summarize d e @ summarize d ek @ [Unsure] in diff --git a/src/mono_util.sml b/src/mono_util.sml index c7309df4..017b86ca 100644 --- a/src/mono_util.sml +++ b/src/mono_util.sml @@ -354,14 +354,14 @@ fun mapfoldB {typ = fc, exp = fe, bind} = fn e' => (ESignalSource e', loc)) - | EServerCall (s, ek, t) => + | EServerCall (s, ek, t, eff) => S.bind2 (mfe ctx s, fn s' => S.bind2 (mfe ctx ek, fn ek' => S.map2 (mft t, fn t' => - (EServerCall (s', ek', t'), loc)))) + (EServerCall (s', ek', t', eff), loc)))) | ERecv (s, ek, t) => S.bind2 (mfe ctx s, fn s' => diff --git a/src/monoize.sml b/src/monoize.sml index 5a164831..62a46277 100644 --- a/src/monoize.sml +++ b/src/monoize.sml @@ -2668,7 +2668,11 @@ fun monoExp (env, st, fm) (all as (e, loc)) = (L'.ERel 0, loc)), loc), (L'.ERecord [], loc)), loc)), loc)), loc) val ek = (L'.EApp (ekf, ek), loc) - val e = (L'.EServerCall (call, ek, t), loc) + val eff = if IS.member (!readCookie, n) then + L'.ReadCookieWrite + else + L'.ReadOnly + val e = (L'.EServerCall (call, ek, t, eff), loc) val e = liftExpInExp 0 e val unit = (L'.TRecord [], loc) val e = (L'.EAbs ("_", unit, unit, e), loc) diff --git a/tests/cookieJsec.ur b/tests/cookieJsec.ur new file mode 100644 index 00000000..46668cf8 --- /dev/null +++ b/tests/cookieJsec.ur @@ -0,0 +1,27 @@ +table t : {Id : int} + +cookie c : int + +fun setter r = + setCookie c (readError r.Id); + return Done + +fun writer () = + ido <- getCookie c; + case ido of + None => error No cookie + | Some id => dml (INSERT INTO t (Id) VALUES ({[id]})) + +fun preWriter () = return +