Skip to content

Commit e029499

Browse files
authored
Implement AES-GCM with CryptoKit on macOS
With this change, GCM on macOS no longer uses OpenSSL, but routes into the CryptoKit library. This means that tags 12-15 bytes long are no longer supported on macOS.
1 parent 1f7e169 commit e029499

File tree

12 files changed

+383
-40
lines changed

12 files changed

+383
-40
lines changed

src/libraries/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.Aead.cs

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,82 @@ internal static unsafe void ChaCha20Poly1305Decrypt(
8787
}
8888
}
8989

90+
internal static unsafe void AesGcmEncrypt(
91+
ReadOnlySpan<byte> key,
92+
ReadOnlySpan<byte> nonce,
93+
ReadOnlySpan<byte> plaintext,
94+
Span<byte> ciphertext,
95+
Span<byte> tag,
96+
ReadOnlySpan<byte> aad)
97+
{
98+
fixed (byte* keyPtr = key)
99+
fixed (byte* noncePtr = nonce)
100+
fixed (byte* plaintextPtr = plaintext)
101+
fixed (byte* ciphertextPtr = ciphertext)
102+
fixed (byte* tagPtr = tag)
103+
fixed (byte* aadPtr = aad)
104+
{
105+
const int Success = 1;
106+
int result = AppleCryptoNative_AesGcmEncrypt(
107+
keyPtr, key.Length,
108+
noncePtr, nonce.Length,
109+
plaintextPtr, plaintext.Length,
110+
ciphertextPtr, ciphertext.Length,
111+
tagPtr, tag.Length,
112+
aadPtr, aad.Length);
113+
114+
if (result != Success)
115+
{
116+
Debug.Assert(result == 0);
117+
CryptographicOperations.ZeroMemory(ciphertext);
118+
CryptographicOperations.ZeroMemory(tag);
119+
throw new CryptographicException();
120+
}
121+
}
122+
}
123+
124+
internal static unsafe void AesGcmDecrypt(
125+
ReadOnlySpan<byte> key,
126+
ReadOnlySpan<byte> nonce,
127+
ReadOnlySpan<byte> ciphertext,
128+
ReadOnlySpan<byte> tag,
129+
Span<byte> plaintext,
130+
ReadOnlySpan<byte> aad)
131+
{
132+
fixed (byte* keyPtr = key)
133+
fixed (byte* noncePtr = nonce)
134+
fixed (byte* ciphertextPtr = ciphertext)
135+
fixed (byte* tagPtr = tag)
136+
fixed (byte* plaintextPtr = plaintext)
137+
fixed (byte* aadPtr = aad)
138+
{
139+
const int Success = 1;
140+
const int AuthTagMismatch = -1;
141+
int result = AppleCryptoNative_AesGcmDecrypt(
142+
keyPtr, key.Length,
143+
noncePtr, nonce.Length,
144+
ciphertextPtr, ciphertext.Length,
145+
tagPtr, tag.Length,
146+
plaintextPtr, plaintext.Length,
147+
aadPtr, aad.Length);
148+
149+
if (result != Success)
150+
{
151+
CryptographicOperations.ZeroMemory(plaintext);
152+
153+
if (result == AuthTagMismatch)
154+
{
155+
throw new AuthenticationTagMismatchException();
156+
}
157+
else
158+
{
159+
Debug.Assert(result == 0);
160+
throw new CryptographicException();
161+
}
162+
}
163+
}
164+
}
165+
90166
[LibraryImport(Libraries.AppleCryptoNative)]
91167
private static unsafe partial int AppleCryptoNative_ChaCha20Poly1305Encrypt(
92168
byte* keyPtr,
@@ -116,5 +192,35 @@ private static unsafe partial int AppleCryptoNative_ChaCha20Poly1305Decrypt(
116192
int plaintextLength,
117193
byte* aadPtr,
118194
int aadLength);
195+
196+
[LibraryImport(Libraries.AppleCryptoNative)]
197+
private static unsafe partial int AppleCryptoNative_AesGcmEncrypt(
198+
byte* keyPtr,
199+
int keyLength,
200+
byte* noncePtr,
201+
int nonceLength,
202+
byte* plaintextPtr,
203+
int plaintextLength,
204+
byte* ciphertextPtr,
205+
int ciphertextLength,
206+
byte* tagPtr,
207+
int tagLength,
208+
byte* aadPtr,
209+
int aadLength);
210+
211+
[LibraryImport(Libraries.AppleCryptoNative)]
212+
private static unsafe partial int AppleCryptoNative_AesGcmDecrypt(
213+
byte* keyPtr,
214+
int keyLength,
215+
byte* noncePtr,
216+
int nonceLength,
217+
byte* ciphertextPtr,
218+
int ciphertextLength,
219+
byte* tagPtr,
220+
int tagLength,
221+
byte* plaintextPtr,
222+
int plaintextLength,
223+
byte* aadPtr,
224+
int aadLength);
119225
}
120226
}

