Move time-limited data protector to Extensions project
This commit is contained in:
parent
94233e76ff
commit
84490846b6
|
|
@ -29,6 +29,10 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.DataProtec
|
|||
EndProject
|
||||
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.DataProtection.SystemWeb", "src\Microsoft.AspNet.DataProtection.SystemWeb\Microsoft.AspNet.DataProtection.SystemWeb.xproj", "{E3552DEB-4173-43AE-BF69-3C10DFF3BAB6}"
|
||||
EndProject
|
||||
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.DataProtection.Extensions.Test", "test\Microsoft.AspNet.DataProtection.Extensions.Test\Microsoft.AspNet.DataProtection.Extensions.Test.xproj", "{04AA8E60-A053-4D50-89FE-E76C3DF45200}"
|
||||
EndProject
|
||||
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.DataProtection.Extensions", "src\Microsoft.AspNet.DataProtection.Extensions\Microsoft.AspNet.DataProtection.Extensions.xproj", "{BF8681DB-C28B-441F-BD92-0DCFE9537A9F}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
|
|
@ -121,6 +125,22 @@ Global
|
|||
{E3552DEB-4173-43AE-BF69-3C10DFF3BAB6}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{E3552DEB-4173-43AE-BF69-3C10DFF3BAB6}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{E3552DEB-4173-43AE-BF69-3C10DFF3BAB6}.Release|x86.Build.0 = Release|Any CPU
|
||||
{04AA8E60-A053-4D50-89FE-E76C3DF45200}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{04AA8E60-A053-4D50-89FE-E76C3DF45200}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{04AA8E60-A053-4D50-89FE-E76C3DF45200}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{04AA8E60-A053-4D50-89FE-E76C3DF45200}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{04AA8E60-A053-4D50-89FE-E76C3DF45200}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{04AA8E60-A053-4D50-89FE-E76C3DF45200}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{04AA8E60-A053-4D50-89FE-E76C3DF45200}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{04AA8E60-A053-4D50-89FE-E76C3DF45200}.Release|x86.Build.0 = Release|Any CPU
|
||||
{BF8681DB-C28B-441F-BD92-0DCFE9537A9F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{BF8681DB-C28B-441F-BD92-0DCFE9537A9F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{BF8681DB-C28B-441F-BD92-0DCFE9537A9F}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{BF8681DB-C28B-441F-BD92-0DCFE9537A9F}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{BF8681DB-C28B-441F-BD92-0DCFE9537A9F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{BF8681DB-C28B-441F-BD92-0DCFE9537A9F}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{BF8681DB-C28B-441F-BD92-0DCFE9537A9F}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{BF8681DB-C28B-441F-BD92-0DCFE9537A9F}.Release|x86.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
|
@ -137,5 +157,7 @@ Global
|
|||
{4F14BA2A-4F04-4676-8586-EC380977EE2E} = {60336AB3-948D-4D15-A5FB-F32A2B91E814}
|
||||
{3277BB22-033F-4010-8131-A515B910CAAD} = {5FCB2DA3-5395-47F5-BCEE-E0EA319448EA}
|
||||
{E3552DEB-4173-43AE-BF69-3C10DFF3BAB6} = {5FCB2DA3-5395-47F5-BCEE-E0EA319448EA}
|
||||
{04AA8E60-A053-4D50-89FE-E76C3DF45200} = {60336AB3-948D-4D15-A5FB-F32A2B91E814}
|
||||
{BF8681DB-C28B-441F-BD92-0DCFE9537A9F} = {5FCB2DA3-5395-47F5-BCEE-E0EA319448EA}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
|
|
|||
|
|
@ -0,0 +1,42 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.AspNet.DataProtection
|
||||
{
|
||||
internal static class BitHelpers
|
||||
{
|
||||
/// <summary>
|
||||
/// Reads an unsigned 64-bit integer from <paramref name="buffer"/>
|
||||
/// starting at offset <paramref name="offset"/>. Data is read big-endian.
|
||||
/// </summary>
|
||||
public static ulong ReadUInt64(byte[] buffer, int offset)
|
||||
{
|
||||
return (((ulong)buffer[offset + 0]) << 56)
|
||||
| (((ulong)buffer[offset + 1]) << 48)
|
||||
| (((ulong)buffer[offset + 2]) << 40)
|
||||
| (((ulong)buffer[offset + 3]) << 32)
|
||||
| (((ulong)buffer[offset + 4]) << 24)
|
||||
| (((ulong)buffer[offset + 5]) << 16)
|
||||
| (((ulong)buffer[offset + 6]) << 8)
|
||||
| (ulong)buffer[offset + 7];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes an unsigned 64-bit integer to <paramref name="buffer"/> starting at
|
||||
/// offset <paramref name="offset"/>. Data is written big-endian.
|
||||
/// </summary>
|
||||
public static void WriteUInt64(byte[] buffer, int offset, ulong value)
|
||||
{
|
||||
buffer[offset + 0] = (byte)(value >> 56);
|
||||
buffer[offset + 1] = (byte)(value >> 48);
|
||||
buffer[offset + 2] = (byte)(value >> 40);
|
||||
buffer[offset + 3] = (byte)(value >> 32);
|
||||
buffer[offset + 4] = (byte)(value >> 24);
|
||||
buffer[offset + 5] = (byte)(value >> 16);
|
||||
buffer[offset + 6] = (byte)(value >> 8);
|
||||
buffer[offset + 7] = (byte)(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,108 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. 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.Security.Cryptography;
|
||||
using Microsoft.Framework.Internal;
|
||||
|
||||
namespace Microsoft.AspNet.DataProtection
|
||||
{
|
||||
public static class DataProtectionExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Cryptographically protects a piece of plaintext data, expiring the data after
|
||||
/// the specified amount of time has elapsed.
|
||||
/// </summary>
|
||||
/// <param name="protector">The protector to use.</param>
|
||||
/// <param name="plaintext">The plaintext data to protect.</param>
|
||||
/// <param name="lifetime">The amount of time after which the payload should no longer be unprotectable.</param>
|
||||
/// <returns>The protected form of the plaintext data.</returns>
|
||||
public static byte[] Protect([NotNull] this ITimeLimitedDataProtector protector, [NotNull] byte[] plaintext, TimeSpan lifetime)
|
||||
{
|
||||
return protector.Protect(plaintext, DateTimeOffset.UtcNow + lifetime);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cryptographically protects a piece of plaintext data, expiring the data at
|
||||
/// the chosen time.
|
||||
/// </summary>
|
||||
/// <param name="protector">The protector to use.</param>
|
||||
/// <param name="plaintext">The plaintext data to protect.</param>
|
||||
/// <param name="expiration">The time when this payload should expire.</param>
|
||||
/// <returns>The protected form of the plaintext data.</returns>
|
||||
public static string Protect([NotNull] this ITimeLimitedDataProtector protector, [NotNull] string plaintext, DateTimeOffset expiration)
|
||||
{
|
||||
var wrappingProtector = new TimeLimitedWrappingProtector(protector) { Expiration = expiration };
|
||||
return wrappingProtector.Protect(plaintext);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cryptographically protects a piece of plaintext data, expiring the data after
|
||||
/// the specified amount of time has elapsed.
|
||||
/// </summary>
|
||||
/// <param name="protector">The protector to use.</param>
|
||||
/// <param name="plaintext">The plaintext data to protect.</param>
|
||||
/// <param name="lifetime">The amount of time after which the payload should no longer be unprotectable.</param>
|
||||
/// <returns>The protected form of the plaintext data.</returns>
|
||||
public static string Protect([NotNull] this ITimeLimitedDataProtector protector, [NotNull] string plaintext, TimeSpan lifetime)
|
||||
{
|
||||
return Protect(protector, plaintext, DateTimeOffset.Now + lifetime);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts an <see cref="IDataProtector"/> into an <see cref="ITimeLimitedDataProtector"/>
|
||||
/// so that payloads can be protected with a finite lifetime.
|
||||
/// </summary>
|
||||
/// <param name="protector">The <see cref="IDataProtector"/> to convert to a time-limited protector.</param>
|
||||
/// <returns>An <see cref="ITimeLimitedDataProtector"/>.</returns>
|
||||
public static ITimeLimitedDataProtector ToTimeLimitedDataProtector([NotNull] this IDataProtector protector)
|
||||
{
|
||||
return (protector as ITimeLimitedDataProtector) ?? new TimeLimitedDataProtector(protector);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cryptographically unprotects a piece of protected data.
|
||||
/// </summary>
|
||||
/// <param name="protector">The protector to use.</param>
|
||||
/// <param name="protectedData">The protected data to unprotect.</param>
|
||||
/// <param name="expiration">An 'out' parameter which upon a successful unprotect
|
||||
/// operation receives the expiration date of the payload.</param>
|
||||
/// <returns>The plaintext form of the protected data.</returns>
|
||||
/// <exception cref="CryptographicException">
|
||||
/// Thrown if <paramref name="protectedData"/> is invalid, malformed, or expired.
|
||||
/// </exception>
|
||||
public static string Unprotect([NotNull] this ITimeLimitedDataProtector protector, [NotNull] string protectedData, out DateTimeOffset expiration)
|
||||
{
|
||||
var wrappingProtector = new TimeLimitedWrappingProtector(protector);
|
||||
string retVal = wrappingProtector.Unprotect(protectedData);
|
||||
expiration = wrappingProtector.Expiration;
|
||||
return retVal;
|
||||
}
|
||||
|
||||
private sealed class TimeLimitedWrappingProtector : IDataProtector
|
||||
{
|
||||
public DateTimeOffset Expiration;
|
||||
private readonly ITimeLimitedDataProtector _innerProtector;
|
||||
|
||||
public TimeLimitedWrappingProtector(ITimeLimitedDataProtector innerProtector)
|
||||
{
|
||||
_innerProtector = innerProtector;
|
||||
}
|
||||
|
||||
public IDataProtector CreateProtector(string purpose)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public byte[] Protect(byte[] plaintext)
|
||||
{
|
||||
return _innerProtector.Protect(plaintext, Expiration);
|
||||
}
|
||||
|
||||
public byte[] Unprotect(byte[] protectedData)
|
||||
{
|
||||
return _innerProtector.Unprotect(protectedData, out Expiration);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. 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.Security.Cryptography;
|
||||
using Microsoft.Framework.Internal;
|
||||
|
||||
namespace Microsoft.AspNet.DataProtection
|
||||
{
|
||||
/// <summary>
|
||||
/// An interface that can provide data protection services where payloads have
|
||||
/// a finite lifetime.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// It is intended that payload lifetimes be somewhat short. Payloads protected
|
||||
/// via this mechanism are not intended for long-term persistence (e.g., longer
|
||||
/// than a few weeks).
|
||||
/// </remarks>
|
||||
public interface ITimeLimitedDataProtector : IDataProtector
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates an <see cref="ITimeLimitedDataProtector"/> given a purpose.
|
||||
/// </summary>
|
||||
/// <param name="purposes">
|
||||
/// The purpose to be assigned to the newly-created <see cref="ITimeLimitedDataProtector"/>.
|
||||
/// </param>
|
||||
/// <returns>An <see cref="ITimeLimitedDataProtector"/> tied to the provided purpose.</returns>
|
||||
/// <remarks>
|
||||
/// The <paramref name="purpose"/> parameter must be unique for the intended use case; two
|
||||
/// different <see cref="ITimeLimitedDataProtector"/> instances created with two different <paramref name="purpose"/>
|
||||
/// values will not be able to decipher each other's payloads. The <paramref name="purpose"/> parameter
|
||||
/// value is not intended to be kept secret.
|
||||
/// </remarks>
|
||||
new ITimeLimitedDataProtector CreateProtector([NotNull] string purpose);
|
||||
|
||||
/// <summary>
|
||||
/// Cryptographically protects a piece of plaintext data, expiring the data at
|
||||
/// the chosen time.
|
||||
/// </summary>
|
||||
/// <param name="plaintext">The plaintext data to protect.</param>
|
||||
/// <param name="expiration">The time when this payload should expire.</param>
|
||||
/// <returns>The protected form of the plaintext data.</returns>
|
||||
byte[] Protect([NotNull] byte[] plaintext, DateTimeOffset expiration);
|
||||
|
||||
/// <summary>
|
||||
/// Cryptographically unprotects a piece of protected data.
|
||||
/// </summary>
|
||||
/// <param name="protectedData">The protected data to unprotect.</param>
|
||||
/// <param name="expiration">An 'out' parameter which upon a successful unprotect
|
||||
/// operation receives the expiration date of the payload.</param>
|
||||
/// <returns>The plaintext form of the protected data.</returns>
|
||||
/// <exception cref="CryptographicException">
|
||||
/// Thrown if <paramref name="protectedData"/> is invalid, malformed, or expired.
|
||||
/// </exception>
|
||||
byte[] Unprotect([NotNull] byte[] protectedData, out DateTimeOffset expiration);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
|
||||
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.Props" Condition="'$(VSToolsPath)' != ''" />
|
||||
<PropertyGroup Label="Globals">
|
||||
<ProjectGuid>bf8681db-c28b-441f-bd92-0dcfe9537a9f</ProjectGuid>
|
||||
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">..\..\artifacts\obj\$(MSBuildProjectName)</BaseIntermediateOutputPath>
|
||||
<OutputPath Condition="'$(OutputPath)'=='' ">..\..\artifacts\bin\$(MSBuildProjectName)\</OutputPath>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<SchemaVersion>2.0</SchemaVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.targets" Condition="'$(VSToolsPath)' != ''" />
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. 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.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo("Microsoft.AspNet.DataProtection.Extensions.Test")]
|
||||
78
src/Microsoft.AspNet.DataProtection.Extensions/Properties/Resources.Designer.cs
generated
Normal file
78
src/Microsoft.AspNet.DataProtection.Extensions/Properties/Resources.Designer.cs
generated
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
// <auto-generated />
|
||||
namespace Microsoft.AspNet.DataProtection.Extensions
|
||||
{
|
||||
using System.Globalization;
|
||||
using System.Reflection;
|
||||
using System.Resources;
|
||||
|
||||
internal static class Resources
|
||||
{
|
||||
private static readonly ResourceManager _resourceManager
|
||||
= new ResourceManager("Microsoft.AspNet.DataProtection.Extensions.Resources", typeof(Resources).GetTypeInfo().Assembly);
|
||||
|
||||
/// <summary>
|
||||
/// An error occurred during a cryptographic operation.
|
||||
/// </summary>
|
||||
internal static string CryptCommon_GenericError
|
||||
{
|
||||
get { return GetString("CryptCommon_GenericError"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An error occurred during a cryptographic operation.
|
||||
/// </summary>
|
||||
internal static string FormatCryptCommon_GenericError()
|
||||
{
|
||||
return GetString("CryptCommon_GenericError");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The payload expired at {0}.
|
||||
/// </summary>
|
||||
internal static string TimeLimitedDataProtector_PayloadExpired
|
||||
{
|
||||
get { return GetString("TimeLimitedDataProtector_PayloadExpired"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The payload expired at {0}.
|
||||
/// </summary>
|
||||
internal static string FormatTimeLimitedDataProtector_PayloadExpired(object p0)
|
||||
{
|
||||
return string.Format(CultureInfo.CurrentCulture, GetString("TimeLimitedDataProtector_PayloadExpired"), p0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The payload is invalid.
|
||||
/// </summary>
|
||||
internal static string TimeLimitedDataProtector_PayloadInvalid
|
||||
{
|
||||
get { return GetString("TimeLimitedDataProtector_PayloadInvalid"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The payload is invalid.
|
||||
/// </summary>
|
||||
internal static string FormatTimeLimitedDataProtector_PayloadInvalid()
|
||||
{
|
||||
return GetString("TimeLimitedDataProtector_PayloadInvalid");
|
||||
}
|
||||
|
||||
private static string GetString(string name, params string[] formatterNames)
|
||||
{
|
||||
var value = _resourceManager.GetString(name);
|
||||
|
||||
System.Diagnostics.Debug.Assert(value != null);
|
||||
|
||||
if (formatterNames != null)
|
||||
{
|
||||
for (var i = 0; i < formatterNames.Length; i++)
|
||||
{
|
||||
value = value.Replace("{" + formatterNames[i] + "}", "{" + i + "}");
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,129 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="CryptCommon_GenericError" xml:space="preserve">
|
||||
<value>An error occurred during a cryptographic operation.</value>
|
||||
</data>
|
||||
<data name="TimeLimitedDataProtector_PayloadExpired" xml:space="preserve">
|
||||
<value>The payload expired at {0}.</value>
|
||||
</data>
|
||||
<data name="TimeLimitedDataProtector_PayloadInvalid" xml:space="preserve">
|
||||
<value>The payload is invalid.</value>
|
||||
</data>
|
||||
</root>
|
||||
|
|
@ -0,0 +1,115 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. 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.Security.Cryptography;
|
||||
using System.Threading;
|
||||
using Microsoft.AspNet.DataProtection.Extensions;
|
||||
using Microsoft.Framework.Internal;
|
||||
|
||||
namespace Microsoft.AspNet.DataProtection
|
||||
{
|
||||
/// <summary>
|
||||
/// Wraps an existing <see cref="IDataProtector"/> and appends a purpose that allows
|
||||
/// protecting data with a finite lifetime.
|
||||
/// </summary>
|
||||
internal sealed class TimeLimitedDataProtector : ITimeLimitedDataProtector
|
||||
{
|
||||
private const string MyPurposeString = "Microsoft.AspNet.DataProtection.TimeLimitedDataProtector.v1";
|
||||
|
||||
private readonly IDataProtector _innerProtector;
|
||||
private IDataProtector _innerProtectorWithTimeLimitedPurpose; // created on-demand
|
||||
|
||||
public TimeLimitedDataProtector(IDataProtector innerProtector)
|
||||
{
|
||||
_innerProtector = innerProtector;
|
||||
}
|
||||
|
||||
public ITimeLimitedDataProtector CreateProtector([NotNull] string purpose)
|
||||
{
|
||||
return new TimeLimitedDataProtector(_innerProtector.CreateProtector(purpose));
|
||||
}
|
||||
|
||||
private IDataProtector GetInnerProtectorWithTimeLimitedPurpose()
|
||||
{
|
||||
// thread-safe lazy init pattern with multi-execution and single publication
|
||||
var retVal = Volatile.Read(ref _innerProtectorWithTimeLimitedPurpose);
|
||||
if (retVal == null)
|
||||
{
|
||||
var newValue = _innerProtector.CreateProtector(MyPurposeString); // we always append our purpose to the end of the chain
|
||||
retVal = Interlocked.CompareExchange(ref _innerProtectorWithTimeLimitedPurpose, newValue, null) ?? newValue;
|
||||
}
|
||||
return retVal;
|
||||
}
|
||||
|
||||
public byte[] Protect([NotNull] byte[] plaintext, DateTimeOffset expiration)
|
||||
{
|
||||
// We prepend the expiration time (as a 64-bit UTC tick count) to the unprotected data.
|
||||
byte[] plaintextWithHeader = new byte[checked(8 + plaintext.Length)];
|
||||
BitHelpers.WriteUInt64(plaintextWithHeader, 0, (ulong)expiration.UtcTicks);
|
||||
Buffer.BlockCopy(plaintext, 0, plaintextWithHeader, 8, plaintext.Length);
|
||||
|
||||
return GetInnerProtectorWithTimeLimitedPurpose().Protect(plaintextWithHeader);
|
||||
}
|
||||
|
||||
public byte[] Unprotect([NotNull] byte[] protectedData, out DateTimeOffset expiration)
|
||||
{
|
||||
return UnprotectCore(protectedData, DateTimeOffset.UtcNow, out expiration);
|
||||
}
|
||||
|
||||
internal byte[] UnprotectCore([NotNull] byte[] protectedData, DateTimeOffset now, out DateTimeOffset expiration)
|
||||
{
|
||||
try
|
||||
{
|
||||
byte[] plaintextWithHeader = GetInnerProtectorWithTimeLimitedPurpose().Unprotect(protectedData);
|
||||
if (plaintextWithHeader.Length < 8)
|
||||
{
|
||||
// header isn't present
|
||||
throw new CryptographicException(Resources.TimeLimitedDataProtector_PayloadInvalid);
|
||||
}
|
||||
|
||||
// Read expiration time back out of the payload
|
||||
ulong utcTicksExpiration = BitHelpers.ReadUInt64(plaintextWithHeader, 0);
|
||||
DateTimeOffset embeddedExpiration = new DateTimeOffset(checked((long)utcTicksExpiration), TimeSpan.Zero /* UTC */);
|
||||
|
||||
// Are we expired?
|
||||
if (now > embeddedExpiration)
|
||||
{
|
||||
throw new CryptographicException(Resources.FormatTimeLimitedDataProtector_PayloadExpired(embeddedExpiration));
|
||||
}
|
||||
|
||||
// Not expired - split and return payload
|
||||
byte[] retVal = new byte[plaintextWithHeader.Length - 8];
|
||||
Buffer.BlockCopy(plaintextWithHeader, 8, retVal, 0, retVal.Length);
|
||||
expiration = new DateTimeOffset((long)utcTicksExpiration, TimeSpan.Zero);
|
||||
return retVal;
|
||||
}
|
||||
catch (Exception ex) when (ex.RequiresHomogenization())
|
||||
{
|
||||
// Homogenize all failures to CryptographicException
|
||||
throw new CryptographicException(Resources.CryptCommon_GenericError, ex);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* EXPLICIT INTERFACE IMPLEMENTATIONS
|
||||
*/
|
||||
|
||||
IDataProtector IDataProtectionProvider.CreateProtector(string purpose)
|
||||
{
|
||||
return CreateProtector(purpose);
|
||||
}
|
||||
|
||||
byte[] IDataProtector.Protect(byte[] plaintext)
|
||||
{
|
||||
// MaxValue essentially means 'no expiration'
|
||||
return Protect(plaintext, DateTimeOffset.MaxValue);
|
||||
}
|
||||
|
||||
byte[] IDataProtector.Unprotect(byte[] protectedData)
|
||||
{
|
||||
DateTimeOffset expiration; // unused
|
||||
return Unprotect(protectedData, out expiration);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"version": "1.0.0-*",
|
||||
"description": "Additional APIs for ASP.NET 5 data protection.",
|
||||
"dependencies": {
|
||||
"Microsoft.AspNet.DataProtection": "1.0.0-*",
|
||||
"Microsoft.AspNet.DataProtection.Shared": { "type": "build", "version": "" },
|
||||
"Microsoft.Framework.DependencyInjection": "1.0.0-*",
|
||||
"Microsoft.Framework.NotNullAttribute.Internal": { "type": "build", "version": "1.0.0-*" }
|
||||
},
|
||||
"frameworks": {
|
||||
"net451": { },
|
||||
"dnx451": { },
|
||||
"dnxcore50": { }
|
||||
},
|
||||
"compilationOptions": {
|
||||
"warningsAsErrors": true
|
||||
}
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Security.Cryptography;
|
||||
using Microsoft.AspNet.DataProtection.Interfaces;
|
||||
using Microsoft.Framework.Internal;
|
||||
|
||||
|
|
@ -201,9 +202,9 @@ namespace Microsoft.AspNet.DataProtection
|
|||
/// <param name="protector">The data protector to use for this operation.</param>
|
||||
/// <param name="protectedData">The protected data to unprotect.</param>
|
||||
/// <returns>The plaintext form of the protected data.</returns>
|
||||
/// <remarks>
|
||||
/// This method will throw CryptographicException if the input is invalid or malformed.
|
||||
/// </remarks>
|
||||
/// <exception cref="CryptographicException">
|
||||
/// Thrown if <paramref name="protectedData"/> is invalid or malformed.
|
||||
/// </exception>
|
||||
public static string Unprotect([NotNull] this IDataProtector protector, [NotNull] string protectedData)
|
||||
{
|
||||
try
|
||||
|
|
|
|||
|
|
@ -1,25 +0,0 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using Microsoft.Framework.Internal;
|
||||
|
||||
namespace Microsoft.AspNet.DataProtection
|
||||
{
|
||||
/// <summary>
|
||||
/// Helpful extension methods for data protection APIs.
|
||||
/// </summary>
|
||||
public static class DataProtectionExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a time-limited data protector based on an existing protector.
|
||||
/// </summary>
|
||||
/// <param name="protector">The existing protector from which to derive a time-limited protector.</param>
|
||||
/// <returns>A time-limited data protector.</returns>
|
||||
public static ITimeLimitedDataProtector AsTimeLimitedDataProtector([NotNull] this IDataProtector protector)
|
||||
{
|
||||
return (protector as ITimeLimitedDataProtector)
|
||||
?? new TimeLimitedDataProtector(protector.CreateProtector(TimeLimitedDataProtector.PurposeString));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -85,14 +85,7 @@ namespace Microsoft.AspNet.DataProtection
|
|||
{
|
||||
return new CryptographicException(Resources.ProtectionProvider_BadVersion);
|
||||
}
|
||||
|
||||
public static CryptographicException TimeLimitedDataProtector_PayloadExpired(ulong utcTicksExpiration)
|
||||
{
|
||||
DateTimeOffset expiration = new DateTimeOffset((long)utcTicksExpiration, TimeSpan.Zero).ToLocalTime();
|
||||
string message = String.Format(CultureInfo.CurrentCulture, Resources.TimeLimitedDataProtector_PayloadExpired, expiration);
|
||||
return new CryptographicException(message);
|
||||
}
|
||||
|
||||
|
||||
public static InvalidOperationException XmlKeyManager_DuplicateKey(Guid keyId)
|
||||
{
|
||||
string message = String.Format(CultureInfo.CurrentCulture, Resources.XmlKeyManager_DuplicateKey, keyId);
|
||||
|
|
|
|||
|
|
@ -1,45 +0,0 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.AspNet.DataProtection
|
||||
{
|
||||
/// <summary>
|
||||
/// An interface that can provide data protection services.
|
||||
/// </summary>
|
||||
public interface ITimeLimitedDataProtector : IDataProtector
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates an IDataProtector given a purpose.
|
||||
/// </summary>
|
||||
/// <param name="purposes">
|
||||
/// The purpose to be assigned to the newly-created IDataProtector.
|
||||
/// This parameter must be unique for the intended use case; two different IDataProtector
|
||||
/// instances created with two different 'purpose' strings will not be able
|
||||
/// to understand each other's payloads. The 'purpose' parameter is not intended to be
|
||||
/// kept secret.
|
||||
/// </param>
|
||||
/// <returns>An IDataProtector tied to the provided purpose.</returns>
|
||||
new ITimeLimitedDataProtector CreateProtector(string purpose);
|
||||
|
||||
/// <summary>
|
||||
/// Cryptographically protects a piece of plaintext data and assigns an expiration date to the data.
|
||||
/// </summary>
|
||||
/// <param name="plaintext">The plaintext data to protect.</param>
|
||||
/// <param name="expiration">The date after which the data can no longer be unprotected.</param>
|
||||
/// <returns>The protected form of the plaintext data.</returns>
|
||||
byte[] Protect(byte[] plaintext, DateTimeOffset expiration);
|
||||
|
||||
/// <summary>
|
||||
/// Cryptographically unprotects a piece of protected data.
|
||||
/// </summary>
|
||||
/// <param name="protectedData">The protected data to unprotect.</param>
|
||||
/// <param name="expiration">After unprotection, contains the expiration date of the protected data.</param>
|
||||
/// <returns>The plaintext form of the protected data.</returns>
|
||||
/// <remarks>
|
||||
/// Implementations should throw CryptographicException if the protected data is invalid or malformed.
|
||||
/// </remarks>
|
||||
byte[] Unprotect(byte[] protectedData, out DateTimeOffset expiration);
|
||||
}
|
||||
}
|
||||
|
|
@ -170,22 +170,6 @@ namespace Microsoft.AspNet.DataProtection
|
|||
return GetString("ProtectionProvider_BadVersion");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The payload expired at {0}.
|
||||
/// </summary>
|
||||
internal static string TimeLimitedDataProtector_PayloadExpired
|
||||
{
|
||||
get { return GetString("TimeLimitedDataProtector_PayloadExpired"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The payload expired at {0}.
|
||||
/// </summary>
|
||||
internal static string FormatTimeLimitedDataProtector_PayloadExpired(object p0)
|
||||
{
|
||||
return string.Format(CultureInfo.CurrentCulture, GetString("TimeLimitedDataProtector_PayloadExpired"), p0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Value must be non-negative.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -147,9 +147,6 @@
|
|||
<data name="ProtectionProvider_BadVersion" xml:space="preserve">
|
||||
<value>The provided payload cannot be decrypted because it was protected with a newer version of the protection provider.</value>
|
||||
</data>
|
||||
<data name="TimeLimitedDataProtector_PayloadExpired" xml:space="preserve">
|
||||
<value>The payload expired at {0}.</value>
|
||||
</data>
|
||||
<data name="Common_ValueMustBeNonNegative" xml:space="preserve">
|
||||
<value>Value must be non-negative.</value>
|
||||
</data>
|
||||
|
|
|
|||
|
|
@ -1,102 +0,0 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using Microsoft.AspNet.Cryptography;
|
||||
using Microsoft.Framework.Internal;
|
||||
|
||||
namespace Microsoft.AspNet.DataProtection
|
||||
{
|
||||
internal sealed class TimeLimitedDataProtector : ITimeLimitedDataProtector
|
||||
{
|
||||
internal const string PurposeString = "Microsoft.AspNet.DataProtection.TimeLimitedDataProtector";
|
||||
|
||||
public TimeLimitedDataProtector(IDataProtector innerProtector)
|
||||
{
|
||||
InnerProtector = innerProtector;
|
||||
}
|
||||
|
||||
internal IDataProtector InnerProtector
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public ITimeLimitedDataProtector CreateProtector([NotNull] string purpose)
|
||||
{
|
||||
return new TimeLimitedDataProtector(InnerProtector.CreateProtector(purpose));
|
||||
}
|
||||
|
||||
public byte[] Protect([NotNull] byte[] plaintext)
|
||||
{
|
||||
return Protect(plaintext, DateTimeOffset.MaxValue);
|
||||
}
|
||||
|
||||
public byte[] Protect([NotNull] byte[] plaintext, DateTimeOffset expiration)
|
||||
{
|
||||
// We prepend the expiration time (as a big-endian 64-bit UTC tick count) to the unprotected data.
|
||||
ulong utcTicksExpiration = (ulong)expiration.UtcTicks;
|
||||
|
||||
byte[] plaintextWithHeader = new byte[checked(8 + plaintext.Length)];
|
||||
plaintextWithHeader[0] = (byte)(utcTicksExpiration >> 56);
|
||||
plaintextWithHeader[1] = (byte)(utcTicksExpiration >> 48);
|
||||
plaintextWithHeader[2] = (byte)(utcTicksExpiration >> 40);
|
||||
plaintextWithHeader[3] = (byte)(utcTicksExpiration >> 32);
|
||||
plaintextWithHeader[4] = (byte)(utcTicksExpiration >> 24);
|
||||
plaintextWithHeader[5] = (byte)(utcTicksExpiration >> 16);
|
||||
plaintextWithHeader[6] = (byte)(utcTicksExpiration >> 8);
|
||||
plaintextWithHeader[7] = (byte)(utcTicksExpiration);
|
||||
Buffer.BlockCopy(plaintext, 0, plaintextWithHeader, 8, plaintext.Length);
|
||||
|
||||
return InnerProtector.Protect(plaintextWithHeader);
|
||||
}
|
||||
|
||||
public byte[] Unprotect([NotNull] byte[] protectedData)
|
||||
{
|
||||
DateTimeOffset unused;
|
||||
return Unprotect(protectedData, out unused);
|
||||
}
|
||||
|
||||
public byte[] Unprotect([NotNull] byte[] protectedData, out DateTimeOffset expiration)
|
||||
{
|
||||
try
|
||||
{
|
||||
byte[] plaintextWithHeader = InnerProtector.Unprotect(protectedData);
|
||||
CryptoUtil.Assert(plaintextWithHeader.Length >= 8, "No header present.");
|
||||
|
||||
// Read expiration time back out of the payload
|
||||
ulong utcTicksExpiration = (((ulong)plaintextWithHeader[0]) << 56)
|
||||
| (((ulong)plaintextWithHeader[1]) << 48)
|
||||
| (((ulong)plaintextWithHeader[2]) << 40)
|
||||
| (((ulong)plaintextWithHeader[3]) << 32)
|
||||
| (((ulong)plaintextWithHeader[4]) << 24)
|
||||
| (((ulong)plaintextWithHeader[5]) << 16)
|
||||
| (((ulong)plaintextWithHeader[6]) << 8)
|
||||
| (ulong)plaintextWithHeader[7];
|
||||
|
||||
// Are we expired?
|
||||
DateTime utcNow = DateTime.UtcNow;
|
||||
if ((ulong)utcNow.Ticks > utcTicksExpiration)
|
||||
{
|
||||
throw Error.TimeLimitedDataProtector_PayloadExpired(utcTicksExpiration);
|
||||
}
|
||||
|
||||
byte[] retVal = new byte[plaintextWithHeader.Length - 8];
|
||||
Buffer.BlockCopy(plaintextWithHeader, 8, retVal, 0, retVal.Length);
|
||||
|
||||
expiration = new DateTimeOffset((long)utcTicksExpiration, TimeSpan.Zero);
|
||||
return retVal;
|
||||
}
|
||||
catch (Exception ex) when (ex.RequiresHomogenization())
|
||||
{
|
||||
// Homogenize all failures to CryptographicException
|
||||
throw Error.CryptCommon_GenericError(ex);
|
||||
}
|
||||
}
|
||||
|
||||
IDataProtector IDataProtectionProvider.CreateProtector([NotNull] string purpose)
|
||||
{
|
||||
return CreateProtector(purpose);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,102 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. 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.Globalization;
|
||||
using System.Text;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNet.DataProtection
|
||||
{
|
||||
public class DataProtectionExtensionsTests
|
||||
{
|
||||
private const string SampleEncodedString = "AQI"; // = WebEncoders.Base64UrlEncode({ 0x01, 0x02 })
|
||||
|
||||
[Fact]
|
||||
public void Protect_PayloadAsString_WithExplicitExpiration()
|
||||
{
|
||||
// Arrange
|
||||
var plaintextAsBytes = Encoding.UTF8.GetBytes("this is plaintext");
|
||||
var expiration = StringToDateTime("2015-01-01 00:00:00Z");
|
||||
var mockDataProtector = new Mock<ITimeLimitedDataProtector>();
|
||||
mockDataProtector.Setup(o => o.Protect(plaintextAsBytes, expiration)).Returns(new byte[] { 0x01, 0x02 });
|
||||
|
||||
// Act
|
||||
string protectedPayload = mockDataProtector.Object.Protect("this is plaintext", expiration);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(SampleEncodedString, protectedPayload);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Protect_PayloadAsString_WithLifetimeAsTimeSpan()
|
||||
{
|
||||
// Arrange
|
||||
var plaintextAsBytes = Encoding.UTF8.GetBytes("this is plaintext");
|
||||
DateTimeOffset actualExpiration = default(DateTimeOffset);
|
||||
var mockDataProtector = new Mock<ITimeLimitedDataProtector>();
|
||||
mockDataProtector.Setup(o => o.Protect(plaintextAsBytes, It.IsAny<DateTimeOffset>()))
|
||||
.Returns<byte[], DateTimeOffset>((_, exp) =>
|
||||
{
|
||||
actualExpiration = exp;
|
||||
return new byte[] { 0x01, 0x02 };
|
||||
});
|
||||
|
||||
// Act
|
||||
DateTimeOffset lowerBound = DateTimeOffset.UtcNow.AddHours(48);
|
||||
string protectedPayload = mockDataProtector.Object.Protect("this is plaintext", TimeSpan.FromHours(48));
|
||||
DateTimeOffset upperBound = DateTimeOffset.UtcNow.AddHours(48);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(SampleEncodedString, protectedPayload);
|
||||
Assert.InRange(actualExpiration, lowerBound, upperBound);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Protect_PayloadAsBytes_WithLifetimeAsTimeSpan()
|
||||
{
|
||||
// Arrange
|
||||
DateTimeOffset actualExpiration = default(DateTimeOffset);
|
||||
var mockDataProtector = new Mock<ITimeLimitedDataProtector>();
|
||||
mockDataProtector.Setup(o => o.Protect(new byte[] { 0x11, 0x22, 0x33 }, It.IsAny<DateTimeOffset>()))
|
||||
.Returns<byte[], DateTimeOffset>((_, exp) =>
|
||||
{
|
||||
actualExpiration = exp;
|
||||
return new byte[] { 0x01, 0x02 };
|
||||
});
|
||||
|
||||
// Act
|
||||
DateTimeOffset lowerBound = DateTimeOffset.UtcNow.AddHours(48);
|
||||
byte[] protectedPayload = mockDataProtector.Object.Protect(new byte[] { 0x11, 0x22, 0x33 }, TimeSpan.FromHours(48));
|
||||
DateTimeOffset upperBound = DateTimeOffset.UtcNow.AddHours(48);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(new byte[] { 0x01, 0x02 }, protectedPayload);
|
||||
Assert.InRange(actualExpiration, lowerBound, upperBound);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Unprotect_PayloadAsString()
|
||||
{
|
||||
// Arrange
|
||||
var futureDate = DateTimeOffset.UtcNow.AddYears(1);
|
||||
var controlExpiration = futureDate;
|
||||
var mockDataProtector = new Mock<ITimeLimitedDataProtector>();
|
||||
mockDataProtector.Setup(o => o.Unprotect(new byte[] { 0x01, 0x02 }, out controlExpiration)).Returns(Encoding.UTF8.GetBytes("this is plaintext"));
|
||||
|
||||
// Act
|
||||
DateTimeOffset testExpiration;
|
||||
string unprotectedPayload = mockDataProtector.Object.Unprotect(SampleEncodedString, out testExpiration);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("this is plaintext", unprotectedPayload);
|
||||
Assert.Equal(futureDate, testExpiration);
|
||||
}
|
||||
|
||||
private static DateTime StringToDateTime(string input)
|
||||
{
|
||||
return DateTimeOffset.ParseExact(input, "u", CultureInfo.InvariantCulture).UtcDateTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
|
||||
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.Props" Condition="'$(VSToolsPath)' != ''" />
|
||||
<PropertyGroup Label="Globals">
|
||||
<ProjectGuid>04aa8e60-a053-4d50-89fe-e76c3df45200</ProjectGuid>
|
||||
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">..\..\artifacts\obj\$(MSBuildProjectName)</BaseIntermediateOutputPath>
|
||||
<OutputPath Condition="'$(OutputPath)'=='' ">..\..\artifacts\bin\$(MSBuildProjectName)\</OutputPath>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<SchemaVersion>2.0</SchemaVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.targets" Condition="'$(VSToolsPath)' != ''" />
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,178 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. 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.Globalization;
|
||||
using System.Security.Cryptography;
|
||||
using Microsoft.AspNet.DataProtection.Extensions;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNet.DataProtection
|
||||
{
|
||||
public class TimeLimitedDataProtectorTests
|
||||
{
|
||||
private const string TimeLimitedPurposeString = "Microsoft.AspNet.DataProtection.TimeLimitedDataProtector.v1";
|
||||
|
||||
[Fact]
|
||||
public void Protect_LifetimeSpecified()
|
||||
{
|
||||
// Arrange
|
||||
// 0x08c1220247e44000 is the representation of midnight 2000-01-01 UTC.
|
||||
DateTimeOffset expiration = StringToDateTime("2000-01-01 00:00:00Z");
|
||||
var mockInnerProtector = new Mock<IDataProtector>();
|
||||
mockInnerProtector.Setup(o => o.CreateProtector("new purpose").CreateProtector(TimeLimitedPurposeString).Protect(
|
||||
new byte[] {
|
||||
0x08, 0xc1, 0x22, 0x02, 0x47, 0xe4, 0x40, 0x00, /* header */
|
||||
0x01, 0x02, 0x03, 0x04, 0x05 /* payload */
|
||||
})).Returns(new byte[] { 0x10, 0x11 });
|
||||
|
||||
var timeLimitedProtector = new TimeLimitedDataProtector(mockInnerProtector.Object);
|
||||
|
||||
// Act
|
||||
var subProtector = timeLimitedProtector.CreateProtector("new purpose");
|
||||
var protectedPayload = subProtector.Protect(new byte[] { 0x01, 0x02, 0x03, 0x04, 0x05 }, expiration);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(new byte[] { 0x10, 0x11 }, protectedPayload);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Protect_LifetimeNotSpecified_UsesInfiniteLifetime()
|
||||
{
|
||||
// Arrange
|
||||
// 0x2bca2875f4373fff is the representation of DateTimeOffset.MaxValue.
|
||||
DateTimeOffset expiration = StringToDateTime("2000-01-01 00:00:00Z");
|
||||
var mockInnerProtector = new Mock<IDataProtector>();
|
||||
mockInnerProtector.Setup(o => o.CreateProtector("new purpose").CreateProtector(TimeLimitedPurposeString).Protect(
|
||||
new byte[] {
|
||||
0x2b, 0xca, 0x28, 0x75, 0xf4, 0x37, 0x3f, 0xff, /* header */
|
||||
0x01, 0x02, 0x03, 0x04, 0x05 /* payload */
|
||||
})).Returns(new byte[] { 0x10, 0x11 });
|
||||
|
||||
var timeLimitedProtector = new TimeLimitedDataProtector(mockInnerProtector.Object);
|
||||
|
||||
// Act
|
||||
var subProtector = timeLimitedProtector.CreateProtector("new purpose");
|
||||
var protectedPayload = subProtector.Protect(new byte[] { 0x01, 0x02, 0x03, 0x04, 0x05 });
|
||||
|
||||
// Assert
|
||||
Assert.Equal(new byte[] { 0x10, 0x11 }, protectedPayload);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Unprotect_WithinPayloadValidityPeriod_Success()
|
||||
{
|
||||
// Arrange
|
||||
// 0x08c1220247e44000 is the representation of midnight 2000-01-01 UTC.
|
||||
DateTimeOffset expectedExpiration = StringToDateTime("2000-01-01 00:00:00Z");
|
||||
DateTimeOffset now = StringToDateTime("1999-01-01 00:00:00Z");
|
||||
var mockInnerProtector = new Mock<IDataProtector>();
|
||||
mockInnerProtector.Setup(o => o.CreateProtector(TimeLimitedPurposeString).Unprotect(new byte[] { 0x10, 0x11 })).Returns(
|
||||
new byte[] {
|
||||
0x08, 0xc1, 0x22, 0x02, 0x47, 0xe4, 0x40, 0x00, /* header */
|
||||
0x01, 0x02, 0x03, 0x04, 0x05 /* payload */
|
||||
});
|
||||
|
||||
var timeLimitedProtector = new TimeLimitedDataProtector(mockInnerProtector.Object);
|
||||
|
||||
// Act
|
||||
DateTimeOffset actualExpiration;
|
||||
var retVal = timeLimitedProtector.UnprotectCore(new byte[] { 0x10, 0x11 }, now, out actualExpiration);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expectedExpiration, actualExpiration);
|
||||
Assert.Equal(new byte[] { 0x01, 0x02, 0x03, 0x04, 0x05 }, retVal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Unprotect_PayloadHasExpired_Fails()
|
||||
{
|
||||
// Arrange
|
||||
// 0x08c1220247e44000 is the representation of midnight 2000-01-01 UTC.
|
||||
DateTimeOffset expectedExpiration = StringToDateTime("2000-01-01 00:00:00Z");
|
||||
DateTimeOffset now = StringToDateTime("2001-01-01 00:00:00Z");
|
||||
var mockInnerProtector = new Mock<IDataProtector>();
|
||||
mockInnerProtector.Setup(o => o.CreateProtector(TimeLimitedPurposeString).Unprotect(new byte[] { 0x10, 0x11 })).Returns(
|
||||
new byte[] {
|
||||
0x08, 0xc1, 0x22, 0x02, 0x47, 0xe4, 0x40, 0x00, /* header */
|
||||
0x01, 0x02, 0x03, 0x04, 0x05 /* payload */
|
||||
});
|
||||
|
||||
var timeLimitedProtector = new TimeLimitedDataProtector(mockInnerProtector.Object);
|
||||
|
||||
// Act & assert
|
||||
DateTimeOffset unused;
|
||||
var ex = Assert.Throws<CryptographicException>(() => timeLimitedProtector.UnprotectCore(new byte[] { 0x10, 0x11 }, now, out unused));
|
||||
|
||||
// Assert
|
||||
Assert.Equal(Resources.FormatTimeLimitedDataProtector_PayloadExpired(expectedExpiration), ex.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Unprotect_ProtectedDataMalformed_Fails()
|
||||
{
|
||||
// Arrange
|
||||
// 0x08c1220247e44000 is the representation of midnight 2000-01-01 UTC.
|
||||
var mockInnerProtector = new Mock<IDataProtector>();
|
||||
mockInnerProtector.Setup(o => o.CreateProtector(TimeLimitedPurposeString).Unprotect(new byte[] { 0x10, 0x11 })).Returns(
|
||||
new byte[] {
|
||||
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 /* header too short */
|
||||
});
|
||||
|
||||
var timeLimitedProtector = new TimeLimitedDataProtector(mockInnerProtector.Object);
|
||||
|
||||
// Act & assert
|
||||
DateTimeOffset unused;
|
||||
var ex = Assert.Throws<CryptographicException>(() => timeLimitedProtector.Unprotect(new byte[] { 0x10, 0x11 }, out unused));
|
||||
|
||||
// Assert
|
||||
Assert.Equal(Resources.TimeLimitedDataProtector_PayloadInvalid, ex.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Unprotect_UnprotectOperationFails_HomogenizesExceptionToCryptographicException()
|
||||
{
|
||||
// Arrange
|
||||
// 0x08c1220247e44000 is the representation of midnight 2000-01-01 UTC.
|
||||
var mockInnerProtector = new Mock<IDataProtector>();
|
||||
mockInnerProtector.Setup(o => o.CreateProtector(TimeLimitedPurposeString).Unprotect(new byte[] { 0x10, 0x11 })).Throws(new Exception("How exceptional!"));
|
||||
var timeLimitedProtector = new TimeLimitedDataProtector(mockInnerProtector.Object);
|
||||
|
||||
// Act & assert
|
||||
DateTimeOffset unused;
|
||||
var ex = Assert.Throws<CryptographicException>(() => timeLimitedProtector.Unprotect(new byte[] { 0x10, 0x11 }, out unused));
|
||||
|
||||
// Assert
|
||||
Assert.Equal(Resources.CryptCommon_GenericError, ex.Message);
|
||||
Assert.Equal("How exceptional!", ex.InnerException.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RoundTrip_ProtectedData()
|
||||
{
|
||||
// Arrange
|
||||
var ephemeralProtector = new EphemeralDataProtectionProvider().CreateProtector("my purpose");
|
||||
var timeLimitedProtector = new TimeLimitedDataProtector(ephemeralProtector);
|
||||
var expectedExpiration = StringToDateTime("2020-01-01 00:00:00Z");
|
||||
|
||||
// Act
|
||||
byte[] ephemeralProtectedPayload = ephemeralProtector.Protect(new byte[] { 0x01, 0x02, 0x03, 0x04 });
|
||||
byte[] timeLimitedProtectedPayload = timeLimitedProtector.Protect(new byte[] { 0x11, 0x22, 0x33, 0x44 }, expectedExpiration);
|
||||
|
||||
// Assert
|
||||
DateTimeOffset actualExpiration;
|
||||
Assert.Equal(new byte[] { 0x11, 0x22, 0x33, 0x44 }, timeLimitedProtector.UnprotectCore(timeLimitedProtectedPayload, StringToDateTime("2010-01-01 00:00:00Z"), out actualExpiration));
|
||||
Assert.Equal(expectedExpiration, actualExpiration);
|
||||
|
||||
// the two providers shouldn't be able to talk to one another (due to the purpose chaining)
|
||||
Assert.Throws<CryptographicException>(() => ephemeralProtector.Unprotect(timeLimitedProtectedPayload));
|
||||
Assert.Throws<CryptographicException>(() => timeLimitedProtector.Unprotect(ephemeralProtectedPayload, out actualExpiration));
|
||||
}
|
||||
|
||||
private static DateTime StringToDateTime(string input)
|
||||
{
|
||||
return DateTimeOffset.ParseExact(input, "u", CultureInfo.InvariantCulture).UtcDateTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"dependencies": {
|
||||
"Microsoft.AspNet.DataProtection.Interfaces": "1.0.0-*",
|
||||
"Microsoft.AspNet.DataProtection.Extensions": "1.0.0-*",
|
||||
"Microsoft.AspNet.Testing": "1.0.0-*",
|
||||
"Moq": "4.2.1312.1622",
|
||||
"xunit.runner.aspnet": "2.0.0-aspnet-*"
|
||||
},
|
||||
"frameworks": {
|
||||
"dnx451": { }
|
||||
},
|
||||
"commands": {
|
||||
"test": "xunit.runner.aspnet"
|
||||
},
|
||||
"compilationOptions": {
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -1,40 +0,0 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNet.DataProtection
|
||||
{
|
||||
public class DataProtectionExtensionsTests
|
||||
{
|
||||
[Fact]
|
||||
public void AsTimeLimitedProtector_ProtectorIsAlreadyTimeLimited_ReturnsThis()
|
||||
{
|
||||
// Arrange
|
||||
var originalProtector = new Mock<ITimeLimitedDataProtector>().Object;
|
||||
|
||||
// Act
|
||||
var retVal = originalProtector.AsTimeLimitedDataProtector();
|
||||
|
||||
// Assert
|
||||
Assert.Same(originalProtector, retVal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AsTimeLimitedProtector_ProtectorIsNotTimeLimited_CreatesNewProtector()
|
||||
{
|
||||
// Arrange
|
||||
var innerProtector = new Mock<IDataProtector>().Object;
|
||||
var outerProtectorMock = new Mock<IDataProtector>();
|
||||
outerProtectorMock.Setup(o => o.CreateProtector("Microsoft.AspNet.DataProtection.TimeLimitedDataProtector")).Returns(innerProtector);
|
||||
|
||||
// Act
|
||||
var timeLimitedProtector = (TimeLimitedDataProtector)outerProtectorMock.Object.AsTimeLimitedDataProtector();
|
||||
|
||||
// Assert
|
||||
Assert.Same(innerProtector, timeLimitedProtector.InnerProtector);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,87 +0,0 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. 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.Security.Cryptography;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNet.DataProtection
|
||||
{
|
||||
public class TimeLimitedDataProtectorTests
|
||||
{
|
||||
[Fact]
|
||||
public void CreateProtector_And_Protect()
|
||||
{
|
||||
// Arrange
|
||||
// 0x08c1220247e44000 is the representation of midnight 2000-01-01 UTC.
|
||||
DateTimeOffset expiration = new DateTimeOffset(new DateTime(2000, 1, 1, 0, 0, 0, DateTimeKind.Utc));
|
||||
Mock<IDataProtector> innerProtectorMock = new Mock<IDataProtector>();
|
||||
innerProtectorMock.Setup(o => o.Protect(new byte[] { 0x08, 0xc1, 0x22, 0x02, 0x47, 0xe4, 0x40, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05 })).Returns(new byte[] { 0x10, 0x11 });
|
||||
Mock<IDataProtector> outerProtectorMock = new Mock<IDataProtector>();
|
||||
outerProtectorMock.Setup(p => p.CreateProtector("new purpose")).Returns(innerProtectorMock.Object);
|
||||
|
||||
// Act
|
||||
var timeLimitedProtector = new TimeLimitedDataProtector(outerProtectorMock.Object);
|
||||
var subProtector = timeLimitedProtector.CreateProtector("new purpose");
|
||||
var protectedPayload = subProtector.Protect(new byte[] { 0x01, 0x02, 0x03, 0x04, 0x05 }, expiration);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(new byte[] { 0x10, 0x11 }, protectedPayload);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExpiredData_Fails()
|
||||
{
|
||||
// Arrange
|
||||
var timeLimitedProtector = CreateEphemeralTimeLimitedProtector();
|
||||
var expiration = DateTimeOffset.UtcNow.AddYears(-1);
|
||||
|
||||
// Act & assert
|
||||
var protectedData = timeLimitedProtector.Protect(new byte[] { 0x04, 0x08, 0x0c }, expiration);
|
||||
Assert.Throws<CryptographicException>(() =>
|
||||
{
|
||||
timeLimitedProtector.Unprotect(protectedData);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GoodData_RoundTrips()
|
||||
{
|
||||
// Arrange
|
||||
var timeLimitedProtector = CreateEphemeralTimeLimitedProtector();
|
||||
var expectedExpiration = DateTimeOffset.UtcNow.AddYears(1);
|
||||
|
||||
// Act
|
||||
var protectedData = timeLimitedProtector.Protect(new byte[] { 0x04, 0x08, 0x0c }, expectedExpiration);
|
||||
DateTimeOffset actualExpiration;
|
||||
var unprotectedData = timeLimitedProtector.Unprotect(protectedData, out actualExpiration);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(new byte[] { 0x04, 0x08, 0x0c }, unprotectedData);
|
||||
Assert.Equal(expectedExpiration, actualExpiration);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Protect_NoExpiration_UsesDateTimeOffsetMaxValue()
|
||||
{
|
||||
// Should pass DateTimeOffset.MaxValue (utc ticks = 0x2bca2875f4373fff) if no expiration date specified
|
||||
|
||||
// Arrange
|
||||
Mock<IDataProtector> innerProtectorMock = new Mock<IDataProtector>();
|
||||
innerProtectorMock.Setup(o => o.Protect(new byte[] { 0x2b, 0xca, 0x28, 0x75, 0xf4, 0x37, 0x3f, 0xff,0x01, 0x02, 0x03, 0x04, 0x05 })).Returns(new byte[] { 0x10, 0x11 });
|
||||
|
||||
// Act
|
||||
var timeLimitedProtector = new TimeLimitedDataProtector(innerProtectorMock.Object);
|
||||
var protectedPayload = timeLimitedProtector.Protect(new byte[] { 0x01, 0x02, 0x03, 0x04, 0x05 });
|
||||
|
||||
// Assert
|
||||
Assert.Equal(new byte[] { 0x10, 0x11 }, protectedPayload);
|
||||
}
|
||||
|
||||
private static TimeLimitedDataProtector CreateEphemeralTimeLimitedProtector()
|
||||
{
|
||||
return new TimeLimitedDataProtector(new EphemeralDataProtectionProvider().CreateProtector("purpose"));
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue