From c14119b612151d0d528b8cdb2bfaa2f11f55ef40 Mon Sep 17 00:00:00 2001 From: Chris R Date: Fri, 16 Oct 2015 16:01:54 -0700 Subject: [PATCH] #263 Consume ITlsTokenBindingFeature in CookieAuthMiddleware. --- .../CookieAuthenticationHandler.cs | 13 ++- .../DataHandler/ISecureDataFormat.cs | 2 + .../DataHandler/SecureDataFormat.cs | 40 +++++++--- .../DataHandler/SecureDataFormatTests.cs | 80 +++++++++++++++++++ ...uthenticationPropertiesFormaterKeyValue.cs | 11 ++- 5 files changed, 131 insertions(+), 15 deletions(-) create mode 100644 test/Microsoft.AspNet.Authentication.Test/DataHandler/SecureDataFormatTests.cs diff --git a/src/Microsoft.AspNet.Authentication.Cookies/CookieAuthenticationHandler.cs b/src/Microsoft.AspNet.Authentication.Cookies/CookieAuthenticationHandler.cs index f135c3f01f..4f61ca5822 100644 --- a/src/Microsoft.AspNet.Authentication.Cookies/CookieAuthenticationHandler.cs +++ b/src/Microsoft.AspNet.Authentication.Cookies/CookieAuthenticationHandler.cs @@ -8,6 +8,7 @@ using System.Security.Claims; using System.Threading.Tasks; using Microsoft.AspNet.Http; using Microsoft.AspNet.Http.Authentication; +using Microsoft.AspNet.Http.Features; using Microsoft.AspNet.Http.Features.Authentication; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Primitives; @@ -45,7 +46,7 @@ namespace Microsoft.AspNet.Authentication.Cookies return null; } - var ticket = Options.TicketDataFormat.Unprotect(cookie); + var ticket = Options.TicketDataFormat.Unprotect(cookie, GetTlsTokenBinding()); if (ticket == null) { Logger.LogWarning(@"Unprotect ticket failed"); @@ -175,7 +176,7 @@ namespace Microsoft.AspNet.Authentication.Cookies ticket = new AuthenticationTicket(principal, null, Options.AuthenticationScheme); } - var cookieValue = Options.TicketDataFormat.Protect(ticket); + var cookieValue = Options.TicketDataFormat.Protect(ticket, GetTlsTokenBinding()); var cookieOptions = BuildCookieOptions(); if (ticket.Properties.IsPersistent && _renewExpiresUtc.HasValue) @@ -244,7 +245,7 @@ namespace Microsoft.AspNet.Authentication.Cookies Options.ClaimsIssuer)); ticket = new AuthenticationTicket(principal, null, Options.AuthenticationScheme); } - var cookieValue = Options.TicketDataFormat.Protect(ticket); + var cookieValue = Options.TicketDataFormat.Protect(ticket, GetTlsTokenBinding()); Options.CookieManager.AppendResponseCookie( Context, @@ -357,5 +358,11 @@ namespace Microsoft.AspNet.Authentication.Cookies return true; } + + private string GetTlsTokenBinding() + { + var binding = Context.Features.Get()?.GetProvidedTokenBindingId(); + return binding == null ? null : Convert.ToBase64String(binding); + } } } diff --git a/src/Microsoft.AspNet.Authentication/DataHandler/ISecureDataFormat.cs b/src/Microsoft.AspNet.Authentication/DataHandler/ISecureDataFormat.cs index cd31bd2a32..c44729e125 100644 --- a/src/Microsoft.AspNet.Authentication/DataHandler/ISecureDataFormat.cs +++ b/src/Microsoft.AspNet.Authentication/DataHandler/ISecureDataFormat.cs @@ -6,6 +6,8 @@ namespace Microsoft.AspNet.Authentication public interface ISecureDataFormat { string Protect(TData data); + string Protect(TData data, string purpose); TData Unprotect(string protectedText); + TData Unprotect(string protectedText, string purpose); } } diff --git a/src/Microsoft.AspNet.Authentication/DataHandler/SecureDataFormat.cs b/src/Microsoft.AspNet.Authentication/DataHandler/SecureDataFormat.cs index 3ede386433..339d3ed221 100644 --- a/src/Microsoft.AspNet.Authentication/DataHandler/SecureDataFormat.cs +++ b/src/Microsoft.AspNet.Authentication/DataHandler/SecureDataFormat.cs @@ -1,8 +1,6 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System; -using System.Diagnostics.CodeAnalysis; using Microsoft.AspNet.DataProtection; namespace Microsoft.AspNet.Authentication @@ -20,14 +18,29 @@ namespace Microsoft.AspNet.Authentication public string Protect(TData data) { - byte[] userData = _serializer.Serialize(data); - byte[] protectedData = _protector.Protect(userData); - string protectedText = Base64UrlTextEncoder.Encode(protectedData); - return protectedText; + return Protect(data, purpose: null); + } + + public string Protect(TData data, string purpose) + { + var userData = _serializer.Serialize(data); + + var protector = _protector; + if (!string.IsNullOrEmpty(purpose)) + { + protector = protector.CreateProtector(purpose); + } + + var protectedData = protector.Protect(userData); + return Base64UrlTextEncoder.Encode(protectedData); } - [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Exception will be traced")] public TData Unprotect(string protectedText) + { + return Unprotect(protectedText, purpose: null); + } + + public TData Unprotect(string protectedText, string purpose) { try { @@ -36,20 +49,25 @@ namespace Microsoft.AspNet.Authentication return default(TData); } - byte[] protectedData = Base64UrlTextEncoder.Decode(protectedText); + var protectedData = Base64UrlTextEncoder.Decode(protectedText); if (protectedData == null) { return default(TData); } - byte[] userData = _protector.Unprotect(protectedData); + var protector = _protector; + if (!string.IsNullOrEmpty(purpose)) + { + protector = protector.CreateProtector(purpose); + } + + var userData = protector.Unprotect(protectedData); if (userData == null) { return default(TData); } - TData model = _serializer.Deserialize(userData); - return model; + return _serializer.Deserialize(userData); } catch { diff --git a/test/Microsoft.AspNet.Authentication.Test/DataHandler/SecureDataFormatTests.cs b/test/Microsoft.AspNet.Authentication.Test/DataHandler/SecureDataFormatTests.cs new file mode 100644 index 0000000000..2cfdd4e793 --- /dev/null +++ b/test/Microsoft.AspNet.Authentication.Test/DataHandler/SecureDataFormatTests.cs @@ -0,0 +1,80 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Text; +using Microsoft.AspNet.DataProtection; +using Microsoft.Extensions.DependencyInjection; +using Xunit; + +namespace Microsoft.AspNet.Authentication.DataHandler +{ + public class SecureDataFormatTests + { + public SecureDataFormatTests() + { + var serviceCollection = new ServiceCollection(); + serviceCollection.AddDataProtection(); + ServiceProvider = serviceCollection.BuildServiceProvider(); + } + + public IServiceProvider ServiceProvider { get; } + + [Fact] + public void ProtectDataRoundTrips() + { + var provider = ServiceProvider.GetRequiredService(); + var prototector = provider.CreateProtector("test"); + var secureDataFormat = new SecureDataFormat(new StringSerializer(), prototector); + + string input = "abcdefghijklmnopqrstuvwxyz0123456789"; + var protectedData = secureDataFormat.Protect(input); + var result = secureDataFormat.Unprotect(protectedData); + Assert.Equal(input, result); + } + + [Fact] + public void ProtectWithPurposeRoundTrips() + { + var provider = ServiceProvider.GetRequiredService(); + var prototector = provider.CreateProtector("test"); + var secureDataFormat = new SecureDataFormat(new StringSerializer(), prototector); + + string input = "abcdefghijklmnopqrstuvwxyz0123456789"; + string purpose = "purpose1"; + var protectedData = secureDataFormat.Protect(input, purpose); + var result = secureDataFormat.Unprotect(protectedData, purpose); + Assert.Equal(input, result); + } + + [Fact] + public void UnprotectWithDifferentPurposeFails() + { + var provider = ServiceProvider.GetRequiredService(); + var prototector = provider.CreateProtector("test"); + var secureDataFormat = new SecureDataFormat(new StringSerializer(), prototector); + + string input = "abcdefghijklmnopqrstuvwxyz0123456789"; + string purpose = "purpose1"; + var protectedData = secureDataFormat.Protect(input, purpose); + var result = secureDataFormat.Unprotect(protectedData); // Null other purpose + Assert.Null(result); + + result = secureDataFormat.Unprotect(protectedData, "purpose2"); + Assert.Null(result); + } + + private class StringSerializer : IDataSerializer + { + public byte[] Serialize(string model) + { + return Encoding.UTF8.GetBytes(model); + } + + public string Deserialize(byte[] data) + { + return Encoding.UTF8.GetString(data); + } + } + } +} diff --git a/test/Microsoft.AspNet.Authentication.Test/OpenIdConnect/AuthenticationPropertiesFormaterKeyValue.cs b/test/Microsoft.AspNet.Authentication.Test/OpenIdConnect/AuthenticationPropertiesFormaterKeyValue.cs index 66fab419fd..74b36d9cbe 100644 --- a/test/Microsoft.AspNet.Authentication.Test/OpenIdConnect/AuthenticationPropertiesFormaterKeyValue.cs +++ b/test/Microsoft.AspNet.Authentication.Test/OpenIdConnect/AuthenticationPropertiesFormaterKeyValue.cs @@ -31,8 +31,12 @@ namespace Microsoft.AspNet.Authentication.Tests.OpenIdConnect return sb.ToString(); } + public string Protect(AuthenticationProperties data, string purpose) + { + return Protect(data); + } - AuthenticationProperties ISecureDataFormat.Unprotect(string protectedText) + public AuthenticationProperties Unprotect(string protectedText) { if (string.IsNullOrEmpty(protectedText)) { @@ -58,5 +62,10 @@ namespace Microsoft.AspNet.Authentication.Tests.OpenIdConnect return propeties; } + + public AuthenticationProperties Unprotect(string protectedText, string purpose) + { + return Unprotect(protectedText); + } } }