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

Commit

Permalink
Add FixedTimeEquals and other crypto helper routines as public API
Browse files Browse the repository at this point in the history
  • Loading branch information
bartonjs authored Feb 14, 2018
1 parent 1c9cd81 commit 3872baf
Show file tree
Hide file tree
Showing 19 changed files with 494 additions and 9 deletions.
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

0 comments on commit 3872baf

Please sign in to comment.