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()); }
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(); }
/// <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); } }