src/libraries/System.Security.Cryptography/src/System.Security.Cryptography.csproj

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -783,6 +783,7 @@
783783
Link="Common\System\Text\UrlBase64Encoding.cs" />
784784
<Compile Include="$(CommonPath)System\Text\ValueUtf8Converter.cs"
785785
Link="Common\System\Text\ValueUtf8Converter.cs" />
786+
<Compile Include="System\Security\Cryptography\AesGcm.OpenSsl.cs" />
786787
<Compile Include="System\Security\Cryptography\AesImplementation.OpenSsl.cs" />
787788
<Compile Include="System\Security\Cryptography\AsnFormatter.OpenSsl.cs" />
788789
<Compile Include="System\Security\Cryptography\CapiHelper.DSA.Shared.cs" />
@@ -870,7 +871,6 @@
870871
<Compile Include="$(CommonPath)Microsoft\Win32\SafeHandles\SafeEvpCipherCtxHandle.Unix.cs"
871872
Link="Common\Microsoft\Win32\SafeHandles\SafeEvpCipherCtxHandle.Unix.cs" />
872873
<Compile Include="System\Security\Cryptography\AesCcm.OpenSsl.cs" />
873-
<Compile Include="System\Security\Cryptography\AesGcm.OpenSsl.cs" />
874874
</ItemGroup>
875875
<ItemGroup Condition="'$(UseAndroidCrypto)' == 'true'">
876876
<Compile Include="$(CommonPath)Interop\Android\Interop.JObjectLifetime.cs"
@@ -1292,6 +1292,7 @@
12921292
Link="Common\System\Security\Cryptography\RSASecurityTransforms.macOS.cs" />
12931293
<Compile Include="$(CommonPath)System\Security\Cryptography\RSAOpenSsl.cs"
12941294
Link="Common\System\Security\Cryptography\RSAOpenSsl.cs" />
1295+
<Compile Include="System\Security\Cryptography\AesGcm.macOS.cs" />
12951296
<Compile Include="System\Security\Cryptography\ChaCha20Poly1305.macOS.cs" />
12961297
<Compile Include="System\Security\Cryptography\DSA.Create.SecurityTransforms.cs" />
12971298
<Compile Include="System\Security\Cryptography\DSACryptoServiceProvider.Unix.cs" />

src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesGcm.Android.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ public sealed partial class AesGcm
1212
private SafeEvpCipherCtxHandle _ctxHandle;
1313

1414
public static bool IsSupported => true;
15+
public static KeySizes TagByteSizes { get; } = new KeySizes(12, 16, 1);
1516

1617
[MemberNotNull(nameof(_ctxHandle))]
1718
private void ImportKey(ReadOnlySpan<byte> key)

