Ejemplo n.º 1
0
        /// <summary>
        /// Determines whether the signature is valid for the specified message.
        /// </summary>
        /// <param name="validator">Used to validate the signature.</param>
        /// <param name="request">The request message that contains the signature.</param>
        /// <param name="signature">The signature to validate.</param>
        /// <returns>
        /// A <see cref="SignatureValidationResult"/> that represents the result of the validation.
        /// </returns>
        public static async Task <SignatureValidationResult> ValidateAsync(
            this HttpSignatureValidator validator, HttpRequest request,
            HttpSignature signature)
        {
            // Allow to read the request body multiple times
            request.EnableBuffering();

            // First, we try the raw request URL (if available)
            var requestFeature = request.HttpContext.Features.Get <IHttpRequestFeature>();

            if (!string.IsNullOrEmpty(requestFeature.RawTarget))
            {
                var result = await validator.ValidateAsync(signature,
                                                           request.Method,
                                                           requestFeature.RawTarget,
                                                           request.Body).ConfigureAwait(false);

                // If the signature is OK, we're done. If it's Expired or Duplicate, there's no point
                // in checking again.
                if (result != SignatureValidationResult.Invalid)
                {
                    return(result);
                }
            }

            return(await validator.ValidateAsync(signature,
                                                 request.Method,
                                                 request.GetEncodedUrl(),
                                                 request.Body).ConfigureAwait(false));
        }
        public void StringCannotBeParsedWithoutNonce()
        {
            var serializedString = "keyId=test,created=1,signature=\"OSQPsZ+PegY=\"";

            Action parse = () => HttpSignature.Parse(serializedString);

            parse.Should().Throw <FormatException>();
        }
        public void StringCannotBeParsedWithoutSignature()
        {
            var serializedString = "keyId=test,nonce=test,created=1";

            Action parse = () => HttpSignature.Parse(serializedString);

            parse.Should().Throw <FormatException>();
        }
        public void ExceptionIsThrownForInvalidTimestamp()
        {
            var serializedString = "keyId=test,nonce=test,created=\"undefined\",signature=\"OSQPsZ+PegY=\"";

            Action parse = () => HttpSignature.Parse(serializedString);

            parse.Should().Throw <FormatException>();
        }
        public void TimestampCanBeParsedFromUnixTimestamp()
        {
            var timestamp        = TestClock.TestValue;
            var serializedString = "keyId=test,nonce=test,created=\"" + timestamp.ToUnixTimeSeconds().ToString() + "\",signature=\"OSQPsZ+PegY=\"";

            var param = HttpSignature.Parse(serializedString);

            param.Timestamp.Should().Be(timestamp);
        }
        public void TimestampCanBeParsedFromIso8601String()
        {
            var timestamp        = TestClock.TestValue;
            var serializedString = "keyId=test,nonce=test,created=\"" + timestamp.ToString("s", CultureInfo.InvariantCulture) + "\",signature=\"OSQPsZ+PegY=\"";

            var param = HttpSignature.Parse(serializedString);

            param.Timestamp.Should().Be(timestamp);
        }
        public void NonceCanBeParsed()
        {
            const string nonce            = "99e3006e-b846-4fe6-9572-6b5e2031773f";
            const string serializedString = "keyId=\"test\",nonce=\"" + nonce + "\",created=1,signature=\"OSQPsZ+PegY=\"";

            var param = HttpSignature.Parse(serializedString);

            param.Nonce.Should().Be(nonce);
        }
        public void QuotedKeyIdCanBeParsed()
        {
            const string keyId            = "te,st";
            const string serializedString = "keyId=\"" + keyId + "\",nonce=test,created=1,signature=\"OSQPsZ+PegY=\"";

            var param = HttpSignature.Parse(serializedString);

            param.KeyId.Should().Be(keyId);
        }
        public void KeyIdCanBeParsedCaseInsensitive()
        {
            const string keyId            = "test";
            const string serializedString = "KEYID=" + keyId + ",nonce=test,created=1,signature=\"OSQPsZ+PegY=\"";

            var param = HttpSignature.Parse(serializedString);

            param.KeyId.Should().Be(keyId);
        }
        /// <summary>
        /// Creates a new authentication ticket for the specified signature.
        /// </summary>
        /// <param name="signature">The signature to authenticate.</param>
        /// <returns>A new <see cref="AuthenticationTicket"/>.</returns>
        protected virtual AuthenticationTicket TicketFor(HttpSignature signature)
        {
            var identity = new ClaimsIdentity(Options.AuthenticationScheme);

            identity.AddClaim(new Claim(identity.NameClaimType, signature.KeyId));

            var principal = new ClaimsPrincipal(identity);

            return(new AuthenticationTicket(principal, Options.AuthenticationScheme));
        }
        public void SignatureCanBeParsed()
        {
            var hash = new byte[8] {
                57, 36, 15, 177, 159, 143, 122, 6
            };
            const string serializedString = "keyId=\"test\",nonce=test,created=1,signature=\"OSQPsZ+PegY=\"";

            var param = HttpSignature.Parse(serializedString);

            param.Hash.Should().Equal(hash);
        }
        public void SignatureParamsCanBeSerializedAndDeserialized()
        {
            var expected = GetRandomParams();

            var actual = HttpSignature.Parse(expected.ToString());

            actual.KeyId.Should().Be(expected.KeyId);
            actual.Nonce.Should().Be(expected.Nonce);
            actual.Timestamp.Should().BeCloseTo(expected.Timestamp, TimeSpan.FromSeconds(1));
            actual.Hash.Should().Equal(expected.Hash);
        }
        protected override Task <HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
        {
            request.Headers.Authorization.Should().NotBeNull();
            request.Headers.Authorization.Parameter.Should().NotBeNull();
            var param = HttpSignature.Parse(request.Headers.Authorization.Parameter);

            param.KeyId.Should().NotBeNull();
            param.Nonce.Should().NotBeNull();
            param.Hash.Should().NotBeEmpty();

            return(Task.FromResult(new HttpResponseMessage
            {
                StatusCode = HttpStatusCode.OK,
                RequestMessage = request
            }));
        }
        /// <summary>
        /// Determines whether the current request is authenticated.
        /// </summary>
        /// <returns>
        /// A task that returns a value indicating whether the authentication succeeded.
        /// </returns>
        protected override async Task <AuthenticateResult> HandleAuthenticateAsync()
        {
            if (!Request.Headers.TryGetValue("Authorization", out var value))
            {
                return(AuthenticateResult.NoResult());
            }

            var authValue = value.LastOrDefault(x => x.StartsWith(Options.AuthenticationScheme));

            if (authValue == null)
            {
                return(AuthenticateResult.NoResult());
            }

            authValue = authValue.Substring(Options.AuthenticationScheme.Length).TrimStart();
            var signature = HttpSignature.Parse(authValue);
            var result    = await Validator.ValidateAsync(Request, signature).ConfigureAwait(false);

            switch (result)
            {
            case SignatureValidationResult.OK:
                return(AuthenticateResult.Success(TicketFor(signature)));

            case SignatureValidationResult.Invalid:
                Logger.LogInformation("Invalid signature with key ID {KeyId}",
                                      signature.KeyId);
                break;

            case SignatureValidationResult.Expired:
                Logger.LogInformation("Expired signature with key ID {KeyId}, created {Timestamp}",
                                      signature.KeyId, signature.Timestamp);
                break;

            case SignatureValidationResult.Duplicate:
                Logger.LogInformation("Duplicate signature with key ID {KeyId}, nonce {Nonce}",
                                      signature.KeyId, signature.Nonce);
                break;

            default:
                return(AuthenticateResult.Fail("Invalid validation result: " + result));
            }

            return(AuthenticateResult.NoResult());
        }
        /// <summary>
        /// Determines whether the signature is valid for the specified message.
        /// </summary>
        /// <param name="signature">The signature to validate.</param>
        /// <param name="method">The HTTP method of the message.</param>
        /// <param name="uri">The requested URI of the message.</param>
        /// <param name="body">The message body.</param>
        /// <param name="cancellationToken">A token to monitor for cancellation requests.</param>
        /// <returns>A value indicating the result of the validation.</returns>
        public virtual async Task <SignatureValidationResult> ValidateAsync(
            HttpSignature signature, string method, string uri, Stream body,
            CancellationToken cancellationToken)
        {
            var timeDiff = Clock.UtcNow - signature.Timestamp;

            if (timeDiff.Duration() > Options.ClockSkewMargin)
            {
                Logger?.LogInformation("The time difference {TimeDiff} between the signature timestamp {Timestamp} and the current time exceeds {Margin}.",
                                       timeDiff, signature.Timestamp, Options.ClockSkewMargin);
                return(SignatureValidationResult.Expired);
            }

            var entry = new NonceCacheEntry(signature.Nonce);

            if (Cache.TryGetValue(entry, out _))
            {
                Logger?.LogInformation("The nonce '{Nonce}' is not unique and has been used before in the past {Expiration}.",
                                       signature.Nonce, Options.NonceExpiration);
                return(SignatureValidationResult.Duplicate);
            }

            var key = await KeyLookup.GetKeyOrDefaultAsync(signature.KeyId).ConfigureAwait(false);

            if (key == null)
            {
                throw KeyNotFoundException.WithId(signature.KeyId);
            }

            var algorithm = new HttpSignatureAlgorithm(key, Clock, Logger);
            var newHash   = await algorithm.CalculateHashAsync(method, uri, body, signature.Nonce,
                                                               signature.Timestamp, cancellationToken).ConfigureAwait(false);

            if (!newHash.HashEquals(signature.Hash))
            {
                Logger?.LogInformation("The signature for {Method} {Uri} with nonce '{Nonce}' and timestamp {Timestamp} does not match.",
                                       method, uri, signature.Nonce, signature.Timestamp);
                return(SignatureValidationResult.Invalid);
            }

            Cache.Set(entry, true, Options.NonceExpiration);
            return(SignatureValidationResult.OK);
        }
