public void TamperDetection() { // Tamper with an encrypted payload and verify that this // is detected via the HMAC signature. using (var cipher = new AesCipher()) { var decrypted = new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; var encrypted = cipher.EncryptToBytes(decrypted); Assert.Equal(decrypted, cipher.DecryptBytesFrom(encrypted)); // Modify the last byte and ensure that decryption fails. var tampered = Clone(encrypted); tampered[encrypted.Length - 1] = (byte)(~tampered[encrypted.Length - 1]); Assert.Throws <CryptographicException>(() => cipher.DecryptBytesFrom(tampered)); // Remove the last byte and ensure that decryption fails. tampered = new byte[encrypted.Length - 1]; for (int i = 0; i < tampered.Length; i++) { tampered[i] = encrypted[i]; } Assert.Throws <CryptographicException>(() => cipher.DecryptBytesFrom(tampered)); } }
public void ActuallyEncrypted() { // Attempt to verify that the encrypted output doesn't actually // include the plaintext data. using (var cipher = new AesCipher()) { var decrypted = new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; var encrypted = cipher.EncryptToBytes(decrypted); var pos = 0; var match = false; while (pos <= encrypted.Length - decrypted.Length) { match = true; for (int i = 0; i < decrypted.Length; i++) { if (encrypted[pos + i] != decrypted[i]) { match = false; break; } } if (match) { break; } pos++; } // If we reach this and [match==true] then we found the // original plaintext embedded in the encypted output // (which is bad). Assert.False(match); } }
public void Padding() { // Ensure that [AesCypher] adding the requested random padding. // We're going to do this by requesting 2K of padding and doing // a bunch of encryption runs recording the sizes of the resulting // encrypted data. // // Then we'll verify that we're seeing at least 256 bytes of // variation in the encrypted lengthss. // $note(jefflill): // // There's a very slight chance that this will fail if we're // incredibly unlucky and [AesCipher] happens to randomly // add padding with lengths closer together than this. const int iterations = 1000; var minSize = int.MaxValue; var maxSize = int.MinValue; for (int i = 0; i < iterations; i++) { using (var cipher = new AesCipher(maxPaddingBytes: 2048)) { var encrypted = cipher.EncryptToBytes("the quick brown fox jumped over the lazy dog."); minSize = Math.Min(minSize, encrypted.Length); maxSize = Math.Max(maxSize, encrypted.Length); } } // Verify that padding was actually added. Assert.True(maxSize > 256); // Verify that the padding size is randmonized. Assert.True(maxSize - minSize >= 256); }
public void Encrypt_ToBytes() { // Encrypt a byte array: using (var cipher = new AesCipher()) { var decrypted = new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; var encrypted = cipher.EncryptToBytes(decrypted); Assert.Equal(decrypted, cipher.DecryptBytesFrom(encrypted)); } // Encrypt a string: using (var cipher = new AesCipher()) { var decrypted = "Hello World!"; var encrypted = cipher.EncryptToBytes(decrypted); Assert.Equal(decrypted, cipher.DecryptStringFrom(encrypted)); } }
/// <summary> /// <para> /// Entrypoint called as part of the request pipeline. /// </para> /// <para> /// This method is responsible for intercepting token requests from clients. /// If the client has a valid cookie with a token response in it, we save the /// token to cache and redirect them back with a code referencing the token in /// the cache. /// </para> /// </summary> public async Task InvokeAsync( HttpContext context, Service NeonSsoSessionProxyService, IDistributedCache cache, AesCipher cipher, DistributedCacheEntryOptions cacheOptions, INeonLogger logger) { try { if (context.Request.Cookies.TryGetValue(Service.SessionCookieName, out var requestCookieBase64)) { var requestCookie = NeonHelper.JsonDeserialize <Cookie>(cipher.DecryptBytesFrom(requestCookieBase64)); if (requestCookie.TokenResponse != null) { var code = NeonHelper.GetCryptoRandomPassword(10); await cache.SetAsync(code, cipher.EncryptToBytes(NeonHelper.JsonSerializeToBytes(requestCookie.TokenResponse)), cacheOptions); var query = new Dictionary <string, string>() { { "code", code } }; if (context.Request.Query.TryGetValue("state", out var state)) { query["state"] = state; } if (context.Request.Query.TryGetValue("redirect_uri", out var redirectUri)) { if (context.Request.Query.TryGetValue("client_id", out var clientId)) { if (!NeonSsoSessionProxyService.Config.StaticClients.Where(client => client.Id == clientId).First().RedirectUris.Contains(redirectUri)) { logger.LogError("Invalid redirect URI"); throw new HttpRequestException("Invalid redirect URI."); } context.Response.StatusCode = StatusCodes.Status302Found; context.Response.Headers.Location = QueryHelpers.AddQueryString(redirectUri, query); logger.LogDebug($"Client and Redirect URI confirmed. [ClientID={clientId}] [RedirectUri={redirectUri}]"); return; } else { logger.LogError("No Client ID specified."); throw new HttpRequestException("Invalid Client ID."); } } else { throw new HttpRequestException("No redirect_uri specified."); } } } } catch (Exception e) { NeonSsoSessionProxyService.Log.LogError(e); } await _next(context); }
/// <summary> /// Transforms the response before returning it to the client. /// /// <para> /// This method will add a <see cref="Cookie"/> to each response containing relevant information /// about the current authentication flow. It also intercepts redirects from Dex and saves any relevant /// tokens to a cache for reuse. /// </para> /// </summary> /// <param name="httpContext"></param> /// <param name="proxyResponse"></param> /// <returns></returns> public override async ValueTask <bool> TransformResponseAsync(HttpContext httpContext, HttpResponseMessage proxyResponse) { await base.TransformResponseAsync(httpContext, proxyResponse); Cookie cookie = null; if (httpContext.Request.Cookies.TryGetValue(Service.SessionCookieName, out var requestCookieBase64)) { try { logger.LogDebug($"Decrypting existing cookie."); cookie = NeonHelper.JsonDeserialize <Cookie>(cipher.DecryptBytesFrom(requestCookieBase64)); } catch (Exception e) { logger.LogError(e); cookie = new Cookie(); } } else { logger.LogDebug($"Cookie not present."); cookie = new Cookie(); } // If we're being redirected, intercept request and save token to cookie. if (httpContext.Response.Headers.Location.Count > 0 && Uri.IsWellFormedUriString(httpContext.Response.Headers.Location.Single(), UriKind.Absolute)) { var location = new Uri(httpContext.Response.Headers.Location.Single()); var code = HttpUtility.ParseQueryString(location.Query).Get("code"); if (!string.IsNullOrEmpty(code)) { if (cookie != null) { var redirect = cookie.RedirectUri; var token = await dexClient.GetTokenAsync(cookie.ClientId, code, redirect, "authorization_code"); await cache.SetAsync(code, cipher.EncryptToBytes(NeonHelper.JsonSerializeToBytes(token)), cacheOptions); logger.LogDebug(NeonHelper.JsonSerialize(token)); cookie.TokenResponse = token; httpContext.Response.Cookies.Append( Service.SessionCookieName, cipher.EncryptToBase64(NeonHelper.JsonSerialize(cookie)), new CookieOptions() { Path = "/", Expires = DateTime.UtcNow.AddSeconds(token.ExpiresIn.Value).AddMinutes(-60), Secure = true, SameSite = SameSiteMode.Strict }); return(true); } } } // Add query parameters to the cookie. if (httpContext.Request.Query.TryGetValue("client_id", out var clientId)) { logger.LogDebug($"Client ID: [{clientId}]"); cookie.ClientId = clientId; } if (httpContext.Request.Query.TryGetValue("state", out var state)) { logger.LogDebug($"State: [{state}]"); cookie.State = state; } if (httpContext.Request.Query.TryGetValue("redirect_uri", out var redirectUri)) { logger.LogDebug($"Redirect Uri: [{redirectUri}]"); cookie.RedirectUri = redirectUri; } if (httpContext.Request.Query.TryGetValue("scope", out var scope)) { logger.LogDebug($"Scope: [{scope}]"); cookie.Scope = scope; } if (httpContext.Request.Query.TryGetValue("response_type", out var responseType)) { logger.LogDebug($"Response Type: [{responseType}]"); cookie.ResponseType = responseType; } httpContext.Response.Cookies.Append( Service.SessionCookieName, cipher.EncryptToBase64(NeonHelper.JsonSerialize(cookie)), new CookieOptions() { Path = "/", Expires = DateTime.UtcNow.AddHours(24), Secure = true, SameSite = SameSiteMode.Strict }); return(true); }
/// <inheritdoc/> public byte[] Protect(byte[] plaintext) { return(cipher.EncryptToBytes(plaintext)); }