Пример #1
0
        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));
            }
        }
Пример #2
0
        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);
            }
        }
Пример #3
0
        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);
        }
Пример #4
0
        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));
            }
        }
Пример #5
0
        /// <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);
        }
Пример #6
0
        /// <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);
        }
Пример #7
0
 /// <inheritdoc/>
 public byte[] Protect(byte[] plaintext)
 {
     return(cipher.EncryptToBytes(plaintext));
 }