Ejemplo n.º 16
0
        public async Task SignedRequestContainsValidAuthorizationHeader()
        {
            var algorithm = new HttpSignatureAlgorithm(TestKeyConstants.TestKey, new TestClock());
            var request   = new HttpRequestMessage
            {
                Method     = HttpMethod.Get,
                RequestUri = new Uri("http://localhost:5000/api/test/1?value=2011-12-20T12:13:21Z")
            };

            await request.SignAsync(algorithm, TestKeyConstants.ValidKeyId);

            request.Headers.Authorization.Should().NotBeNull();
            request.Headers.Authorization.Parameter.Should().NotBeNull();
            var param = HttpSignature.Parse(request.Headers.Authorization.Parameter);

            param.KeyId.Should().Be(TestKeyConstants.ValidKeyId);
            param.Nonce.Should().NotBeNull();
            param.Timestamp.Should().Be(TestClock.TestValue);
            param.Hash.Should().NotBeEmpty();
        }
Ejemplo n.º 17
0
        /// <summary>
        /// Validate the signature against the requested payload.
        /// </summary>
        /// <param name="signature"></param>
        /// <param name="key">The public key</param>
        /// <param name="httpRequest"></param>
        /// <returns></returns>
        public static bool Validate(this HttpSignature signature, SecurityKey key, HttpRequest httpRequest)
        {
            var    headers       = httpRequest.Headers.ToDictionary(x => x.Key, x => (string)x.Value, StringComparer.OrdinalIgnoreCase);
            var    options       = (HttpSignatureOptions)httpRequest.HttpContext.RequestServices.GetService(typeof(HttpSignatureOptions));
            var    forwardedPath = httpRequest.Headers[options.ForwardedPathHeaderName];
            string rawTarget     = null;

            if (!string.IsNullOrWhiteSpace(forwardedPath))
            {
                rawTarget = forwardedPath;
            }
            else
            {
                var requestFeature = httpRequest.HttpContext.Features.Get <IHttpRequestFeature>();
                rawTarget = $"{requestFeature.Path}{requestFeature.QueryString}";
            }
            headers.Add(HttpRequestTarget.HeaderName, new HttpRequestTarget(httpRequest.Method, rawTarget).ToString());
            headers.Add(HeaderFieldNames.Created, httpRequest.Headers[options.RequestCreatedHeaderName]);
            return(signature.Validate(key, headers));
        }
