Skip to content

Commit b3e5b42

Browse files
committed
key ring based part
1 parent b9b45b2 commit b3e5b42

File tree

6 files changed

+99
-5
lines changed

6 files changed

+99
-5
lines changed

src/DataProtection/DataProtection/src/AuthenticatedEncryption/AuthenticatedEncryptorExtensions.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,21 @@ public static byte[] Encrypt(this IAuthenticatedEncryptor encryptor, ArraySegmen
3232
}
3333
}
3434

35+
#if NET10_0_OR_GREATER
36+
public static bool TryEncrypt(
37+
this IAuthenticatedEncryptor encryptor,
38+
ReadOnlySpan<byte> plaintext,
39+
ReadOnlySpan<byte> additionalAuthenticatedData,
40+
Span<byte> destination,
41+
uint preBufferSize,
42+
uint postBufferSize,
43+
out int bytesWritten)
44+
{
45+
var plaintextWithOffsets = plaintext.Slice((int)preBufferSize, plaintext.Length - (int)(preBufferSize + postBufferSize));
46+
return encryptor.TryEncrypt(plaintextWithOffsets, additionalAuthenticatedData, destination, out bytesWritten);
47+
}
48+
#endif
49+
3550
/// <summary>
3651
/// Performs a self-test of this encryptor by running a sample payload through an
3752
/// encrypt-then-decrypt operation. Throws if the operation fails.

src/DataProtection/DataProtection/src/AuthenticatedEncryption/IAuthenticatedEncryptor.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,6 @@ public interface IAuthenticatedEncryptor
3535

3636
#if NET10_0_OR_GREATER
3737
int GetEncryptedSize(int plainTextLength);
38-
bool TryEncrypt(ReadOnlySpan<byte> plainText, ReadOnlySpan<byte> additionalAuthenticatedData, Span<byte> destination, out int bytesWritten);
38+
bool TryEncrypt(ReadOnlySpan<byte> plaintext, ReadOnlySpan<byte> additionalAuthenticatedData, Span<byte> destination, out int bytesWritten);
3939
#endif
4040
}

src/DataProtection/DataProtection/src/KeyManagement/KeyRingBasedDataProtector.cs

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,9 +101,51 @@ public int GetProtectedSize(ReadOnlySpan<byte> plainText)
101101
return defaultEncryptor.GetEncryptedSize(plainText.Length);
102102
}
103103

104-
public bool TryProtect(ReadOnlySpan<byte> plainText, Span<byte> destination, out int bytesWritten)
104+
public bool TryProtect(ReadOnlySpan<byte> plaintext, Span<byte> destination, out int bytesWritten)
105105
{
106-
throw new NotImplementedException();
106+
try
107+
{
108+
// Perform the encryption operation using the current default encryptor.
109+
var currentKeyRing = _keyRingProvider.GetCurrentKeyRing();
110+
var defaultKeyId = currentKeyRing.DefaultKeyId;
111+
var defaultEncryptorInstance = currentKeyRing.DefaultAuthenticatedEncryptor;
112+
CryptoUtil.Assert(defaultEncryptorInstance != null, "defaultEncryptorInstance != null");
113+
114+
if (_logger.IsDebugLevelEnabled())
115+
{
116+
_logger.PerformingProtectOperationToKeyWithPurposes(defaultKeyId, JoinPurposesForLog(Purposes));
117+
}
118+
119+
// We'll need to apply the default key id to the template if it hasn't already been applied.
120+
// If the default key id has been updated since the last call to Protect, also write back the updated template.
121+
var aad = _aadTemplate.GetAadForKey(defaultKeyId, isProtecting: true);
122+
123+
// We allocate a 20-byte pre-buffer so that we can inject the magic header and key id into the return value.
124+
var success = defaultEncryptorInstance.TryEncrypt(
125+
plaintext: plaintext,
126+
additionalAuthenticatedData: aad,
127+
destination: destination,
128+
preBufferSize: (uint)(sizeof(uint) + sizeof(Guid)),
129+
postBufferSize: 0,
130+
out bytesWritten);
131+
132+
// At this point: destination := { 000..000 || encryptorSpecificProtectedPayload },
133+
// where 000..000 is a placeholder for our magic header and key id.
134+
135+
// Write out the magic header and key id
136+
BinaryPrimitives.WriteUInt32BigEndian(destination.Slice(0, sizeof(uint)), MAGIC_HEADER_V0);
137+
var writeKeyIdResult = defaultKeyId.TryWriteBytes(destination.Slice(sizeof(uint), sizeof(Guid)));
138+
Debug.Assert(writeKeyIdResult, "Failed to write Guid to destination.");
139+
140+
// At this point, destination := { magicHeader || keyId || encryptorSpecificProtectedPayload }
141+
// And we're done!
142+
return success;
143+
}
144+
catch (Exception ex) when (ex.RequiresHomogenization())
145+
{
146+
// homogenize all errors to CryptographicException
147+
throw Error.Common_EncryptionFailed(ex);
148+
}
107149
}
108150
#endif
109151

