Skip to content

Commit 9c0d240

Browse files
authored
Always Encrypted | Align reads of CekMdVersion and EkValueCount with TDS specification (#4240)
* Align read of keyMDVersion with TDS ULONGLONG datatype * Align read of EkValueCount in COLMETADATA with TDS USHORT datatype * Address test failures Test code uses reflection to call SqlTceCipherInfoEntry.Add
1 parent c92d901 commit 9c0d240

6 files changed

Lines changed: 37 additions & 42 deletions

File tree

src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncryptedHelperClasses.cs

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
// Licensed to the .NET Foundation under one or more agreements.
1+
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33
// See the LICENSE file in the project root for more information.
44

55
using System;
6+
using System.Buffers.Binary;
67
using System.Collections.Generic;
78
using System.Diagnostics;
89
using Microsoft.Data.SqlClient.Server;
@@ -20,7 +21,7 @@ internal class SqlEncryptionKeyInfo
2021
internal int databaseId;
2122
internal int cekId;
2223
internal int cekVersion;
23-
internal byte[] cekMdVersion;
24+
internal ulong cekMdVersion;
2425
internal string keyPath;
2526
internal string keyStoreName;
2627
internal string algorithmName;
@@ -63,7 +64,7 @@ internal class SqlTceCipherInfoEntry
6364
/// <summary>
6465
/// Cek MD Version
6566
/// </summary>
66-
private byte[] _cekMdVersion;
67+
private ulong _cekMdVersion;
6768

6869
/// <summary>
6970
/// Return the ordinal.
@@ -112,7 +113,7 @@ internal int CekVersion
112113
/// <summary>
113114
/// Return the CEK MD Version.
114115
/// </summary>
115-
internal byte[] CekMdVersion
116+
internal ulong CekMdVersion
116117
{
117118
get
118119
{
@@ -142,7 +143,7 @@ internal List<SqlEncryptionKeyInfo> ColumnEncryptionKeyValues
142143
/// <param name="keyPath"></param>
143144
/// <param name="keyStoreName"></param>
144145
/// <param name="algorithmName"></param>
145-
internal void Add(byte[] encryptedKey, int databaseId, int cekId, int cekVersion, byte[] cekMdVersion, string keyPath, string keyStoreName, string algorithmName)
146+
internal void Add(byte[] encryptedKey, int databaseId, int cekId, int cekVersion, ulong cekMdVersion, string keyPath, string keyStoreName, string algorithmName)
146147
{
147148

148149
Debug.Assert(_columnEncryptionKeyValues != null, "_columnEncryptionKeyValues should already be initialized.");
@@ -172,7 +173,6 @@ internal void Add(byte[] encryptedKey, int databaseId, int cekId, int cekVersion
172173
Debug.Assert(_databaseId == databaseId);
173174
Debug.Assert(_cekId == cekId);
174175
Debug.Assert(_cekVersion == cekVersion);
175-
Debug.Assert(_cekMdVersion != null && cekMdVersion != null && _cekMdVersion.Length == _cekMdVersion.Length);
176176
}
177177
}
178178

@@ -186,7 +186,7 @@ internal SqlTceCipherInfoEntry(int ordinal = 0)
186186
_databaseId = 0;
187187
_cekId = 0;
188188
_cekVersion = 0;
189-
_cekMdVersion = null;
189+
_cekMdVersion = 0;
190190
_columnEncryptionKeyValues = new List<SqlEncryptionKeyInfo>();
191191
}
192192
}
@@ -550,7 +550,7 @@ private byte[] SerializeToWriteFormat()
550550
totalLength += sizeof(int);
551551

552552
// Metadata version of the encryption key.
553-
totalLength += _cipherMetadata.EncryptionKeyInfo.cekMdVersion.Length;
553+
totalLength += sizeof(ulong);
554554

555555
// Normalization Rule Version.
556556
totalLength += sizeof(byte);
@@ -576,8 +576,8 @@ private byte[] SerializeToWriteFormat()
576576
SerializeIntIntoBuffer(_cipherMetadata.EncryptionKeyInfo.cekVersion, serializedWireFormat, ref consumedBytes);
577577

578578
// 6 - Write the metadata version of the encryption key.
579-
Buffer.BlockCopy(_cipherMetadata.EncryptionKeyInfo.cekMdVersion, 0, serializedWireFormat, consumedBytes, _cipherMetadata.EncryptionKeyInfo.cekMdVersion.Length);
580-
consumedBytes += _cipherMetadata.EncryptionKeyInfo.cekMdVersion.Length;
579+
BinaryPrimitives.WriteUInt64LittleEndian(serializedWireFormat.AsSpan(consumedBytes), _cipherMetadata.EncryptionKeyInfo.cekMdVersion);
580+
consumedBytes += sizeof(ulong);
581581

582582
// 7 - Write Normalization Rule Version.
583583
serializedWireFormat[consumedBytes++] = _cipherMetadata.NormalizationRuleVersion;

src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ColumnEncryptionKeyInfo.cs

Lines changed: 9 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
// Licensed to the .NET Foundation under one or more agreements.
1+
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33
// See the LICENSE file in the project root for more information.
44

55
using System;
6+
using System.Buffers.Binary;
67

78
namespace Microsoft.Data.SqlClient
89
{
@@ -16,10 +17,9 @@ internal class ColumnEncryptionKeyInfo
1617
internal readonly byte[] DecryptedKeyBytes;
1718
internal readonly byte[] KeyIdBytes;
1819
internal readonly byte[] DatabaseIdBytes;
19-
internal readonly byte[] KeyMetadataVersionBytes;
20+
internal readonly ulong KeyMetadataVersion;
2021

2122
private static readonly string _decryptedKeyName = "DecryptedKey";
22-
private static readonly string _keyMetadataVersionName = "KeyMetadataVersion";
2323
private static readonly string _className = "ColumnEncryptionKeyInfo";
2424
private static readonly string _bytePackageName = "BytePackage";
2525
private static readonly string _serializeToBufferMethodName = "SerializeToBuffer";
@@ -32,7 +32,7 @@ internal class ColumnEncryptionKeyInfo
3232
/// <param name="databaseId">database id for this column encryption key</param>
3333
/// <param name="keyMetadataVersion">key metadata version for this column encryption key</param>
3434
/// <param name="keyid">key id for this column encryption key</param>
35-
internal ColumnEncryptionKeyInfo(byte[] decryptedKey, int databaseId, byte[] keyMetadataVersion, int keyid)
35+
internal ColumnEncryptionKeyInfo(byte[] decryptedKey, int databaseId, ulong keyMetadataVersion, int keyid)
3636
{
3737

3838
if (decryptedKey == null)
@@ -43,19 +43,11 @@ internal ColumnEncryptionKeyInfo(byte[] decryptedKey, int databaseId, byte[] key
4343
{
4444
throw SQL.EmptyArgumentInConstructorInternal(_decryptedKeyName, _className);
4545
}
46-
if (keyMetadataVersion == null)
47-
{
48-
throw SQL.NullArgumentInConstructorInternal(_keyMetadataVersionName, _className);
49-
}
50-
if (0 == keyMetadataVersion.Length)
51-
{
52-
throw SQL.EmptyArgumentInConstructorInternal(_keyMetadataVersionName, _className);
53-
}
5446

5547
KeyId = keyid;
5648
DatabaseId = databaseId;
5749
DecryptedKeyBytes = decryptedKey;
58-
KeyMetadataVersionBytes = keyMetadataVersion;
50+
KeyMetadataVersion = keyMetadataVersion;
5951

6052
//Covert keyId to Bytes
6153
ushort keyIdUShort;
@@ -96,7 +88,8 @@ internal int GetLengthForSerialization()
9688
lengthForSerialization += DecryptedKeyBytes.Length;
9789
lengthForSerialization += KeyIdBytes.Length;
9890
lengthForSerialization += DatabaseIdBytes.Length;
99-
lengthForSerialization += KeyMetadataVersionBytes.Length;
91+
// KeyMetadataVersion is of type ulong which is 8 bytes
92+
lengthForSerialization += sizeof(ulong);
10093
return lengthForSerialization;
10194
}
10295

@@ -131,8 +124,8 @@ internal int SerializeToBuffer(byte[] bytePackage, int startOffset)
131124

132125
Buffer.BlockCopy(DatabaseIdBytes, 0, bytePackage, startOffset, DatabaseIdBytes.Length);
133126
startOffset += DatabaseIdBytes.Length;
134-
Buffer.BlockCopy(KeyMetadataVersionBytes, 0, bytePackage, startOffset, KeyMetadataVersionBytes.Length);
135-
startOffset += KeyMetadataVersionBytes.Length;
127+
BinaryPrimitives.WriteUInt64LittleEndian(bytePackage.AsSpan(startOffset), KeyMetadataVersion);
128+
startOffset += sizeof(ulong);
136129
Buffer.BlockCopy(KeyIdBytes, 0, bytePackage, startOffset, KeyIdBytes.Length);
137130
startOffset += KeyIdBytes.Length;
138131
Buffer.BlockCopy(DecryptedKeyBytes, 0, bytePackage, startOffset, DecryptedKeyBytes.Length);

src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.Encryption.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// See the LICENSE file in the project root for more information.
44

55
using System;
6+
using System.Buffers.Binary;
67
using System.Collections.Concurrent;
78
using System.Collections.Generic;
89
using System.Collections.ObjectModel;
@@ -1016,7 +1017,7 @@ private bool ReadDescribeEncryptionParameterResultsKeys(
10161017
databaseId: ds.GetInt32((int)DescribeParameterEncryptionResultSet1.DbId),
10171018
cekId: ds.GetInt32((int)DescribeParameterEncryptionResultSet1.KeyId),
10181019
cekVersion: ds.GetInt32((int)DescribeParameterEncryptionResultSet1.KeyVersion),
1019-
cekMdVersion: keyMdVersion,
1020+
cekMdVersion: BinaryPrimitives.ReadUInt64LittleEndian(keyMdVersion),
10201021
keyPath: keyPath,
10211022
keyStoreName: providerName,
10221023
algorithmName: ds.GetString((int)DescribeParameterEncryptionResultSet1.KeyEncryptionAlgorithm));

src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.cs

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5288,6 +5288,10 @@ internal TdsOperationStatus TryProcessAltMetaData(int cColumns, TdsParserStateOb
52885288
/// <summary>
52895289
/// <para> Parses the TDS message to read single CIPHER_INFO entry.</para>
52905290
/// </summary>
5291+
/// <remarks>
5292+
/// The CIPHER_INFO structure is represented as an EK_INFO structure in the TDS protocol.
5293+
/// </remarks>
5294+
/// <seealso href="https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-tds/b0f2c914-c5ee-4714-802a-f118edf1b33d"/>.
52915295
internal TdsOperationStatus TryReadCipherInfoEntry(TdsParserStateObject stateObj, out SqlTceCipherInfoEntry entry)
52925296
{
52935297
byte cekValueCount = 0;
@@ -5318,8 +5322,7 @@ internal TdsOperationStatus TryReadCipherInfoEntry(TdsParserStateObject stateObj
53185322
}
53195323

53205324
// Read the key MD Version
5321-
byte[] keyMDVersion = new byte[8];
5322-
result = stateObj.TryReadByteArray(keyMDVersion, 8);
5325+
result = stateObj.TryReadInt64(out long keyMDVersion);
53235326
if (result != TdsOperationStatus.Done)
53245327
{
53255328
return result;
@@ -5414,7 +5417,7 @@ internal TdsOperationStatus TryReadCipherInfoEntry(TdsParserStateObject stateObj
54145417
databaseId: dbId,
54155418
cekId: keyId,
54165419
cekVersion: keyVersion,
5417-
cekMdVersion: keyMDVersion,
5420+
cekMdVersion: unchecked((ulong)keyMDVersion),
54185421
keyPath: keyPath,
54195422
keyStoreName: keyStoreName,
54205423
algorithmName: algorithmName);
@@ -5429,9 +5432,8 @@ internal TdsOperationStatus TryReadCipherInfoEntry(TdsParserStateObject stateObj
54295432
internal TdsOperationStatus TryProcessCipherInfoTable(TdsParserStateObject stateObj, out SqlTceCipherInfoTable cipherTable)
54305433
{
54315434
// Read count
5432-
short tableSize = 0;
54335435
cipherTable = null;
5434-
TdsOperationStatus result = stateObj.TryReadInt16(out tableSize);
5436+
TdsOperationStatus result = stateObj.TryReadUInt16(out ushort tableSize);
54355437
if (result != TdsOperationStatus.Done)
54365438
{
54375439
return result;
@@ -11445,8 +11447,7 @@ internal void WriteEncryptionEntries(ref SqlTceCipherInfoTable cekTable, TdsPars
1144511447
WriteInt(cekTable[i].CekVersion, stateObj);
1144611448

1144711449
// Write 8 bytes of key MD Version
11448-
Debug.Assert(8 == cekTable[i].CekMdVersion.Length);
11449-
stateObj.WriteByteArray(cekTable[i].CekMdVersion, 8, 0);
11450+
WriteUnsignedLong(cekTable[i].CekMdVersion, stateObj);
1145011451

1145111452
// We don't really need to send the keys
1145211453
stateObj.WriteByte(0x00);

src/Microsoft.Data.SqlClient/tests/FunctionalTests/AlwaysEncryptedTests/ExceptionsAlgorithmErrors.cs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ public void TestInvalidEncryptionType()
7272
{
7373
const byte invalidEncryptionType = 3;
7474
Object cipherMD = GetSqlCipherMetadata(0, 2, null, invalidEncryptionType, 0x01);
75-
AddEncryptionKeyToCipherMD(cipherMD, _encryptedCek, 0, 0, 0, new byte[] { 0x01, 0x02, 0x03 }, _certificatePath, "MSSQL_CERTIFICATE_STORE", "RSA_OAEP");
75+
AddEncryptionKeyToCipherMD(cipherMD, _encryptedCek, 0, 0, 0, 0x010203, _certificatePath, "MSSQL_CERTIFICATE_STORE", "RSA_OAEP");
7676
byte[] plainText = Encoding.Unicode.GetBytes("HelloWorld");
7777
byte[] cipherText = EncryptDataUsingAED(plainText, _cek, CColumnEncryptionType.Deterministic);
7878

@@ -135,7 +135,7 @@ public void TestNullColumnEncryptionAlgorithm()
135135
string expectedMessage = string.Format(SystemDataResourceManager.Instance.TCE_NullColumnEncryptionAlgorithm,
136136
"'AEAD_AES_256_CBC_HMAC_SHA256'");
137137
Object cipherMD = GetSqlCipherMetadata(0, 0, null, 1, 0x01);
138-
AddEncryptionKeyToCipherMD(cipherMD, _encryptedCek, 0, 0, 0, new byte[] { 0x01, 0x02, 0x03 }, _certificatePath, "MSSQL_CERTIFICATE_STORE", "RSA_OAEP");
138+
AddEncryptionKeyToCipherMD(cipherMD, _encryptedCek, 0, 0, 0, 0x010203, _certificatePath, "MSSQL_CERTIFICATE_STORE", "RSA_OAEP");
139139
byte[] plainText = Encoding.Unicode.GetBytes("HelloWorld");
140140
byte[] cipherText = EncryptDataUsingAED(plainText, _cek, CColumnEncryptionType.Deterministic);
141141

@@ -153,7 +153,7 @@ public void TestUnknownEncryptionAlgorithmId()
153153
string expectedMessage = string.Format(SystemDataResourceManager.Instance.TCE_UnknownColumnEncryptionAlgorithmId,
154154
unknownEncryptionAlgoId, "'1', '2'");
155155
Object cipherMD = GetSqlCipherMetadata(0, unknownEncryptionAlgoId, null, 1, 0x01);
156-
AddEncryptionKeyToCipherMD(cipherMD, _encryptedCek, 0, 0, 0, new byte[] { 0x01, 0x02, 0x03 }, _certificatePath, "MSSQL_CERTIFICATE_STORE", "RSA_OAEP");
156+
AddEncryptionKeyToCipherMD(cipherMD, _encryptedCek, 0, 0, 0, 0x010203, _certificatePath, "MSSQL_CERTIFICATE_STORE", "RSA_OAEP");
157157
byte[] plainText = Encoding.Unicode.GetBytes("HelloWorld");
158158
byte[] cipherText = EncryptDataUsingAED(plainText, _cek, CColumnEncryptionType.Deterministic);
159159

@@ -177,7 +177,7 @@ public void TestUnknownCustomKeyStoreProvider()
177177
string expectedMessage = string.Format(SystemDataResourceManager.Instance.TCE_UnrecognizedKeyStoreProviderName,
178178
invalidProviderName, "'MSSQL_CERTIFICATE_STORE', 'MSSQL_CNG_STORE', 'MSSQL_CSP_PROVIDER'", "");
179179
Object cipherMD = GetSqlCipherMetadata(0, 1, null, 1, 0x03);
180-
AddEncryptionKeyToCipherMD(cipherMD, _encryptedCek, 0, 0, 0, new byte[] { 0x01, 0x02, 0x03 }, _certificatePath, invalidProviderName, "RSA_OAEP");
180+
AddEncryptionKeyToCipherMD(cipherMD, _encryptedCek, 0, 0, 0, 0x010203, _certificatePath, invalidProviderName, "RSA_OAEP");
181181
byte[] plainText = Encoding.Unicode.GetBytes("HelloWorld");
182182
byte[] cipherText = EncryptDataUsingAED(plainText, _cek, CColumnEncryptionType.Deterministic);
183183

@@ -199,7 +199,7 @@ public void TestTceUnknownEncryptionAlgorithm()
199199
string expectedMessage = string.Format(SystemDataResourceManager.Instance.TCE_UnknownColumnEncryptionAlgorithm,
200200
unknownEncryptionAlgorithm, "'AEAD_AES_256_CBC_HMAC_SHA256'");
201201
Object cipherMD = GetSqlCipherMetadata(0, 0, "Dummy", 1, 0x01);
202-
AddEncryptionKeyToCipherMD(cipherMD, _encryptedCek, 0, 0, 0, new byte[] { 0x01, 0x02, 0x03 }, _certificatePath, "MSSQL_CERTIFICATE_STORE", "RSA_OAEP");
202+
AddEncryptionKeyToCipherMD(cipherMD, _encryptedCek, 0, 0, 0, 0x010203, _certificatePath, "MSSQL_CERTIFICATE_STORE", "RSA_OAEP");
203203
byte[] plainText = Encoding.Unicode.GetBytes("HelloWorld");
204204
byte[] cipherText = EncryptDataUsingAED(plainText, _cek, CColumnEncryptionType.Deterministic);
205205

@@ -220,7 +220,7 @@ public void TestExceptionsFromCertStore()
220220
"MSSQL_CERTIFICATE_STORE", BitConverter.ToString(corruptedCek, corruptedCek.Length - 10, 10));
221221

222222
Object cipherMD = GetSqlCipherMetadata(0, 1, null, 1, 0x01);
223-
AddEncryptionKeyToCipherMD(cipherMD, corruptedCek, 0, 0, 0, new byte[] { 0x01, 0x02, 0x03 }, _certificatePath, "MSSQL_CERTIFICATE_STORE", "RSA_OAEP");
223+
AddEncryptionKeyToCipherMD(cipherMD, corruptedCek, 0, 0, 0, 0x010203, _certificatePath, "MSSQL_CERTIFICATE_STORE", "RSA_OAEP");
224224
byte[] plainText = Encoding.Unicode.GetBytes("HelloWorld");
225225
byte[] cipherText = EncryptDataUsingAED(plainText, _cek, CColumnEncryptionType.Deterministic);
226226

@@ -244,7 +244,7 @@ public void TestExceptionsFromCustomKeyStore()
244244
SqlConnection.RegisterColumnEncryptionKeyStoreProviders(customProviders);
245245

246246
object cipherMD = GetSqlCipherMetadata(0, 1, null, 1, 0x01);
247-
AddEncryptionKeyToCipherMD(cipherMD, _encryptedCek, 0, 0, 0, new byte[] { 0x01, 0x02, 0x03 }, _certificatePath, "DummyProvider", "DummyAlgo");
247+
AddEncryptionKeyToCipherMD(cipherMD, _encryptedCek, 0, 0, 0, 0x010203, _certificatePath, "DummyProvider", "DummyAlgo");
248248
byte[] plainText = Encoding.Unicode.GetBytes("HelloWorld");
249249
byte[] cipherText = EncryptDataUsingAED(plainText, _cek, CColumnEncryptionType.Deterministic);
250250

src/Microsoft.Data.SqlClient/tests/FunctionalTests/AlwaysEncryptedTests/Utility.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,7 @@ internal static void AddEncryptionKeyToCipherMD(
217217
int databaseId,
218218
int cekId,
219219
int cekVersion,
220-
byte[] cekMdVersion,
220+
ulong cekMdVersion,
221221
string keyPath,
222222
string keyStoreName,
223223
string algorithmName)

0 commit comments

Comments
 (0)