Skip to content

Commit

Permalink
Implement AES-GCM with CryptoKit on macOS
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
vcsjones authored Nov 8, 2022
1 parent 1f7e169 commit e029499
Show file tree
Hide file tree
Showing 12 changed files with 383 additions and 40 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,82 @@ internal static unsafe void ChaCha20Poly1305Decrypt(
}
}

internal static unsafe void AesGcmEncrypt(
ReadOnlySpan<byte> key,
ReadOnlySpan<byte> nonce,
ReadOnlySpan<byte> plaintext,
Span<byte> ciphertext,
Span<byte> tag,
ReadOnlySpan<byte> aad)
{
fixed (byte* keyPtr = key)
fixed (byte* noncePtr = nonce)
fixed (byte* plaintextPtr = plaintext)
fixed (byte* ciphertextPtr = ciphertext)
fixed (byte* tagPtr = tag)
fixed (byte* aadPtr = aad)
{
const int Success = 1;
int result = AppleCryptoNative_AesGcmEncrypt(
keyPtr, key.Length,
noncePtr, nonce.Length,
plaintextPtr, plaintext.Length,
ciphertextPtr, ciphertext.Length,
tagPtr, tag.Length,
aadPtr, aad.Length);

if (result != Success)
{
Debug.Assert(result == 0);
CryptographicOperations.ZeroMemory(ciphertext);
CryptographicOperations.ZeroMemory(tag);
throw new CryptographicException();
}
}
}

internal static unsafe void AesGcmDecrypt(
ReadOnlySpan<byte> key,
ReadOnlySpan<byte> nonce,
ReadOnlySpan<byte> ciphertext,
ReadOnlySpan<byte> tag,
Span<byte> plaintext,
ReadOnlySpan<byte> aad)
{
fixed (byte* keyPtr = key)
fixed (byte* noncePtr = nonce)
fixed (byte* ciphertextPtr = ciphertext)
fixed (byte* tagPtr = tag)
fixed (byte* plaintextPtr = plaintext)
fixed (byte* aadPtr = aad)
{
const int Success = 1;
const int AuthTagMismatch = -1;
int result = AppleCryptoNative_AesGcmDecrypt(
keyPtr, key.Length,
noncePtr, nonce.Length,
ciphertextPtr, ciphertext.Length,
tagPtr, tag.Length,
plaintextPtr, plaintext.Length,
aadPtr, aad.Length);

if (result != Success)
{
CryptographicOperations.ZeroMemory(plaintext);

if (result == AuthTagMismatch)
{
throw new AuthenticationTagMismatchException();
}
else
{
Debug.Assert(result == 0);
throw new CryptographicException();
}
}
}
}

