Skip to content

Use one-shot static hash methods #36368

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Sep 13, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 2 additions & 17 deletions src/Mvc/shared/Mvc.Views.TestCommon/TestRazorCompiledItem.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Security.Cryptography;
using System.Text;

Expand Down Expand Up @@ -49,20 +46,8 @@ public TestRazorCompiledItem(Type type, string kind, string identifier, object[]

public static string GetChecksum(string content)
{
byte[] bytes;
using (var sha = SHA1.Create())
{
bytes = sha.ComputeHash(Encoding.UTF8.GetBytes(content));
}

var result = new StringBuilder(bytes.Length);
for (var i = 0; i < bytes.Length; i++)
{
// The x2 format means lowercase hex, where each byte is a 2-character string.
result.Append(bytes[i].ToString("x2", CultureInfo.InvariantCulture));
}

return result.ToString();
var bytes = SHA1.HashData(Encoding.UTF8.GetBytes(content));
return Convert.ToHexString(bytes).ToLowerInvariant();
}
}
}
17 changes: 7 additions & 10 deletions src/Security/Authentication/Facebook/src/FacebookHandler.cs
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Generic;
using System.Globalization;
using System.Net.Http;
using System.Security.Claims;
using System.Security.Cryptography;
using System.Text;
using System.Text.Encodings.Web;
using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication.OAuth;
using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Extensions.Logging;
Expand Down Expand Up @@ -60,16 +58,15 @@ protected override async Task<AuthenticationTicket> CreateTicketAsync(ClaimsIden

private string GenerateAppSecretProof(string accessToken)
{
using (var algorithm = new HMACSHA256(Encoding.ASCII.GetBytes(Options.AppSecret)))
var key = Encoding.ASCII.GetBytes(Options.AppSecret);
var tokenBytes = Encoding.ASCII.GetBytes(accessToken);
var hash = HMACSHA256.HashData(key, tokenBytes);
var builder = new StringBuilder();
for (int i = 0; i < hash.Length; i++)
{
var hash = algorithm.ComputeHash(Encoding.ASCII.GetBytes(accessToken));
var builder = new StringBuilder();
for (int i = 0; i < hash.Length; i++)
{
builder.Append(hash[i].ToString("x2", CultureInfo.InvariantCulture));
}
return builder.ToString();
builder.Append(hash[i].ToString("x2", CultureInfo.InvariantCulture));
}
return builder.ToString();
}

/// <inheritdoc />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ public string AppSecret
/// <summary>
/// Gets or sets if the <c>appsecret_proof</c> should be generated and sent with Facebook API calls.
/// </summary>
/// <remarks>See https://developers.facebook.com/docs/graph-api/securing-requests/#appsecret_proof for more details.</remarks>
/// <remarks>See https://developers.facebook.com/docs/graph-api/security#appsecret_proof for more details.</remarks>
/// <value>Defaults to <see langword="true"/>.</value>
public bool SendAppSecretProof { get; set; }

Expand Down
26 changes: 8 additions & 18 deletions src/Security/Authentication/Twitter/src/TwitterHandler.cs
Original file line number Diff line number Diff line change
@@ -1,20 +1,13 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Security.Claims;
using System.Security.Cryptography;
using System.Text;
using System.Text.Encodings.Web;
using System.Text.Json;
using System.Threading.Tasks;
using System.Xml;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Extensions.Logging;
Expand Down Expand Up @@ -290,7 +283,7 @@ private async Task<RequestToken> ObtainRequestTokenAsync(string callBackUri, Aut

private async Task<AccessToken> ObtainAccessTokenAsync(RequestToken token, string verifier)
{
// https://dev.twitter.com/docs/api/1/post/oauth/access_token
// https://developer.twitter.com/en/docs/authentication/api-reference/access_token

Logger.ObtainAccessToken();

Expand Down Expand Up @@ -342,16 +335,13 @@ private string GenerateTimeStamp()

private static string ComputeSignature(string consumerSecret, string? tokenSecret, string signatureData)
{
using (var algorithm = new HMACSHA1())
{
algorithm.Key = Encoding.ASCII.GetBytes(
string.Format(CultureInfo.InvariantCulture,
"{0}&{1}",
Uri.EscapeDataString(consumerSecret),
string.IsNullOrEmpty(tokenSecret) ? string.Empty : Uri.EscapeDataString(tokenSecret)));
var hash = algorithm.ComputeHash(Encoding.ASCII.GetBytes(signatureData));
return Convert.ToBase64String(hash);
}
var key = Encoding.ASCII.GetBytes(
string.Format(CultureInfo.InvariantCulture,
"{0}&{1}",
Uri.EscapeDataString(consumerSecret),
string.IsNullOrEmpty(tokenSecret) ? string.Empty : Uri.EscapeDataString(tokenSecret)));
var hash = HMACSHA1.HashData(key, Encoding.ASCII.GetBytes(signatureData));
return Convert.ToBase64String(hash);
}

// https://developer.twitter.com/en/docs/apps/callback-urls
Expand Down
1 change: 1 addition & 0 deletions src/Security/Authentication/test/FacebookTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,7 @@ public async Task CustomUserInfoEndpointHasValidGraphQuery()
Assert.Equal(1, finalUserInfoEndpoint.Count(c => c == '?'));
Assert.Contains("fields=email,timezone,picture", finalUserInfoEndpoint);
Assert.Contains("&access_token=", finalUserInfoEndpoint);
Assert.Contains("&appsecret_proof=b7fb6d5a4510926b4af6fe080497827d791dc45fe6541d88ba77bdf6e8e208c6&", finalUserInfoEndpoint);
}

private static async Task<IHost> CreateHost(Action<IApplicationBuilder> configure, Action<IServiceCollection> configureServices, Func<HttpContext, Task<bool>> handler)
Expand Down
184 changes: 175 additions & 9 deletions src/Security/Authentication/test/TwitterTests.cs
Original file line number Diff line number Diff line change
@@ -1,20 +1,24 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.TestHost;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Net.Http.Headers;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Security.Claims;
using System.Text;
using System.Text.Encodings.Web;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.TestHost;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Net.Http.Headers;
using Xunit;

namespace Microsoft.AspNetCore.Authentication.Twitter
Expand Down Expand Up @@ -381,6 +385,158 @@ public async Task BadCallbackCallsRemoteAuthFailedWithState()
Assert.Equal(HttpStatusCode.NotAcceptable, response.StatusCode);
}

[Fact]
public async Task CanSignIn()
{
var stateFormat = new SecureDataFormat<RequestToken>(new RequestTokenSerializer(), new EphemeralDataProtectionProvider(NullLoggerFactory.Instance).CreateProtector("TwitterTest"));
using var host = await CreateHost((options) =>
{
options.ConsumerKey = "Test App Id";
options.ConsumerSecret = "PLACEHOLDER";
options.SaveTokens = true;
options.StateDataFormat = stateFormat;
options.BackchannelHttpHandler = new TestHttpMessageHandler
{
Sender = req =>
{
if (req.RequestUri.GetComponents(UriComponents.SchemeAndServer | UriComponents.Path, UriFormat.UriEscaped) == "https://api.twitter.com/oauth/access_token")
{
var res = new HttpResponseMessage(HttpStatusCode.OK);
var content = new Dictionary<string, string>()
{
["oauth_token"] = "Test Access Token",
["oauth_token_secret"] = "PLACEHOLDER",
["user_id"] = "123456",
["screen_name"] = "@dotnet"
};
res.Content = new FormUrlEncodedContent(content);
return res;
}
return null;
}
};
});

var token = new RequestToken()
{
Token = "TestToken",
TokenSecret = "PLACEHOLDER",
Properties = new()
};

var correlationKey = ".xsrf";
var correlationValue = "TestCorrelationId";
token.Properties.Items.Add(correlationKey, correlationValue);
token.Properties.RedirectUri = "/me";
var state = stateFormat.Protect(token);
using var server = host.GetTestServer();
var transaction = await server.SendAsync(
"https://example.com/signin-twitter?oauth_token=TestToken&oauth_verifier=TestVerifier",
$".AspNetCore.Correlation.{correlationValue}=N;__TwitterState={UrlEncoder.Default.Encode(state)}");
Assert.Equal(HttpStatusCode.Redirect, transaction.Response.StatusCode);
Assert.Equal("/me", transaction.Response.Headers.GetValues("Location").First());

var authCookie = transaction.AuthenticationCookieValue;
transaction = await server.SendAsync("https://example.com/me", authCookie);
Assert.Equal(HttpStatusCode.OK, transaction.Response.StatusCode);
var expectedIssuer = TwitterDefaults.AuthenticationScheme;
Assert.Equal("@dotnet", transaction.FindClaimValue(ClaimTypes.Name, expectedIssuer));
Assert.Equal("123456", transaction.FindClaimValue(ClaimTypes.NameIdentifier, expectedIssuer));
Assert.Equal("123456", transaction.FindClaimValue("urn:twitter:userid", expectedIssuer));
Assert.Equal("@dotnet", transaction.FindClaimValue("urn:twitter:screenname", expectedIssuer));

transaction = await server.SendAsync("https://example.com/tokens", authCookie);
Assert.Equal(HttpStatusCode.OK, transaction.Response.StatusCode);
Assert.Equal("Test Access Token", transaction.FindTokenValue("access_token"));
Assert.Equal("PLACEHOLDER", transaction.FindTokenValue("access_token_secret"));
}

[Fact]
public async Task CanFetchUserDetails()
{
var verifyCredentialsEndpoint = "https://api.twitter.com/1.1/account/verify_credentials.json";
var finalVerifyCredentialsEndpoint = string.Empty;
var finalAuthorizationParameter = string.Empty;
var stateFormat = new SecureDataFormat<RequestToken>(new RequestTokenSerializer(), new EphemeralDataProtectionProvider(NullLoggerFactory.Instance).CreateProtector("TwitterTest"));
using var host = await CreateHost((options) =>
{
options.ConsumerKey = "Test App Id";
options.ConsumerSecret = "PLACEHOLDER";
options.RetrieveUserDetails = true;
options.StateDataFormat = stateFormat;
options.BackchannelHttpHandler = new TestHttpMessageHandler
{
Sender = req =>
{
if (req.RequestUri.GetComponents(UriComponents.SchemeAndServer | UriComponents.Path, UriFormat.UriEscaped) == "https://api.twitter.com/oauth/access_token")
{
var res = new HttpResponseMessage(HttpStatusCode.OK);
var content = new Dictionary<string, string>()
{
["oauth_token"] = "Test Access Token",
["oauth_token_secret"] = "PLACEHOLDER",
["user_id"] = "123456",
["screen_name"] = "@dotnet"
};
res.Content = new FormUrlEncodedContent(content);
return res;
}
if (req.RequestUri.GetComponents(UriComponents.SchemeAndServer | UriComponents.Path, UriFormat.UriEscaped) ==
new Uri(verifyCredentialsEndpoint).GetComponents(UriComponents.SchemeAndServer | UriComponents.Path, UriFormat.UriEscaped))
{
finalVerifyCredentialsEndpoint = req.RequestUri.ToString();
finalAuthorizationParameter = req.Headers.Authorization.Parameter;
var res = new HttpResponseMessage(HttpStatusCode.OK);
var graphResponse = "{ \"email\": \"Test email\" }";
res.Content = new StringContent(graphResponse, Encoding.UTF8);
return res;
}
return null;
}
};
});

var token = new RequestToken()
{
Token = "TestToken",
TokenSecret = "PLACEHOLDER",
Properties = new()
};

var correlationKey = ".xsrf";
var correlationValue = "TestCorrelationId";
token.Properties.Items.Add(correlationKey, correlationValue);
token.Properties.RedirectUri = "/me";
var state = stateFormat.Protect(token);
using var server = host.GetTestServer();
var transaction = await server.SendAsync(
"https://example.com/signin-twitter?oauth_token=TestToken&oauth_verifier=TestVerifier",
$".AspNetCore.Correlation.{correlationValue}=N;__TwitterState={UrlEncoder.Default.Encode(state)}");
Assert.Equal(HttpStatusCode.Redirect, transaction.Response.StatusCode);
Assert.Equal("/me", transaction.Response.Headers.GetValues("Location").First());

Assert.Equal(1, finalVerifyCredentialsEndpoint.Count(c => c == '?'));
Assert.Contains("include_email=true", finalVerifyCredentialsEndpoint);

Assert.Contains("oauth_consumer_key=", finalAuthorizationParameter);
Assert.Contains("oauth_nonce=", finalAuthorizationParameter);
Assert.Contains("oauth_signature=", finalAuthorizationParameter);
Assert.Contains("oauth_signature_method=", finalAuthorizationParameter);
Assert.Contains("oauth_timestamp=", finalAuthorizationParameter);
Assert.Contains("oauth_token=", finalAuthorizationParameter);
Assert.Contains("oauth_version=", finalAuthorizationParameter);

var authCookie = transaction.AuthenticationCookieValue;
transaction = await server.SendAsync("https://example.com/me", authCookie);
Assert.Equal(HttpStatusCode.OK, transaction.Response.StatusCode);
var expectedIssuer = TwitterDefaults.AuthenticationScheme;
Assert.Equal("@dotnet", transaction.FindClaimValue(ClaimTypes.Name, expectedIssuer));
Assert.Equal("123456", transaction.FindClaimValue(ClaimTypes.NameIdentifier, expectedIssuer));
Assert.Equal("123456", transaction.FindClaimValue("urn:twitter:userid", expectedIssuer));
Assert.Equal("@dotnet", transaction.FindClaimValue("urn:twitter:screenname", expectedIssuer));
Assert.Equal("Test email", transaction.FindClaimValue(ClaimTypes.Email, expectedIssuer));
}

private static async Task<IHost> CreateHost(Action<TwitterOptions> options, Func<HttpContext, Task<bool>> handler = null)
{
var host = new HostBuilder()
Expand All @@ -405,6 +561,16 @@ private static async Task<IHost> CreateHost(Action<TwitterOptions> options, Func
{
await Assert.ThrowsAsync<InvalidOperationException>(() => context.ForbidAsync("Twitter"));
}
else if (req.Path == new PathString("/me"))
{
await res.DescribeAsync(context.User);
}
else if (req.Path == new PathString("/tokens"))
{
var result = await context.AuthenticateAsync(TestExtensions.CookieAuthenticationScheme);
var tokens = result.Properties.GetTokens();
await res.DescribeAsync(tokens);
}
else if (handler == null || !await handler(context))
{
await next(context);
Expand All @@ -418,8 +584,8 @@ private static async Task<IHost> CreateHost(Action<TwitterOptions> options, Func
o.SignInScheme = "External";
options(o);
};
services.AddAuthentication()
.AddCookie("External", _ => { })
services.AddAuthentication(TestExtensions.CookieAuthenticationScheme)
.AddCookie(TestExtensions.CookieAuthenticationScheme, o => o.ForwardChallenge = TwitterDefaults.AuthenticationScheme)
.AddTwitter(wrapOptions);
}))
.Build();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,10 @@ public static string CreateResponseKey(string requestKey)
throw new ArgumentNullException(nameof(requestKey));
}

using (var algorithm = SHA1.Create())
{
string merged = requestKey + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
byte[] mergedBytes = Encoding.UTF8.GetBytes(merged);
byte[] hashedBytes = algorithm.ComputeHash(mergedBytes);
return Convert.ToBase64String(hashedBytes);
}
string merged = requestKey + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
byte[] mergedBytes = Encoding.UTF8.GetBytes(merged);
byte[] hashedBytes = SHA1.HashData(mergedBytes);
return Convert.ToBase64String(hashedBytes);
}
}
}