src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesGcm.NotSupported.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ namespace System.Security.Cryptography
88
public partial class AesGcm
99
{
1010
public static bool IsSupported => false;
11+
public static KeySizes TagByteSizes { get; } = new KeySizes(12, 16, 1);
1112

1213
#pragma warning disable CA1822, IDE0060
1314
private void ImportKey(ReadOnlySpan<byte> key)

src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesGcm.OpenSsl.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ public sealed partial class AesGcm
1212
private SafeEvpCipherCtxHandle _ctxHandle;
1313

1414
public static bool IsSupported { get; } = Interop.OpenSslNoInit.OpenSslIsAvailable;
15+
public static KeySizes TagByteSizes { get; } = new KeySizes(12, 16, 1);
1516

1617
[MemberNotNull(nameof(_ctxHandle))]
1718
private void ImportKey(ReadOnlySpan<byte> key)

src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesGcm.Windows.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ public partial class AesGcm
1212
private SafeKeyHandle _keyHandle;
1313

1414
public static bool IsSupported => true;
15+
public static KeySizes TagByteSizes { get; } = new KeySizes(12, 16, 1);
1516

1617
[MemberNotNull(nameof(_keyHandle))]
1718
private void ImportKey(ReadOnlySpan<byte> key)

src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesGcm.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ public sealed partial class AesGcm : IDisposable
1313
{
1414
private const int NonceSize = 12;
1515
public static KeySizes NonceByteSizes { get; } = new KeySizes(NonceSize, NonceSize, 1);
16-
public static KeySizes TagByteSizes { get; } = new KeySizes(12, 16, 1);
1716

1817
public AesGcm(ReadOnlySpan<byte> key)
1918
{
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Diagnostics;
5+
using System.Diagnostics.CodeAnalysis;
6+
using Microsoft.Win32.SafeHandles;
7+
8+
namespace System.Security.Cryptography
9+
{
10+
public sealed partial class AesGcm
11+
{
12+
private byte[]? _key;
13+
14+
// CryptoKit added AES.GCM in macOS 10.15, which is our minimum target for macOS.
15+
public static bool IsSupported => true;
16+
17+
// CryptoKit only supports 16 byte tags.
18+
public static KeySizes TagByteSizes { get; } = new KeySizes(16, 16, 1);
19+
20+
[MemberNotNull(nameof(_key))]
21+
private void ImportKey(ReadOnlySpan<byte> key)
22+
{
23+
// We should only be calling this in the constructor, so there shouldn't be a previous key.
24+
Debug.Assert(_key is null);
25+
26+
// Pin the array on the POH so that the GC doesn't move it around to allow zeroing to be more effective.
27+
_key = GC.AllocateArray<byte>(key.Length, pinned: true);
28+
key.CopyTo(_key);
29+
}
30+
31+
private void EncryptCore(
32+
ReadOnlySpan<byte> nonce,
33+
ReadOnlySpan<byte> plaintext,
34+
Span<byte> ciphertext,
35+
Span<byte> tag,
36+
ReadOnlySpan<byte> associatedData)
37+
{
38+
CheckDisposed();
39+
Interop.AppleCrypto.AesGcmEncrypt(
40+
_key,
41+
nonce,
42+
plaintext,
43+
ciphertext,
44+
tag,
45+
associatedData);
46+
}
47+
48+
private void DecryptCore(
49+
ReadOnlySpan<byte> nonce,
50+
ReadOnlySpan<byte> ciphertext,
51+
ReadOnlySpan<byte> tag,
52+
Span<byte> plaintext,
53+
ReadOnlySpan<byte> associatedData)
54+
{
55+
CheckDisposed();
56+
Interop.AppleCrypto.AesGcmDecrypt(
57+
_key,
58+
nonce,
59+
ciphertext,
60+
tag,
61+
plaintext,
62+
associatedData);
63+
}
64+
65+
public void Dispose()
66+
{
67+
CryptographicOperations.ZeroMemory(_key);
68+
_key = null;
69+
}
70+
71+
[MemberNotNull(nameof(_key))]
72+
private void CheckDisposed()
73+
{
74+
ObjectDisposedException.ThrowIf(_key is null, this);
75+
}
76+
}
77+
}

0 commit comments

Comments
 (0)