[LibraryImport(Libraries.AppleCryptoNative)]
private static unsafe partial int AppleCryptoNative_ChaCha20Poly1305Encrypt(
byte* keyPtr,
Expand Down Expand Up @@ -116,5 +192,35 @@ private static unsafe partial int AppleCryptoNative_ChaCha20Poly1305Decrypt(
int plaintextLength,
byte* aadPtr,
int aadLength);

[LibraryImport(Libraries.AppleCryptoNative)]
private static unsafe partial int AppleCryptoNative_AesGcmEncrypt(
byte* keyPtr,
int keyLength,
byte* noncePtr,
int nonceLength,
byte* plaintextPtr,
int plaintextLength,
byte* ciphertextPtr,
int ciphertextLength,
byte* tagPtr,
int tagLength,
byte* aadPtr,
int aadLength);

[LibraryImport(Libraries.AppleCryptoNative)]
private static unsafe partial int AppleCryptoNative_AesGcmDecrypt(
byte* keyPtr,
int keyLength,
byte* noncePtr,
int nonceLength,
byte* ciphertextPtr,
int ciphertextLength,
byte* tagPtr,
int tagLength,
byte* plaintextPtr,
int plaintextLength,
byte* aadPtr,
int aadLength);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -783,6 +783,7 @@
Link="Common\System\Text\UrlBase64Encoding.cs" />
<Compile Include="$(CommonPath)System\Text\ValueUtf8Converter.cs"
Link="Common\System\Text\ValueUtf8Converter.cs" />
<Compile Include="System\Security\Cryptography\AesGcm.OpenSsl.cs" />
<Compile Include="System\Security\Cryptography\AesImplementation.OpenSsl.cs" />
<Compile Include="System\Security\Cryptography\AsnFormatter.OpenSsl.cs" />
<Compile Include="System\Security\Cryptography\CapiHelper.DSA.Shared.cs" />
Expand Down Expand Up @@ -870,7 +871,6 @@
<Compile Include="$(CommonPath)Microsoft\Win32\SafeHandles\SafeEvpCipherCtxHandle.Unix.cs"
Link="Common\Microsoft\Win32\SafeHandles\SafeEvpCipherCtxHandle.Unix.cs" />
<Compile Include="System\Security\Cryptography\AesCcm.OpenSsl.cs" />
<Compile Include="System\Security\Cryptography\AesGcm.OpenSsl.cs" />
</ItemGroup>
<ItemGroup Condition="'$(UseAndroidCrypto)' == 'true'">
<Compile Include="$(CommonPath)Interop\Android\Interop.JObjectLifetime.cs"
Expand Down Expand Up @@ -1292,6 +1292,7 @@
Link="Common\System\Security\Cryptography\RSASecurityTransforms.macOS.cs" />
<Compile Include="$(CommonPath)System\Security\Cryptography\RSAOpenSsl.cs"
Link="Common\System\Security\Cryptography\RSAOpenSsl.cs" />
<Compile Include="System\Security\Cryptography\AesGcm.macOS.cs" />
<Compile Include="System\Security\Cryptography\ChaCha20Poly1305.macOS.cs" />
<Compile Include="System\Security\Cryptography\DSA.Create.SecurityTransforms.cs" />
<Compile Include="System\Security\Cryptography\DSACryptoServiceProvider.Unix.cs" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ public sealed partial class AesGcm
private SafeEvpCipherCtxHandle _ctxHandle;

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

[MemberNotNull(nameof(_ctxHandle))]
private void ImportKey(ReadOnlySpan<byte> key)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ namespace System.Security.Cryptography
public partial class AesGcm
{
public static bool IsSupported => false;
public static KeySizes TagByteSizes { get; } = new KeySizes(12, 16, 1);

#pragma warning disable CA1822, IDE0060
private void ImportKey(ReadOnlySpan<byte> key)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ public sealed partial class AesGcm
private SafeEvpCipherCtxHandle _ctxHandle;

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

[MemberNotNull(nameof(_ctxHandle))]
private void ImportKey(ReadOnlySpan<byte> key)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ public partial class AesGcm
private SafeKeyHandle _keyHandle;

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

[MemberNotNull(nameof(_keyHandle))]
private void ImportKey(ReadOnlySpan<byte> key)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ public sealed partial class AesGcm : IDisposable
{
private const int NonceSize = 12;
public static KeySizes NonceByteSizes { get; } = new KeySizes(NonceSize, NonceSize, 1);
public static KeySizes TagByteSizes { get; } = new KeySizes(12, 16, 1);

public AesGcm(ReadOnlySpan<byte> key)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using Microsoft.Win32.SafeHandles;

namespace System.Security.Cryptography
{
public sealed partial class AesGcm
{
private byte[]? _key;

// CryptoKit added AES.GCM in macOS 10.15, which is our minimum target for macOS.
public static bool IsSupported => true;

// CryptoKit only supports 16 byte tags.
public static KeySizes TagByteSizes { get; } = new KeySizes(16, 16, 1);

[MemberNotNull(nameof(_key))]
private void ImportKey(ReadOnlySpan<byte> key)
{
// We should only be calling this in the constructor, so there shouldn't be a previous key.
Debug.Assert(_key is null);

// Pin the array on the POH so that the GC doesn't move it around to allow zeroing to be more effective.
_key = GC.AllocateArray<byte>(key.Length, pinned: true);
key.CopyTo(_key);
}

private void EncryptCore(
ReadOnlySpan<byte> nonce,
ReadOnlySpan<byte> plaintext,
Span<byte> ciphertext,
Span<byte> tag,
ReadOnlySpan<byte> associatedData)
{
CheckDisposed();
Interop.AppleCrypto.AesGcmEncrypt(
_key,
nonce,
plaintext,
ciphertext,
tag,
associatedData);
}

private void DecryptCore(
ReadOnlySpan<byte> nonce,
ReadOnlySpan<byte> ciphertext,
ReadOnlySpan<byte> tag,
Span<byte> plaintext,
ReadOnlySpan<byte> associatedData)
{
CheckDisposed();
Interop.AppleCrypto.AesGcmDecrypt(
_key,
nonce,
ciphertext,
tag,
plaintext,
associatedData);
}

public void Dispose()
{
CryptographicOperations.ZeroMemory(_key);
_key = null;
}

[MemberNotNull(nameof(_key))]
private void CheckDisposed()
{
ObjectDisposedException.ThrowIf(_key is null, this);
}
}
}
Loading

0 comments on commit e029499

Please sign in to comment.