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);
        }
        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);
        }
        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);
        }
        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());
        }
Esempio n. 13
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();
        }
Esempio n. 14
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);
            }
        }