Ejemplo n.º 18
0
        /// <summary>
        /// Invokes the middleware.
        /// </summary>
        /// <param name="httpContext">Encapsulates all HTTP-specific information about an individual HTTP request.</param>
        /// <param name="logger">A generic interface for logging.</param>
        public async Task Invoke(HttpContext httpContext, ILogger <HttpSignatureMiddleware> logger)
        {
            var headerNames  = new List <string>();
            var mustValidate = _options.RequestValidation && _options.TryMatch(httpContext, out headerNames);

            if (mustValidate || httpContext.Request.Headers.ContainsKey(HttpSignature.HTTPHeaderName))
            {
                var rawSignature = httpContext.Request.Headers[HttpSignature.HTTPHeaderName];
                Debug.WriteLine($"{nameof(HttpSignatureMiddleware)}: Raw Signature: {rawSignature}");
                var rawDigest = httpContext.Request.Headers[HttpDigest.HTTPHeaderName];
                Debug.WriteLine($"{nameof(HttpSignatureMiddleware)}: Raw Digest: {rawDigest}");
                var rawCertificate = httpContext.Request.Headers[_options.RequestSignatureCertificateHeaderName];
                Debug.WriteLine($"{nameof(HttpSignatureMiddleware)}: Raw Certificate: {rawCertificate}");
                if (string.IsNullOrWhiteSpace(rawSignature))
                {
                    var error = $"Missing httpSignature in HTTP header '{HttpSignature.HTTPHeaderName}'. Cannot validate signature.";
                    await WriteErrorResponse(httpContext, logger, HttpStatusCode.BadRequest, error);

                    return;
                }
                if (string.IsNullOrWhiteSpace(rawCertificate))
                {
                    var error = $"Missing certificate in HTTP header '{_options.RequestSignatureCertificateHeaderName}'. Cannot validate signature.";
                    await WriteErrorResponse(httpContext, logger, HttpStatusCode.BadRequest, error);

                    return;
                }
                X509Certificate2 cert;
                try {
                    cert = new X509Certificate2(Convert.FromBase64String(rawCertificate));
                } catch {
                    var error = $"Signature Certificate not in a valid format. Expected a base64 encoded x509.";
                    await WriteErrorResponse(httpContext, logger, HttpStatusCode.Unauthorized, error);

                    return;
                }
                var validationKey = new X509SecurityKey(cert);
                Debug.WriteLine($"{nameof(HttpSignatureMiddleware)}: Validation Key: {validationKey.KeyId}");
                var httpSignature = HttpSignature.Parse(rawSignature);
                Debug.WriteLine($"{nameof(HttpSignatureMiddleware)}: HTTP Signature: {httpSignature}");
                var requestBody = Array.Empty <byte>();
                switch (httpContext.Request.Method)
                {
                case "POST":
                case "PUT":
                    requestBody = await GetRequestBody(httpContext.Request);

                    break;

                default:
                    break;
                }
                // Validate the request.
                if (httpSignature.Headers.Contains(HttpDigest.HTTPHeaderName))
                {
                    if (!string.IsNullOrWhiteSpace(rawSignature) && string.IsNullOrWhiteSpace(rawDigest))
                    {
                        var error = $"Missing digest in HTTP header '{HttpDigest.HTTPHeaderName}'. Cannot validate signature.";
                        await WriteErrorResponse(httpContext, logger, HttpStatusCode.BadRequest, error);

                        return;
                    }
                    var httpDigest = HttpDigest.Parse(rawDigest);
                    Debug.WriteLine($"{nameof(HttpSignatureMiddleware)}: HTTP Digest: {httpDigest}");
                    var digestIsValid = httpDigest.Validate(requestBody);
                    if (!digestIsValid)
                    {
                        var error = $"Digest validation failed.";
                        await WriteErrorResponse(httpContext, logger, HttpStatusCode.Unauthorized, error);

                        return;
                    }
                }
                var signatureIsValid = httpSignature.Validate(validationKey, httpContext.Request);
                if (!signatureIsValid)
                {
                    var error = $"Signature validation failed.";
                    await WriteErrorResponse(httpContext, logger, HttpStatusCode.Unauthorized, error);

                    return;
                }
                logger.LogInformation("Signature validated successfuly for path: '{0} {1}'", httpContext.Request.Method, httpContext.Request.Path);
                // Call the next middleware delegate in the pipeline.
            }
            if (mustValidate && _options.ResponseSigning == true)
            {
                using (var responseMemory = new MemoryStream()) {
                    var originalStream = httpContext.Response.Body;
                    httpContext.Response.Body = responseMemory;
                    await _next.Invoke(httpContext);

                    responseMemory.Seek(0, SeekOrigin.Begin);
                    var content = responseMemory.ToArray();
                    responseMemory.Seek(0, SeekOrigin.Begin);
                    // Apply logic here for deciding which headers to add.
                    var signingCredentialsStore = httpContext.RequestServices.GetService <IHttpSigningCredentialsStore>();
                    var validationKeysStore     = httpContext.RequestServices.GetService <IHttpValidationKeysStore>();
                    var signingCredentials      = await signingCredentialsStore.GetSigningCredentialsAsync();

                    var validationKeys = await validationKeysStore.GetValidationKeysAsync();

                    var validationKey = validationKeys.First() as X509SecurityKey;
                    Debug.WriteLine($"{nameof(HttpSignatureMiddleware)}: Validation Key: {validationKey.KeyId}");
                    var rawTarget = httpContext.GetPathAndQuery();
                    Debug.WriteLine($"{nameof(HttpSignatureMiddleware)}: Raw Target: {rawTarget}");
                    var extraHeaders = new Dictionary <string, string>(StringComparer.OrdinalIgnoreCase)
                    {
                        [HttpRequestTarget.HeaderName]  = new HttpRequestTarget(httpContext.Request.Method, rawTarget).ToString(),
                        [HttpDigest.HTTPHeaderName]     = new HttpDigest(signingCredentials.Algorithm, content).ToString(),
                        [HeaderFieldNames.Created]      = _systemClock.UtcNow.ToString("r"),
                        [_options.ResponseIdHeaderName] = Guid.NewGuid().ToString()
                    };
                    var includedHeaders = new Dictionary <string, string>(StringComparer.OrdinalIgnoreCase);
                    foreach (var name in headerNames)
                    {
                        if (httpContext.Response.Headers.ContainsKey(name))
                        {
                            if (includedHeaders.ContainsKey(name))
                            {
                                includedHeaders[name] = httpContext.Response.Headers[name];
                            }
                            else
                            {
                                includedHeaders.Add(name, httpContext.Response.Headers[name]);
                            }
                        }
                        else if (extraHeaders.ContainsKey(name))
                        {
                            if (name != HttpRequestTarget.HeaderName)
                            {
                                var responseHeaderName = name == HeaderFieldNames.Created ? _options.ResponseCreatedHeaderName : name;
                                httpContext.Response.Headers.Add(responseHeaderName, extraHeaders[name]);
                            }
                            includedHeaders.Add(name, extraHeaders[name]);
                            Debug.WriteLine($"{nameof(HttpSignatureMiddleware)}: Added Header {name}: {includedHeaders[name]}");
                        }
                    }
                    var signature = new HttpSignature(signingCredentials, includedHeaders, null, null);
                    httpContext.Response.Headers.Add(HttpSignature.HTTPHeaderName, signature.ToString());
                    Debug.WriteLine($"{nameof(HttpSignatureMiddleware)}: {HttpSignature.HTTPHeaderName} Header: {signature}");
                    httpContext.Response.Headers.Add(_options.ResponseSignatureCertificateHeaderName, Convert.ToBase64String(validationKey.Certificate.Export(X509ContentType.Cert)));
                    // Go on with life.
                    await responseMemory.CopyToAsync(originalStream);

                    httpContext.Response.Body = originalStream;
                }
            }
            else
            {
                await _next.Invoke(httpContext);
            }
        }
Ejemplo n.º 19
0
 /// <summary>
 /// Validate the signature against the requested payload.
 /// </summary>
 /// <param name="signature">The signature to validate.</param>
 /// <param name="key">The public key.</param>
 /// <param name="headers">The headers.</param>
 public static bool Validate(this HttpSignature signature, SecurityKey key, IDictionary <string, StringValues> headers) => signature.Validate(key, headers.ToDictionary(x => x.Key, x => (string)x.Value, StringComparer.OrdinalIgnoreCase));
        private async Task <HttpResponseMessage> SendAsync(TestServer server, string method, string uri, HttpSignature signature = null)
        {
            var client  = server.CreateClient();
            var request = new HttpRequestMessage
            {
                Method     = new HttpMethod(method),
                RequestUri = new Uri(uri)
            };

            if (signature != null)
            {
                request.Headers.Add("Authorization", "Signature " + signature.ToString());
            }
            return(await client.SendAsync(request));
        }
 /// <summary>
 /// Determines whether the signature is valid for the specified message.
 /// </summary>
 /// <param name="signature">The signature to validate.</param>
 /// <param name="method">The HTTP method of the message.</param>
 /// <param name="uri">The requested URI of the message.</param>
 /// <param name="body">The message body.</param>
 /// <returns>A value indicating the result of the validation.</returns>
 public Task <SignatureValidationResult> ValidateAsync(
     HttpSignature signature, string method, string uri, Stream body)
 => ValidateAsync(signature, method, uri, body, default);