public void VerifySignature() { var toSign = new Dictionary<string, string> { { "a", "xyz" }, { "b", "1234567890" }, { "c", Convert.ToBase64String(Current.Random(16)) }, { "d", "false" } }; var sig = GetSignature("/some/dummy/path", toSign); var fakeAffiliate = new Affiliate { VerificationModulus = "zB3eUr66GkFESizQCnjrm1jCbhHW/vy2UoCHAMIlsOweMOnbU2y8IohlRBEBaS80CqAPlRNfjtRjzdZU3F+J/lUZqipH5sZjXyE6/rPXbvp3tlRSF0pgcQDlFYmAQWKbPKwt2PCg8/Od+wI7cBnHEfveRTjzMzfeFUzoWPiYEo0=" }; var clock = new Stopwatch(); clock.Start(); for (int i = 0; i < 100000; i++) { Assert.IsTrue(fakeAffiliate.ConfirmSignature(sig, "/some/dummy/path", toSign), "Signature didn't pass and should have"); } clock.Stop(); Assert.IsTrue(clock.Elapsed < TimeSpan.FromSeconds(120), "100k in 30s [" + clock.Elapsed + "]"); }
public void ValidCallback() { var affiliate = new Affiliate { HostFilter = "dev.stackoverflow.com" }; Assert.IsTrue(affiliate.IsValidCallback("http://dev.stackoverflow.com/blah-blah-blah")); Assert.IsTrue(affiliate.IsValidCallback("http://dev.stackoverflow.com/blah-blah-blah/more-blah")); Assert.IsTrue(affiliate.IsValidCallback("http://dev.stackoverflow.com/blah-blah-blah/more-blah?param=yesyes")); Assert.IsTrue(affiliate.IsValidCallback("http://dev.stackoverflow.com/blah-blah-blah/more-blah?param=yesyes&indeed=nono")); affiliate = new Affiliate { HostFilter = "dev.*.stackexchange.com" }; Assert.IsTrue(affiliate.IsValidCallback("http://dev.webapps.stackexchange.com/blah-blah-blah")); Assert.IsTrue(affiliate.IsValidCallback("http://dev.webapps.stackexchange.com/blah-blah-blah/more-blah")); Assert.IsTrue(affiliate.IsValidCallback("http://dev.webapps.stackexchange.com/blah-blah-blah/more-blah?param=yesyes")); Assert.IsTrue(affiliate.IsValidCallback("http://dev.webapps.stackexchange.com/blah-blah-blah/more-blah?param=yesyes&indeed=nono")); }
public void InvalidCallback() { var affiliate = new Affiliate { HostFilter = "dev.stackoverflow.com" }; Assert.IsFalse(affiliate.IsValidCallback("http://dev.stackexchange.com/blah-blah-blah")); Assert.IsFalse(affiliate.IsValidCallback("http://dev.superuser.com/blah-blah-blah/more-blah")); Assert.IsFalse(affiliate.IsValidCallback("http://dev.etc.com/blah-blah-blah/more-blah?param=yesyes")); Assert.IsFalse(affiliate.IsValidCallback("http://example.com?indeed=http://dev.stackoverflow.com/")); affiliate = new Affiliate { HostFilter = "dev.*.stackexchange.com" }; Assert.IsFalse(affiliate.IsValidCallback("http://other.stackexchange.com/blah-blah-blah")); Assert.IsFalse(affiliate.IsValidCallback("http://stackexchange.com/blah-blah-blah/more-blah")); Assert.IsFalse(affiliate.IsValidCallback("http://dev.stackexchange.com/blah-blah-blah/more-blah?param=yesyes")); Assert.IsFalse(affiliate.IsValidCallback("http://dev.stackoverflow.com/")); affiliate = new Affiliate { HostFilter = "*.stackexchange.com" }; Assert.IsFalse(affiliate.IsValidCallback("http://one.two.stackexchange.com/")); }
partial void DeleteAffiliate(Affiliate instance);
partial void UpdateAffiliate(Affiliate instance);
partial void InsertAffiliate(Affiliate instance);
public ActionResult CreateAffiliate(string filter) { if (!Affiliate.IsValidFilter(filter)) return RecoverableError("Invalid host filter", new { filter }); var c = new RSACryptoServiceProvider(); var key = c.ExportParameters(true); // meeeh... it would be nice if there were a way to *fix* the exponent. May just need to store it... for (int i = 0; i < key.Exponent.Length; i++) if (key.Exponent[i] != Affiliate.FixedExponent[i]) throw new Exception("Exponent not as expected!"); var modulus = Convert.ToBase64String(key.Modulus); var now = Current.Now; var newAffiliate = new Affiliate { CreationDate = now, HostFilter = filter, OwnerUserId = Current.LoggedInUser.Id, VerificationModulus = modulus }; Current.LoggedInUser.LastActivityDate = now; Current.WriteDB.Affiliates.InsertOnSubmit(newAffiliate); Current.WriteDB.SubmitChanges(); // This is your key, don't lose it! var jsonKey = Json( new { D = Convert.ToBase64String(key.D), DP = Convert.ToBase64String(key.DP), DQ = Convert.ToBase64String(key.DQ), InverseQ = Convert.ToBase64String(key.InverseQ), Modulus = Convert.ToBase64String(key.Modulus), P = Convert.ToBase64String(key.P), Q = Convert.ToBase64String(key.Q) }); var success = Current.Email.SendEmail( Current.LoggedInUser.Email, Email.Template.AffiliateRegistered, new { D = Convert.ToBase64String(key.D), DP = Convert.ToBase64String(key.DP), DQ = Convert.ToBase64String(key.DQ), InverseQ = Convert.ToBase64String(key.InverseQ), Modulus = Convert.ToBase64String(key.Modulus), P = Convert.ToBase64String(key.P), Q = Convert.ToBase64String(key.Q), Id = newAffiliate.Id, Host = newAffiliate.HostFilter }); if (!success) { return IrrecoverableError("An error occurred sending the email", "This has been recorded, and will be looked into shortly"); } return jsonKey; }
/// <summary> /// Returns true if the parameters contain a valid signature for a request. /// </summary> private static bool VerifySignature(Dictionary<string, string> @params, out Affiliate validFor, out string failureReason) { if ([email protected]("authCode") || [email protected]("affId") || [email protected]("nonce")) { validFor = null; failureReason = "Missing parameter"; return false; } validFor = null; var authCode = @params["authCode"].ToString(); int affId; if (!int.TryParse(@params["affId"], out affId)) { failureReason = "No affId"; return false; } var nonce = @params["nonce"].ToString(); string nonceMsg; if (!Nonces.IsValid(nonce, Current.RemoteIP, out nonceMsg)) { failureReason = "Invalid Nonce [" + nonceMsg + "]"; return false; } var affiliate = Current.ReadDB.Affiliates.SingleOrDefault(a => a.Id == affId); if (affiliate == null) { failureReason = "Could not find affiliate"; return false; } var copy = new Dictionary<string, string>(); foreach (var item in @params.Keys.Where(k => k != "authCode")) { copy[item] = @params[item]; } if (authCode.HasValue() && !affiliate.ConfirmSignature(authCode, Current.RequestUri.AbsolutePath, copy)) { failureReason = "Affiliate signature confirmation failed"; return false; } validFor = affiliate; Nonces.MarkUsed(nonce, Current.RemoteIP); failureReason = null; return true; }
/// <summary> /// We greatly restrict access to this controller. /// /// It can only be entered with a request signed by a registered affiliate. /// That is, all GETs must carry a signed authCode. All POSTs are protected by /// our standard XSRF tricks. /// </summary> protected override void OnActionExecuting(ActionExecutingContext filterContext) { Current.NoCache = true; if (Current.PostExpectedAndNotReceived) { Current.ShouldBustFrames = false; filterContext.Result = PostExpectedAndNotReceived(); return; } var @params = new Dictionary<string, string>(); foreach (var x in filterContext.HttpContext.Request.QueryString.AllKeys) { @params[x] = filterContext.HttpContext.Request.QueryString[x]; } if ([email protected]("affId") && Request.Form.AllKeys.Contains("affId")) { @params["affId"] = Request.Form["affId"]; } var method = filterContext.HttpContext.Request.HttpMethod; string failureReason = null; var failure = false; if ([email protected]("affId")) { failureReason = "No affId"; failure = true; } // OK, this is tricky so pay attention if (!failure) { ViewData["affId"] = @params["affId"]; // Anything that's a GET in this controller comes from an affiliate // that means the whole thing needs to be signed (all parameters) if (method == "GET") { // HACK: Ok, we need *one* exception to all this chicanery, so we're just // hacking it in here var reqUrl = filterContext.RouteData.Route as System.Web.Routing.Route; if (reqUrl != null && reqUrl.Url == "affiliate/form/switch") { int affId; if (int.TryParse(@params["affId"], out affId)) { CurrentAffiliate = Current.ReadDB.Affiliates.SingleOrDefault(a => a.Id == affId); if (CurrentAffiliate != null) { Current.ShouldBustFrames = false; base.OnActionExecuting(filterContext); return; } } } else { // For all other routes, confirm that they came from a registered affiliate if ([email protected]("nonce")) { failureReason = "No Nonce"; failure = true; } if ([email protected]("authCode")) { failureReason = "No Auth Code"; failure = true; } string sigError; if(!VerifySignature(@params, out CurrentAffiliate, out sigError)) { failure = true; failureReason = "Signature invalid - " + sigError; } } } else if (method == "POST") { // Anything that's a POST comes from our own forms (in iframes) // We know this because of the XSRF token. CurrentAffiliate = Current.ReadDB.Affiliates.SingleOrDefault(a => a.Id == int.Parse(@params["affId"].ToString())); if (CurrentAffiliate == null) { failure = true; failureReason = "On Post, invalid affId"; } } else { // ... and anything else is a garbage request failure = true; failureReason = " Not GET or POST"; } } Current.ShouldBustFrames = false; // no frame busting in this controller if (failure) { Current.LogException(new Exception("Affiliate Forms failure: " + failureReason)); filterContext.Result = IrrecoverableErrorWithHelp("Affiliate Form failure:", failureReason); return; } var formCanaryCookie = new HttpCookie("canary", "1"); formCanaryCookie.HttpOnly = false; // the whole point is to check for this via javascript formCanaryCookie.Expires = Current.Now + TimeSpan.FromMinutes(5); filterContext.HttpContext.Response.Cookies.Add(formCanaryCookie); base.OnActionExecuting(filterContext); }