Skip to content
This repository has been archived by the owner on Jan 23, 2023. It is now read-only.
/ corefx Public archive

Add FixedTimeEquals and other crypto helper routines as public API #27103

Merged
merged 6 commits into from
Feb 14, 2018
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
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,6 @@ internal partial class AuthenticationHelper
// 48='0', 65='A', 97='a'
private static int[] s_alphaNumChooser = new int[] { 48, 65, 97 };

// Define a random number generator for cnonce
private static RandomNumberGenerator s_rng = RandomNumberGenerator.Create();

public async static Task<bool> TrySetDigestAuthToken(HttpRequestMessage request, ICredentials credentials, DigestResponse digestResponse, string authHeader)
{
NetworkCredential credential = credentials.GetCredential(request.RequestUri, Digest);
Expand Down Expand Up @@ -218,7 +215,7 @@ private static string GetRandomAlphaNumericString()
{
const int Length = 16;
Span<byte> randomNumbers = stackalloc byte[Length * 2];
s_rng.GetBytes(randomNumbers);
RandomNumberGenerator.Fill(randomNumbers);

StringBuilder sb = StringBuilderCache.Acquire(Length);
for (int i = 0; i < randomNumbers.Length;)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ public void AppendData(System.ReadOnlySpan<byte> data) { }
}
public abstract partial class RandomNumberGenerator : System.IDisposable
{
public static void Fill(Span<byte> data) => throw null;
public virtual void GetBytes(System.Span<byte> data) { }
public virtual void GetNonZeroBytes(System.Span<byte> data) { }
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ namespace System.Security.Cryptography
{
partial class RandomNumberGeneratorImplementation
{
private void GetBytes(ref byte pbBuffer, int count)
private static void GetBytes(ref byte pbBuffer, int count)
{
Debug.Assert(count > 0);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ namespace System.Security.Cryptography
{
partial class RandomNumberGeneratorImplementation
{
private void GetBytes(ref byte pbBuffer, int count)
private static void GetBytes(ref byte pbBuffer, int count)
{
Debug.Assert(count > 0);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ namespace System.Security.Cryptography
{
partial class RandomNumberGeneratorImplementation
{
private void GetBytes(ref byte pbBuffer, int count)
private static void GetBytes(ref byte pbBuffer, int count)
{
Debug.Assert(count > 0);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,16 @@ namespace System.Security.Cryptography
{
internal sealed partial class RandomNumberGeneratorImplementation : RandomNumberGenerator
{
// As long as each implementation can provide a static GetBytes(ref byte buf, int length)
// they can share this one implementation of FillSpan.
internal static void FillSpan(Span<byte> data)
{
if (data.Length > 0)
{
GetBytes(ref MemoryMarshal.GetReference(data), data.Length);
}
}

public override void GetBytes(byte[] data)
{
if (data == null) throw new ArgumentNullException(nameof(data));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,11 @@ public virtual void GetNonZeroBytes(Span<byte> data)
}
}

public static void Fill(Span<byte> data)
{
RandomNumberGeneratorImplementation.FillSpan(data);
}

internal void VerifyGetBytes(byte[] data, int offset, int count)
{
if (data == null) throw new ArgumentNullException(nameof(data));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,5 +55,42 @@ public static void GetNonZeroBytes_Span()
Assert.Equal(-1, Array.IndexOf<byte>(rand, 0));
}
}

[Fact]
public static void Fill_ZeroLengthSpan()
{
byte[] rand = { 1 };
RandomNumberGenerator.Fill(new Span<byte>(rand, 0, 0));
Assert.Equal(1, rand[0]);
}

[Fact]
public static void Fill_SpanLength1()
{
byte[] rand = { 1 };
bool replacedValue = false;

for (int i = 0; i < 10; i++)
{
RandomNumberGenerator.Fill(rand);

if (rand[0] != 1)
{
replacedValue = true;
break;
}
}

Assert.True(replacedValue, "Fill eventually wrote a different byte");
}

[Fact]
public static void Fill_RandomDistribution()
{
byte[] random = new byte[2048];
RandomNumberGenerator.Fill(random);

RandomDataGenerator.VerifyRandomDistribution(random);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,24 +20,48 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{E107E9C1-E89
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ref", "ref", "{2E666815-2EDB-464B-9DF6-380BF4789AD4}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "System.Security.Cryptography.Primitives.Performance.Tests", "tests\Performance\System.Security.Cryptography.Primitives.Performance.Tests.csproj", "{FB3EA273-567D-414F-B36D-3698BE8D198B}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
netcoreapp-Debug|Any CPU = netcoreapp-Debug|Any CPU
netcoreapp-Release|Any CPU = netcoreapp-Release|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{101EB757-55A4-4F48-841C-C088640B8F57}.Debug|Any CPU.ActiveCfg = netcoreapp-Debug|Any CPU
{101EB757-55A4-4F48-841C-C088640B8F57}.Debug|Any CPU.Build.0 = netcoreapp-Debug|Any CPU
{101EB757-55A4-4F48-841C-C088640B8F57}.netcoreapp-Debug|Any CPU.ActiveCfg = netcoreapp-Debug|Any CPU
{101EB757-55A4-4F48-841C-C088640B8F57}.netcoreapp-Debug|Any CPU.Build.0 = netcoreapp-Debug|Any CPU
{101EB757-55A4-4F48-841C-C088640B8F57}.netcoreapp-Release|Any CPU.ActiveCfg = netcoreapp-Release|Any CPU
{101EB757-55A4-4F48-841C-C088640B8F57}.netcoreapp-Release|Any CPU.Build.0 = netcoreapp-Release|Any CPU
{101EB757-55A4-4F48-841C-C088640B8F57}.Release|Any CPU.ActiveCfg = netcoreapp-Release|Any CPU
{101EB757-55A4-4F48-841C-C088640B8F57}.Release|Any CPU.Build.0 = netcoreapp-Release|Any CPU
{DF73E985-E143-4BF5-9FA4-E199E7D36235}.Debug|Any CPU.ActiveCfg = netcoreapp-Debug|Any CPU
{DF73E985-E143-4BF5-9FA4-E199E7D36235}.Debug|Any CPU.Build.0 = netcoreapp-Debug|Any CPU
{DF73E985-E143-4BF5-9FA4-E199E7D36235}.netcoreapp-Debug|Any CPU.ActiveCfg = netcoreapp-Debug|Any CPU
{DF73E985-E143-4BF5-9FA4-E199E7D36235}.netcoreapp-Debug|Any CPU.Build.0 = netcoreapp-Debug|Any CPU
{DF73E985-E143-4BF5-9FA4-E199E7D36235}.netcoreapp-Release|Any CPU.ActiveCfg = netcoreapp-Release|Any CPU
{DF73E985-E143-4BF5-9FA4-E199E7D36235}.netcoreapp-Release|Any CPU.Build.0 = netcoreapp-Release|Any CPU
{DF73E985-E143-4BF5-9FA4-E199E7D36235}.Release|Any CPU.ActiveCfg = netcoreapp-Release|Any CPU
{DF73E985-E143-4BF5-9FA4-E199E7D36235}.Release|Any CPU.Build.0 = netcoreapp-Release|Any CPU
{F050C895-297F-41C6-98C3-406D791AD515}.Debug|Any CPU.ActiveCfg = netcoreapp-Debug|Any CPU
{F050C895-297F-41C6-98C3-406D791AD515}.Debug|Any CPU.Build.0 = netcoreapp-Debug|Any CPU
{F050C895-297F-41C6-98C3-406D791AD515}.netcoreapp-Debug|Any CPU.ActiveCfg = netcoreapp-Debug|Any CPU
{F050C895-297F-41C6-98C3-406D791AD515}.netcoreapp-Debug|Any CPU.Build.0 = netcoreapp-Debug|Any CPU
{F050C895-297F-41C6-98C3-406D791AD515}.netcoreapp-Release|Any CPU.ActiveCfg = netcoreapp-Release|Any CPU
{F050C895-297F-41C6-98C3-406D791AD515}.netcoreapp-Release|Any CPU.Build.0 = netcoreapp-Release|Any CPU
{F050C895-297F-41C6-98C3-406D791AD515}.Release|Any CPU.ActiveCfg = netcoreapp-Release|Any CPU
{F050C895-297F-41C6-98C3-406D791AD515}.Release|Any CPU.Build.0 = netcoreapp-Release|Any CPU
{FB3EA273-567D-414F-B36D-3698BE8D198B}.Debug|Any CPU.ActiveCfg = netcoreapp-Debug|Any CPU
{FB3EA273-567D-414F-B36D-3698BE8D198B}.Debug|Any CPU.Build.0 = netcoreapp-Debug|Any CPU
{FB3EA273-567D-414F-B36D-3698BE8D198B}.netcoreapp-Debug|Any CPU.ActiveCfg = netcoreapp-Debug|Any CPU
{FB3EA273-567D-414F-B36D-3698BE8D198B}.netcoreapp-Debug|Any CPU.Build.0 = netcoreapp-Debug|Any CPU
{FB3EA273-567D-414F-B36D-3698BE8D198B}.netcoreapp-Release|Any CPU.ActiveCfg = netcoreapp-Release|Any CPU
{FB3EA273-567D-414F-B36D-3698BE8D198B}.netcoreapp-Release|Any CPU.Build.0 = netcoreapp-Release|Any CPU
{FB3EA273-567D-414F-B36D-3698BE8D198B}.Release|Any CPU.ActiveCfg = netcoreapp-Release|Any CPU
{FB3EA273-567D-414F-B36D-3698BE8D198B}.Release|Any CPU.Build.0 = netcoreapp-Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -46,5 +70,6 @@ Global
{101EB757-55A4-4F48-841C-C088640B8F57} = {1A2F9F4A-A032-433E-B914-ADD5992BB178}
{DF73E985-E143-4BF5-9FA4-E199E7D36235} = {E107E9C1-E893-4E87-987E-04EF0DCEAEFD}
{F050C895-297F-41C6-98C3-406D791AD515} = {2E666815-2EDB-464B-9DF6-380BF4789AD4}
{FB3EA273-567D-414F-B36D-3698BE8D198B} = {1A2F9F4A-A032-433E-B914-ADD5992BB178}
EndGlobalSection
EndGlobal
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@ public enum CipherMode
[System.ComponentModel.EditorBrowsableAttribute((System.ComponentModel.EditorBrowsableState)(1))]
OFB = 3,
}
public static partial class CryptographicOperations
{
public static bool FixedTimeEquals(System.ReadOnlySpan<byte> left, System.ReadOnlySpan<byte> right) => throw null;
public static void ZeroMemory(System.Span<byte> buffer) => throw null;
}
public partial class CryptographicUnexpectedOperationException : System.Security.Cryptography.CryptographicException
{
public CryptographicUnexpectedOperationException() { }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,4 @@
<ProjectReference Include="..\..\System.Threading.Tasks\ref\System.Threading.Tasks.csproj" />
</ItemGroup>
<Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.targets))\dir.targets" />
</Project>
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
<ItemGroup>
<Compile Include="System\Security\Cryptography\AsymmetricAlgorithm.cs" />
<Compile Include="System\Security\Cryptography\CipherMode.cs" />
<Compile Include="System\Security\Cryptography\CryptographicOperations.cs" />
<Compile Include="System\Security\Cryptography\CryptographicUnexpectedOperationException.cs" />
<Compile Include="System\Security\Cryptography\CryptoStream.cs" />
<Compile Include="System\Security\Cryptography\CryptoStreamMode.cs" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System.Runtime.CompilerServices;

namespace System.Security.Cryptography
{
public static class CryptographicOperations
{
/// <summary>
/// Determine the equality of two byte sequences in an amount of time which depends on
/// the length of the sequences, but not the values.
/// </summary>
/// <param name="left">The first buffer to compare.</param>
/// <param name="right">The second buffer to compare.</param>
/// <returns>
/// <c>true</c> if <paramref name="left"/> and <paramref name="right"/> have the same
/// values for <see cref="ReadOnlySpan{T}.Length"/> and the same contents, <c>false</c>
/// otherwise.
/// </returns>
/// <remarks>
/// This method compares two buffers' contents for equality in a manner which does not
/// leak timing information, making it ideal for use within cryptographic routines.
/// This method will short-circuit and return <c>false</c> only if <paramref name="left"/>
/// and <paramref name="right"/> have different lengths.
///
/// Fixed-time behavior is guaranteed in all other cases, including if <paramref name="left"/>
/// and <paramref name="right"/> reference the same address.
/// </remarks>
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
public static bool FixedTimeEquals(ReadOnlySpan<byte> left, ReadOnlySpan<byte> right)
{
// NoOptimization because we want this method to be exactly as non-short-circuiting
// as written.
//
// NoInlining because the NoOptimization would get lost if the method got inlined.

if (left.Length != right.Length)
{
return false;
}

int length = left.Length;
int accum = 0;

for (int i = 0; i < length; i++)
{
accum |= left[i] - right[i];
}

return accum == 0;
}

[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
public static void ZeroMemory(Span<byte> buffer)
{
// NoOptimize to prevent the optimizer from deciding this call is unnecessary
// NoInlining to prevent the inliner from forgetting that the method was no-optimize
buffer.Clear();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System.Buffers;
using System.Reflection;
using System.Runtime.CompilerServices;
using Xunit;

namespace System.Security.Cryptography.Primitives.Tests
{
public static class FixedTimeEqualsTests
{
[Theory]
[InlineData(0)]
[InlineData(1)]
[InlineData(128 / 8)]
[InlineData(256 / 8)]
[InlineData(512 / 8)]
[InlineData(96)]
[InlineData(1024)]
public static void EqualReturnsTrue(int byteLength)
{
byte[] rented = ArrayPool<byte>.Shared.Rent(byteLength);
Span<byte> testSpan = new Span<byte>(rented, 0, byteLength);
RandomNumberGenerator.Fill(testSpan);

byte[] rented2 = ArrayPool<byte>.Shared.Rent(byteLength);
Span<byte> testSpan2 = new Span<byte>(rented2, 0, byteLength);

testSpan.CopyTo(testSpan2);

bool isEqual = CryptographicOperations.FixedTimeEquals(testSpan, testSpan2);

ArrayPool<byte>.Shared.Return(rented);
ArrayPool<byte>.Shared.Return(rented2);

Assert.True(isEqual);
}

[Theory]
[InlineData(1)]
[InlineData(128 / 8)]
[InlineData(256 / 8)]
[InlineData(512 / 8)]
[InlineData(96)]
[InlineData(1024)]
public static void UnequalReturnsFalse(int byteLength)
{
byte[] rented = ArrayPool<byte>.Shared.Rent(byteLength);
Span<byte> testSpan = new Span<byte>(rented, 0, byteLength);
RandomNumberGenerator.Fill(testSpan);

byte[] rented2 = ArrayPool<byte>.Shared.Rent(byteLength);
Span<byte> testSpan2 = new Span<byte>(rented2, 0, byteLength);

testSpan.CopyTo(testSpan2);
testSpan[testSpan[0] % testSpan.Length] ^= 0xFF;

bool isEqual = CryptographicOperations.FixedTimeEquals(testSpan, testSpan2);

ArrayPool<byte>.Shared.Return(rented);
ArrayPool<byte>.Shared.Return(rented2);

Assert.False(isEqual);
}

[Theory]
[InlineData(1)]
[InlineData(128 / 8)]
[InlineData(256 / 8)]
[InlineData(512 / 8)]
[InlineData(96)]
[InlineData(1024)]
public static void DifferentLengthsReturnFalse(int byteLength)
{
byte[] rented = ArrayPool<byte>.Shared.Rent(byteLength);
Span<byte> testSpan = new Span<byte>(rented, 0, byteLength);
RandomNumberGenerator.Fill(testSpan);

byte[] rented2 = ArrayPool<byte>.Shared.Rent(byteLength);
Span<byte> testSpan2 = new Span<byte>(rented2, 0, byteLength);

testSpan.CopyTo(testSpan2);

bool isEqualA = CryptographicOperations.FixedTimeEquals(testSpan, testSpan2.Slice(0, byteLength - 1));
bool isEqualB = CryptographicOperations.FixedTimeEquals(testSpan.Slice(0, byteLength - 1), testSpan2);

ArrayPool<byte>.Shared.Return(rented);
ArrayPool<byte>.Shared.Return(rented2);

Assert.False(isEqualA, "value, value missing last byte");
Assert.False(isEqualB, "value missing last byte, value");
}

[Fact]
public static void HasCorrectMethodImpl()
{
Type t = typeof(CryptographicOperations);
MethodInfo mi = t.GetMethod(nameof(CryptographicOperations.FixedTimeEquals));

// This method cannot be optimized, or it loses its fixed time guarantees.
// It cannot be inlined, or it loses its no-optimization guarantee.
Assert.Equal(
MethodImplAttributes.NoInlining | MethodImplAttributes.NoOptimization,
mi.MethodImplementationFlags);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<BuildConfigurations>
netcoreapp;
</BuildConfigurations>
</PropertyGroup>
</Project>
Loading