src/DataProtection/DataProtection/test/Microsoft.AspNetCore.DataProtection.Tests/KeyManagement/KeyRingBasedDataProtectorTests.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
using System.Buffers.Binary;
45
using System.Globalization;
56
using System.Net;
67
using System.Text;
@@ -619,6 +620,12 @@ public void CreateProtector_ChainsPurposes()
619620
Assert.Equal(expectedProtectedData, retVal);
620621
}
621622

623+
[Fact]
624+
public void GetProtectedSize_TryProtect_CorrectlyEstimatesDataLength()
625+
{
626+
// TODO!
627+
}
628+
622629
private static byte[] BuildAadFromPurposeStrings(Guid keyId, params string[] purposes)
623630
{
624631
var expectedAad = new byte[] { 0x09, 0xF0, 0xC9, 0xF0 } // magic header

src/DataProtection/Extensions/src/BitHelpers.cs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
using System;
5+
46
namespace Microsoft.AspNetCore.DataProtection;
57

68
internal static class BitHelpers
@@ -36,4 +38,20 @@ public static void WriteUInt64(byte[] buffer, int offset, ulong value)
3638
buffer[offset + 6] = (byte)(value >> 8);
3739
buffer[offset + 7] = (byte)(value);
3840
}
41+
42+
/// <summary>
43+
/// Writes an unsigned 64-bit integer to <paramref name="buffer"/> starting at
44+
/// offset <paramref name="offset"/>. Data is written big-endian.
45+
/// </summary>
46+
public static void WriteUInt64(Span<byte> buffer, int offset, ulong value)
47+
{
48+
buffer[offset + 0] = (byte)(value >> 56);
49+
buffer[offset + 1] = (byte)(value >> 48);
50+
buffer[offset + 2] = (byte)(value >> 40);
51+
buffer[offset + 3] = (byte)(value >> 32);
52+
buffer[offset + 4] = (byte)(value >> 24);
53+
buffer[offset + 5] = (byte)(value >> 16);
54+
buffer[offset + 6] = (byte)(value >> 8);
55+
buffer[offset + 7] = (byte)(value);
56+
}
3957
}

src/DataProtection/Extensions/src/TimeLimitedDataProtector.cs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -126,13 +126,25 @@ byte[] IDataProtector.Unprotect(byte[] protectedData)
126126
return Unprotect(protectedData, out _);
127127
}
128128

129+
#if NET10_0_OR_GREATER
129130
public int GetProtectedSize(ReadOnlySpan<byte> plainText)
130131
{
131-
throw new NotImplementedException();
132+
var dataProtector = GetInnerProtectorWithTimeLimitedPurpose();
133+
var size = dataProtector.GetProtectedSize(plainText);
134+
135+
// prepended the expiration time as a 64-bit UTC tick count takes 8 bytes;
136+
// see Protect(byte[] plaintext, DateTimeOffset expiration) for details
137+
return size + 8;
132138
}
133139

134-
public bool TryProtect(ReadOnlySpan<byte> plainText, Span<byte> destination, out int bytesWritten)
140+
public bool TryProtect(ReadOnlySpan<byte> plaintext, Span<byte> destination, out int bytesWritten)
141+
=> TryProtect(plaintext, destination, DateTimeOffset.MaxValue, out bytesWritten);
142+
143+
public bool TryProtect(ReadOnlySpan<byte> plaintext, Span<byte> destination, DateTimeOffset expiration, out int bytesWritten)
135144
{
145+
// we cant prepend the expiration time as a 64-bit UTC tick count
146+
// because input is ReadOnlySpan<byte>
136147
throw new NotImplementedException();
137148
}
149+
#endif
138150
}

0 commit comments

Comments
 (0)