Skip to content

Commit 37a9c99

Browse files
authored
Performance | Use lower-allocation AE primitives in SqlColumnEncryptionCertificateStoreProvider (#3660)
1 parent 6fc4792 commit 37a9c99

8 files changed

Lines changed: 232 additions & 529 deletions

File tree

src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/ColumnMasterKeyMetadata.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ namespace Microsoft.Data.SqlClient.AlwaysEncrypted;
3232
/// This takes ownership of the RSA instance supplied to it, disposing of it when Dispose is called.
3333
/// </para>
3434
/// </remarks>
35-
internal readonly ref struct ColumnMasterKeyMetadata // : IDisposable
35+
internal readonly ref struct ColumnMasterKeyMetadata : IDisposable
3636
{
3737
private static readonly HashAlgorithmName s_hashAlgorithm = HashAlgorithmName.SHA256;
3838

@@ -49,7 +49,6 @@ private struct Sha256Hash
4949
#endif
5050
private readonly RSA _rsa;
5151

52-
// @TODO: SqlColumnEncryptionCertificateStoreProvider.SignMasterKeyMetadata and .VerifyMasterKeyMetadata should use this type.
5352
/// <summary>
5453
/// Represents metadata associated with a column master key, including its cryptographic hash, path, provider name,
5554
/// and enclave computation settings.

src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncrypted/EncryptedColumnEncryptionKeyParameters.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ namespace Microsoft.Data.SqlClient.AlwaysEncrypted;
4141
/// This takes ownership of the RSA instance supplied to it, disposing of it when Dispose is called.
4242
/// </para>
4343
/// </remarks>
44-
internal readonly ref struct EncryptedColumnEncryptionKeyParameters // : IDisposable
44+
internal readonly ref struct EncryptedColumnEncryptionKeyParameters : IDisposable
4545
{
4646
private const byte AlgorithmVersion = 0x01;
4747

@@ -64,7 +64,6 @@ namespace Microsoft.Data.SqlClient.AlwaysEncrypted;
6464
private readonly string _keyType;
6565
private readonly string _keyPathReference;
6666

67-
// @TODO: SqlColumnEncryptionCertificateStoreProvider, SqlColumnEncryptionCngProvider and SqlColumnEncryptionCspProvider should use this type.
6867
/// <summary>
6968
/// Initializes a new instance of the <see cref="EncryptedColumnEncryptionKeyParameters"/> struct with the specified
7069
/// RSA key, key path, key type, and key path reference.

src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCertificateStoreProvider.cs

Lines changed: 184 additions & 420 deletions
Large diffs are not rendered by default.

src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlUtil.cs

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1714,31 +1714,16 @@ internal static Exception InvalidCiphertextLengthInEncryptedCEK(string keyType,
17141714
return ADP.Argument(StringsHelper.GetString(Strings.TCE_InvalidCiphertextLengthInEncryptedCEK, actual, expected, keyType, masterKeyPath, keyPathReference), TdsEnums.TCE_PARAM_ENCRYPTED_CEK);
17151715
}
17161716

1717-
internal static Exception InvalidCiphertextLengthInEncryptedCEKCertificate(int actual, int expected, string certificateName)
1718-
{
1719-
return ADP.Argument(StringsHelper.GetString(Strings.TCE_InvalidCiphertextLengthInEncryptedCEKCertificate, actual, expected, certificateName), TdsEnums.TCE_PARAM_ENCRYPTED_CEK);
1720-
}
1721-
17221717
internal static Exception InvalidSignatureInEncryptedCEK(string keyType, string keyPathReference, int actual, int expected, string masterKeyPath)
17231718
{
17241719
return ADP.Argument(StringsHelper.GetString(Strings.TCE_InvalidSignatureInEncryptedCEK, actual, expected, keyType, masterKeyPath, keyPathReference), TdsEnums.TCE_PARAM_ENCRYPTED_CEK);
17251720
}
17261721

1727-
internal static Exception InvalidSignatureInEncryptedCEKCertificate(int actual, int expected, string masterKeyPath)
1728-
{
1729-
return ADP.Argument(StringsHelper.GetString(Strings.TCE_InvalidSignatureInEncryptedCEKCertificate, actual, expected, masterKeyPath), TdsEnums.TCE_PARAM_ENCRYPTED_CEK);
1730-
}
1731-
17321722
internal static Exception InvalidSignature(string masterKeyPath, string keyType)
17331723
{
17341724
return ADP.Argument(StringsHelper.GetString(Strings.TCE_InvalidSignature, keyType, masterKeyPath), TdsEnums.TCE_PARAM_ENCRYPTED_CEK);
17351725
}
17361726

1737-
internal static Exception InvalidCertificateSignature(string certificatePath)
1738-
{
1739-
return ADP.Argument(StringsHelper.GetString(Strings.TCE_InvalidCertificateSignature, certificatePath), TdsEnums.TCE_PARAM_ENCRYPTED_CEK);
1740-
}
1741-
17421727
internal static Exception CertificateWithNoPrivateKey(string keyPath, bool isSystemOp)
17431728
{
17441729
if (isSystemOp)

src/Microsoft.Data.SqlClient/src/Resources/Strings.Designer.cs

Lines changed: 0 additions & 27 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Microsoft.Data.SqlClient/src/Resources/Strings.resx

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4077,21 +4077,12 @@
40774077
<data name="TCE_InvalidCiphertextLengthInEncryptedCEK" xml:space="preserve">
40784078
<value>The specified encrypted column encryption key's ciphertext length: {0} does not match the ciphertext length: {1} when using column master key ({2}) in '{3}'. The encrypted column encryption key may be corrupt, or the specified {4} path may be incorrect.</value>
40794079
</data>
4080-
<data name="TCE_InvalidCiphertextLengthInEncryptedCEKCertificate" xml:space="preserve">
4081-
<value>The specified encrypted column encryption key's ciphertext length: {0} does not match the ciphertext length: {1} when using column master key (certificate) in '{2}'. The encrypted column encryption key may be corrupt, or the specified certificate path may be incorrect.</value>
4082-
</data>
40834080
<data name="TCE_InvalidSignatureInEncryptedCEK" xml:space="preserve">
40844081
<value>The specified encrypted column encryption key's signature length: {0} does not match the signature length: {1} when using column master key ({2}) in '{3}'. The encrypted column encryption key may be corrupt, or the specified {4} path may be incorrect.</value>
40854082
</data>
4086-
<data name="TCE_InvalidSignatureInEncryptedCEKCertificate" xml:space="preserve">
4087-
<value>The specified encrypted column encryption key's signature length: {0} does not match the signature length: {1} when using column master key (certificate) in '{2}'. The encrypted column encryption key may be corrupt, or the specified certificate path may be incorrect.</value>
4088-
</data>
40894083
<data name="TCE_InvalidSignature" xml:space="preserve">
40904084
<value>The specified encrypted column encryption key signature does not match the signature computed with the column master key ({0}) in '{1}'. The encrypted column encryption key may be corrupt, or the specified path may be incorrect.</value>
40914085
</data>
4092-
<data name="TCE_InvalidCertificateSignature" xml:space="preserve">
4093-
<value>The specified encrypted column encryption key signature does not match the signature computed with the column master key (certificate) in '{0}'. The encrypted column encryption key may be corrupt, or the specified path may be incorrect.</value>
4094-
</data>
40954086
<data name="TCE_CertificateWithNoPrivateKey" xml:space="preserve">
40964087
<value>Certificate specified in key path '{0}' does not have a private key to encrypt a column encryption key. Verify the certificate is imported correctly.</value>
40974088
</data>

src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/CoreCryptoTests.cs

Lines changed: 46 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
using Microsoft.Data.SqlClient.ManualTesting.Tests.AlwaysEncrypted.Setup;
2-
using System;
1+
using System;
32
using System.Collections.Generic;
3+
using System.Diagnostics;
44
using System.Linq;
5+
using System.Security.Cryptography.X509Certificates;
56
using System.Text;
67
using Microsoft.Data.SqlClient;
7-
using System.Diagnostics;
8+
using Microsoft.Data.SqlClient.ManualTesting.Tests.AlwaysEncrypted.Setup;
89
using Xunit;
910

1011
namespace Microsoft.Data.SqlClient.ManualTesting.Tests.AlwaysEncrypted
@@ -47,6 +48,8 @@ public void TestAeadCryptoWithNativeBaseline()
4748
[Fact]
4849
public void TestRsaCryptoWithNativeBaseline()
4950
{
51+
SqlColumnEncryptionCertificateStoreProvider rsaProvider = new();
52+
5053
// Initialize the reader for resource text file which has the native code generated baseline.
5154
CryptoNativeBaselineReader cryptoNativeBaselineReader = new CryptoNativeBaselineReader();
5255

@@ -62,23 +65,50 @@ public void TestRsaCryptoWithNativeBaseline()
6265
byte[] rsaKeyPair = cryptoParametersListForTest[0].RsaKeyPair;
6366
byte[] rsaPfx = cryptoParametersListForTest[1].RsaKeyPair;
6467

65-
// For each crypto vector, run the test to compare the output generated through sqlclient's code and the native code.
66-
foreach (CryptoVector cryptoParameter in cryptoParametersListForTest)
68+
// Convert the PFX into a certificate and install it into the local user's certificate store.
69+
// We can only do this cross-platform on the CurrentUser store, which matches the baseline data we have.
70+
Assert.NotNull(rsaPfx);
71+
Assert.NotEmpty(rsaPfx);
72+
73+
X509Store store = null;
74+
bool addedToStore = false;
75+
#if NET9_0_OR_GREATER
76+
using X509Certificate2 x509 = X509CertificateLoader.LoadPkcs12(rsaPfx, @"P@zzw0rD!SqlvN3x+");
77+
#else
78+
using X509Certificate2 x509 = new(rsaPfx, @"P@zzw0rD!SqlvN3x+");
79+
#endif
80+
Assert.True(x509.HasPrivateKey);
81+
82+
try
6783
{
68-
if (cryptoParameter.CryptNativeTestVectorTypeVal == CryptNativeTestVectorType.Rsa)
69-
{
70-
// Verify that we are using the right padding scheme for RSA encryption
71-
byte[] plaintext = CertificateUtility.DecryptRsaDirectly(rsaPfx, cryptoParameter.CiphertextCek, @"Test");
72-
Assert.True(cryptoParameter.PlaintextCek.SequenceEqual(plaintext), "Plaintext CEK Value does not match with the native code baseline.");
84+
store = new(StoreName.My, StoreLocation.CurrentUser);
85+
store.Open(OpenFlags.ReadWrite);
7386

74-
// Verify that the signed blob is conforming to our envelope (SHA-256, PKCS 1 padding)
75-
bool signatureVerified = CertificateUtility.VerifyRsaSignatureDirectly(cryptoParameter.HashedCek, cryptoParameter.SignedCek, rsaPfx);
76-
Assert.True(signatureVerified, "Plaintext CEK signature scheme does not match with the native code baseline.");
87+
store.Add(x509);
88+
addedToStore = true;
7789

78-
//// TODO: Programmatically install the in-memory PFX into the right store (based on path) & use the public API
79-
//plaintext = Utility.VerifyRsaSignature(cryptoParameter.PathCek, cryptoParameter.FinalcellCek, rsaPfx);
80-
//CError.Compare(cryptoParameter.PlaintextCek.SequenceEqual(plaintext), "Plaintext CEK Value does not match with the native code baseline (end to end).");
90+
// For each crypto vector, run the test to compare the output generated through sqlclient's code and the native code.
91+
foreach (CryptoVector cryptoParameter in cryptoParametersListForTest)
92+
{
93+
if (cryptoParameter.CryptNativeTestVectorTypeVal == CryptNativeTestVectorType.Rsa)
94+
{
95+
Assert.NotNull(cryptoParameter.PathCek);
96+
Assert.StartsWith("CurrentUser/My", cryptoParameter.PathCek);
97+
98+
// Decrypt the supplied final cell CEK, and ensure that the plaintext CEK value matches the native code baseline.
99+
byte[] plaintext = rsaProvider.DecryptColumnEncryptionKey(cryptoParameter.PathCek, "RSA_OAEP", cryptoParameter.FinalcellCek);
100+
Assert.Equal(cryptoParameter.PlaintextCek, plaintext);
101+
}
102+
}
103+
}
104+
finally
105+
{
106+
if (addedToStore)
107+
{
108+
store.Remove(x509);
81109
}
110+
111+
store?.Dispose();
82112
}
83113
}
84114

src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/Setup/CertificateUtility.cs

Lines changed: 0 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,6 @@ private CertificateUtility()
3333
public static MethodInfo sqlAeadAes256CbcHmac256FactoryCreate = sqlAeadAes256CbcHmac256Factory.GetMethod("Create", BindingFlags.Instance | BindingFlags.NonPublic);
3434
public static Type sqlClientEncryptionAlgorithm = systemData.GetType("Microsoft.Data.SqlClient.SqlClientEncryptionAlgorithm");
3535
public static MethodInfo sqlClientEncryptionAlgorithmEncryptData = sqlClientEncryptionAlgorithm.GetMethod("EncryptData", BindingFlags.Instance | BindingFlags.NonPublic);
36-
public static Type SqlColumnEncryptionCertificateStoreProvider = systemData.GetType("Microsoft.Data.SqlClient.SqlColumnEncryptionCertificateStoreProvider");
37-
public static MethodInfo SqlColumnEncryptionCertificateStoreProviderRSADecrypt = SqlColumnEncryptionCertificateStoreProvider.GetMethod("RSADecrypt", BindingFlags.Instance | BindingFlags.NonPublic);
38-
public static MethodInfo SqlColumnEncryptionCertificateStoreProviderRSAVerifySignature = SqlColumnEncryptionCertificateStoreProvider.GetMethod("RSAVerifySignature", BindingFlags.Instance | BindingFlags.NonPublic);
3936
public static MethodInfo sqlClientEncryptionAlgorithmDecryptData = sqlClientEncryptionAlgorithm.GetMethod("DecryptData", BindingFlags.Instance | BindingFlags.NonPublic);
4037
public static Type SqlSymmetricKeyCache = systemData.GetType("Microsoft.Data.SqlClient.SqlSymmetricKeyCache");
4138
public static MethodInfo SqlSymmetricKeyCacheGetInstance = SqlSymmetricKeyCache.GetMethod("GetInstance", BindingFlags.Static | BindingFlags.NonPublic);
@@ -102,41 +99,6 @@ internal static void CleanSqlClientCache()
10299
ClearCache(cache);
103100
}
104101

105-
internal static byte[] DecryptRsaDirectly(byte[] rsaPfx, byte[] ciphertextCek, string masterKeyPath)
106-
{
107-
Debug.Assert(rsaPfx != null && rsaPfx.Length > 0);
108-
// The rest of the parameters may be invalid for exception handling test cases
109-
110-
#if NET9_0_OR_GREATER
111-
X509Certificate2 x509 = X509CertificateLoader.LoadPkcs12(rsaPfx, @"P@zzw0rD!SqlvN3x+");
112-
#else
113-
X509Certificate2 x509 = new(rsaPfx, @"P@zzw0rD!SqlvN3x+");
114-
#endif
115-
Debug.Assert(x509.HasPrivateKey);
116-
117-
SqlColumnEncryptionCertificateStoreProvider rsaProvider = new SqlColumnEncryptionCertificateStoreProvider();
118-
Object RsaDecryptionResult = SqlColumnEncryptionCertificateStoreProviderRSADecrypt.Invoke(rsaProvider, new object[] { ciphertextCek, x509 });
119-
120-
return (byte[])RsaDecryptionResult;
121-
}
122-
123-
internal static bool VerifyRsaSignatureDirectly(byte[] hashedCek, byte[] signedCek, byte[] rsaPfx)
124-
{
125-
Debug.Assert(rsaPfx != null && rsaPfx.Length > 0);
126-
127-
#if NET9_0_OR_GREATER
128-
X509Certificate2 x509 = X509CertificateLoader.LoadPkcs12(rsaPfx, @"P@zzw0rD!SqlvN3x+");
129-
#else
130-
X509Certificate2 x509 = new(rsaPfx, @"P@zzw0rD!SqlvN3x+");
131-
#endif
132-
Debug.Assert(x509.HasPrivateKey);
133-
134-
SqlColumnEncryptionCertificateStoreProvider rsaProvider = new SqlColumnEncryptionCertificateStoreProvider();
135-
Object RsaVerifySignatureResult = SqlColumnEncryptionCertificateStoreProviderRSAVerifySignature.Invoke(rsaProvider, new object[] { hashedCek, signedCek, x509 });
136-
137-
return (bool)RsaVerifySignatureResult;
138-
}
139-
140102
/// <summary>
141103
/// Decrypt Data using AEAD
142104
/// </summary>

0 commit comments

Comments
 (0)