diff --git a/eng/native/configurecompiler.cmake b/eng/native/configurecompiler.cmake index da893f7da616ba..93d0f9c5ad54d3 100644 --- a/eng/native/configurecompiler.cmake +++ b/eng/native/configurecompiler.cmake @@ -509,7 +509,7 @@ if (CLR_CMAKE_HOST_UNIX) set(CMAKE_OSX_DEPLOYMENT_TARGET "11.0") add_compile_options(-arch arm64) elseif(CLR_CMAKE_HOST_ARCH_AMD64) - set(CMAKE_OSX_DEPLOYMENT_TARGET "10.14") + set(CMAKE_OSX_DEPLOYMENT_TARGET "10.15") add_compile_options(-arch x86_64) else() clr_unknown_arch() diff --git a/src/libraries/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.Aead.cs b/src/libraries/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.Aead.cs new file mode 100644 index 00000000000000..62102ac0a5939f --- /dev/null +++ b/src/libraries/Common/src/Interop/OSX/System.Security.Cryptography.Native.Apple/Interop.Aead.cs @@ -0,0 +1,120 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Security.Cryptography; +using System.Security.Cryptography.Apple; + +internal static partial class Interop +{ + internal static partial class AppleCrypto + { + internal static unsafe void ChaCha20Poly1305Encrypt( + ReadOnlySpan key, + ReadOnlySpan nonce, + ReadOnlySpan plaintext, + Span ciphertext, + Span tag, + ReadOnlySpan 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_ChaCha20Poly1305Encrypt( + 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 ChaCha20Poly1305Decrypt( + ReadOnlySpan key, + ReadOnlySpan nonce, + ReadOnlySpan ciphertext, + ReadOnlySpan tag, + Span plaintext, + ReadOnlySpan 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_ChaCha20Poly1305Decrypt( + 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, + 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_ChaCha20Poly1305Decrypt( + byte* keyPtr, + int keyLength, + byte* noncePtr, + int nonceLength, + byte* ciphertextPtr, + int ciphertextLength, + byte* tagPtr, + int tagLength, + byte* plaintextPtr, + int plaintextLength, + byte* aadPtr, + int aadLength); + } +} diff --git a/src/libraries/System.Security.Cryptography/src/System.Security.Cryptography.csproj b/src/libraries/System.Security.Cryptography/src/System.Security.Cryptography.csproj index ed68d33a835db9..f390480bce452c 100644 --- a/src/libraries/System.Security.Cryptography/src/System.Security.Cryptography.csproj +++ b/src/libraries/System.Security.Cryptography/src/System.Security.Cryptography.csproj @@ -788,6 +788,7 @@ + @@ -868,7 +869,6 @@ Link="Common\Interop\Unix\System.Security.Cryptography.Native\Interop.EVP.Cipher.cs" /> - @@ -1257,6 +1257,8 @@ Link="Common\Interop\Unix\System.Security.Cryptography.Native\Interop.EvpPkey.Rsa.cs" /> + + diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/ChaCha20Poly1305.macOS.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/ChaCha20Poly1305.macOS.cs new file mode 100644 index 00000000000000..82f1633c1882e1 --- /dev/null +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/ChaCha20Poly1305.macOS.cs @@ -0,0 +1,72 @@ +// 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; + +namespace System.Security.Cryptography +{ + public sealed partial class ChaCha20Poly1305 + { + // CryptoKit added ChaCha20Poly1305 in macOS 10.15, which is our minimum target for macOS. + public static bool IsSupported => true; + private byte[]? _key; + + [MemberNotNull(nameof(_key))] + private void ImportKey(ReadOnlySpan 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(key.Length, pinned: true); + key.CopyTo(_key); + } + + private void EncryptCore( + ReadOnlySpan nonce, + ReadOnlySpan plaintext, + Span ciphertext, + Span tag, + ReadOnlySpan associatedData = default) + { + CheckDisposed(); + Interop.AppleCrypto.ChaCha20Poly1305Encrypt( + _key, + nonce, + plaintext, + ciphertext, + tag, + associatedData); + } + + private void DecryptCore( + ReadOnlySpan nonce, + ReadOnlySpan ciphertext, + ReadOnlySpan tag, + Span plaintext, + ReadOnlySpan associatedData = default) + { + CheckDisposed(); + Interop.AppleCrypto.ChaCha20Poly1305Decrypt( + _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); + } + } +} diff --git a/src/libraries/System.Security.Cryptography/tests/ChaCha20Poly1305Tests.cs b/src/libraries/System.Security.Cryptography/tests/ChaCha20Poly1305Tests.cs index f381e17e73d190..40f17690cdffcc 100644 --- a/src/libraries/System.Security.Cryptography/tests/ChaCha20Poly1305Tests.cs +++ b/src/libraries/System.Security.Cryptography/tests/ChaCha20Poly1305Tests.cs @@ -457,8 +457,12 @@ public static void CheckIsSupported() // OpenSSL is present, and a high enough version, // but the distro build options turned off ChaCha/Poly. } - else if (PlatformDetection.OpenSslPresentOnSystem && - (PlatformDetection.IsOSX || PlatformDetection.IsOpenSslSupported)) + else if (PlatformDetection.IsOSX) + { + // CryptoKit is supported on macOS 10.15+, which is our minimum target. + expectedIsSupported = true; + } + else if (PlatformDetection.OpenSslPresentOnSystem && PlatformDetection.IsOpenSslSupported) { const int OpenSslChaChaMinimumVersion = 0x1_01_00_00_F; //major_minor_fix_patch_status expectedIsSupported = SafeEvpPKeyHandle.OpenSslVersion >= OpenSslChaChaMinimumVersion; diff --git a/src/native/libs/System.Security.Cryptography.Native.Apple/CMakeLists.txt b/src/native/libs/System.Security.Cryptography.Native.Apple/CMakeLists.txt index 77aa423237c358..0c288067d1148a 100644 --- a/src/native/libs/System.Security.Cryptography.Native.Apple/CMakeLists.txt +++ b/src/native/libs/System.Security.Cryptography.Native.Apple/CMakeLists.txt @@ -30,6 +30,7 @@ if (CLR_CMAKE_TARGET_MACCATALYST OR CLR_CMAKE_TARGET_IOS OR CLR_CMAKE_TARGET_TVO else() set(NATIVECRYPTO_SOURCES ${NATIVECRYPTO_SOURCES} + pal_swiftbindings.o pal_keychain_macos.c pal_keyderivation_macos.c pal_seckey_macos.c @@ -38,6 +39,24 @@ else() ) endif() +# As of CMake 3.20.2 support for Swift only works with the Ninja and XCode +# generators so we cannot rely on it. Even with the Ninja generator it doesn't +# work in combination with other languages within the same library. +if (NOT SWIFT_COMPILER_TARGET AND CLR_CMAKE_TARGET_OSX) + set(SWIFT_PLATFORM "macosx") + set(SWIFT_PLATFORM_SUFFIX "") + set(SWIFT_DEPLOYMENT_TARGET ${CMAKE_OSX_DEPLOYMENT_TARGET}) + set(SWIFT_COMPILER_TARGET "${CMAKE_OSX_ARCHITECTURES}-apple-${SWIFT_PLATFORM}${SWIFT_DEPLOYMENT_TARGET}${SWIFT_PLATFORM_SUFFIX}") +endif() + +add_custom_command( + OUTPUT pal_swiftbindings.o + COMMAND xcrun swiftc -emit-object -static -parse-as-library -runtime-compatibility-version none -sdk ${CMAKE_OSX_SYSROOT} -target ${SWIFT_COMPILER_TARGET} ${CMAKE_CURRENT_SOURCE_DIR}/pal_swiftbindings.swift -o pal_swiftbindings.o + MAIN_DEPENDENCY ${CMAKE_CURRENT_SOURCE_DIR}/pal_swiftbindings.swift + COMMENT "Compiling Swift file pal_swiftbindings.swift" +) +set_source_files_properties(pal_swiftbindings.o PROPERTIES EXTERNAL_OBJECT true GENERATED true) + if (CLR_CMAKE_TARGET_MACCATALYST) add_definitions(-DTARGET_MACCATALYST) endif() diff --git a/src/native/libs/System.Security.Cryptography.Native.Apple/entrypoints.c b/src/native/libs/System.Security.Cryptography.Native.Apple/entrypoints.c index 16e1efe9d25d1d..4f013c4cf74843 100644 --- a/src/native/libs/System.Security.Cryptography.Native.Apple/entrypoints.c +++ b/src/native/libs/System.Security.Cryptography.Native.Apple/entrypoints.c @@ -17,6 +17,7 @@ #include "pal_seckey_macos.h" #include "pal_signverify.h" #include "pal_ssl.h" +#include "pal_swiftbindings.h" #include "pal_symmetric.h" #include "pal_trust_macos.h" #include "pal_x509.h" @@ -25,6 +26,8 @@ static const Entry s_cryptoAppleNative[] = { + DllImportEntry(AppleCryptoNative_ChaCha20Poly1305Encrypt) + DllImportEntry(AppleCryptoNative_ChaCha20Poly1305Decrypt) DllImportEntry(AppleCryptoNative_DigestFree) DllImportEntry(AppleCryptoNative_DigestCreate) DllImportEntry(AppleCryptoNative_DigestUpdate) diff --git a/src/native/libs/System.Security.Cryptography.Native.Apple/extra_libs.cmake b/src/native/libs/System.Security.Cryptography.Native.Apple/extra_libs.cmake index 9ed82cd31bc29e..07b595bd6e4c2b 100644 --- a/src/native/libs/System.Security.Cryptography.Native.Apple/extra_libs.cmake +++ b/src/native/libs/System.Security.Cryptography.Native.Apple/extra_libs.cmake @@ -4,4 +4,10 @@ macro(append_extra_cryptography_apple_libs NativeLibsExtra) find_library(SECURITY_LIBRARY Security) list(APPEND ${NativeLibsExtra} ${COREFOUNDATION_LIBRARY} ${SECURITY_LIBRARY}) + + if (CLR_CMAKE_TARGET_OSX) + find_library(CRYPTOKIT_LIBRARY CryptoKit) + + list(APPEND ${NativeLibsExtra} ${CRYPTOKIT_LIBRARY} -L/usr/lib/swift -lobjc -lswiftCore -lswiftFoundation) + endif() endmacro() diff --git a/src/native/libs/System.Security.Cryptography.Native.Apple/pal_swiftbindings.h b/src/native/libs/System.Security.Cryptography.Native.Apple/pal_swiftbindings.h new file mode 100644 index 00000000000000..42e3c22e7dad6c --- /dev/null +++ b/src/native/libs/System.Security.Cryptography.Native.Apple/pal_swiftbindings.h @@ -0,0 +1,35 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#pragma once + +#include "pal_types.h" +#include "pal_compiler.h" + +PALEXPORT int32_t AppleCryptoNative_ChaCha20Poly1305Encrypt( + uint8_t* keyPtr, + int32_t keyLength, + uint8_t* noncePtr, + int32_t nonceLength, + uint8_t* plaintextPtr, + int32_t plaintextLength, + uint8_t* ciphertextBuffer, + int32_t ciphertextBufferLength, + uint8_t* tagBuffer, + int32_t tagBufferLength, + uint8_t* aadPtr, + int32_t aadLength); + +PALEXPORT int32_t AppleCryptoNative_ChaCha20Poly1305Decrypt( + uint8_t* keyPtr, + int32_t keyLength, + uint8_t* noncePtr, + int32_t nonceLength, + uint8_t* ciphertextPtr, + int32_t ciphertextLength, + uint8_t* tagPtr, + int32_t tagLength, + uint8_t* plaintextBuffer, + int32_t plaintextBufferLength, + uint8_t* aadPtr, + int32_t aadLength); diff --git a/src/native/libs/System.Security.Cryptography.Native.Apple/pal_swiftbindings.swift b/src/native/libs/System.Security.Cryptography.Native.Apple/pal_swiftbindings.swift new file mode 100644 index 00000000000000..7d54df744b1f70 --- /dev/null +++ b/src/native/libs/System.Security.Cryptography.Native.Apple/pal_swiftbindings.swift @@ -0,0 +1,88 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +import CryptoKit +import Foundation + +@_cdecl("AppleCryptoNative_ChaCha20Poly1305Encrypt") +public func AppleCryptoNative_ChaCha20Poly1305Encrypt( + keyPtr: UnsafeMutableRawPointer, + keyLength: Int32, + noncePtr: UnsafeMutableRawPointer, + nonceLength: Int32, + plaintextPtr: UnsafeMutableRawPointer, + plaintextLength: Int32, + ciphertextBuffer: UnsafeMutablePointer, + ciphertextBufferLength: Int32, + tagBuffer: UnsafeMutablePointer, + tagBufferLength: Int32, + aadPtr: UnsafeMutableRawPointer, + aadLength: Int32 + ) -> Int32 { + let nonceData = Data(bytesNoCopy: noncePtr, count: Int(nonceLength), deallocator: Data.Deallocator.none) + let key = Data(bytesNoCopy: keyPtr, count: Int(keyLength), deallocator: Data.Deallocator.none) + let plaintext = Data(bytesNoCopy: plaintextPtr, count: Int(plaintextLength), deallocator: Data.Deallocator.none) + let aad = Data(bytesNoCopy: aadPtr, count: Int(aadLength), deallocator: Data.Deallocator.none) + let symmetricKey = SymmetricKey(data: key) + + guard let nonce = try? ChaChaPoly.Nonce(data: nonceData) else { + return 0 + } + + guard let result = try? ChaChaPoly.seal(plaintext, using: symmetricKey, nonce: nonce, authenticating: aad) else { + return 0 + } + + assert(ciphertextBufferLength >= result.ciphertext.count) + assert(tagBufferLength >= result.tag.count) + + result.ciphertext.copyBytes(to: ciphertextBuffer, count: result.ciphertext.count) + result.tag.copyBytes(to: tagBuffer, count: result.tag.count) + return 1 + } + +@_cdecl("AppleCryptoNative_ChaCha20Poly1305Decrypt") +public func AppleCryptoNative_ChaCha20Poly1305Decrypt( + keyPtr: UnsafeMutableRawPointer, + keyLength: Int32, + noncePtr: UnsafeMutableRawPointer, + nonceLength: Int32, + ciphertextPtr: UnsafeMutableRawPointer, + ciphertextLength: Int32, + tagPtr: UnsafeMutableRawPointer, + tagLength: Int32, + plaintextBuffer: UnsafeMutablePointer, + plaintextBufferLength: Int32, + aadPtr: UnsafeMutableRawPointer, + aadLength: Int32 +) -> Int32 +{ + let nonceData = Data(bytesNoCopy: noncePtr, count: Int(nonceLength), deallocator: Data.Deallocator.none) + let key = Data(bytesNoCopy: keyPtr, count: Int(keyLength), deallocator: Data.Deallocator.none) + let ciphertext = Data(bytesNoCopy: ciphertextPtr, count: Int(ciphertextLength), deallocator: Data.Deallocator.none) + let aad = Data(bytesNoCopy: aadPtr, count: Int(aadLength), deallocator: Data.Deallocator.none) + let tag = Data(bytesNoCopy: tagPtr, count: Int(tagLength), deallocator: Data.Deallocator.none) + let symmetricKey = SymmetricKey(data: key) + + guard let nonce = try? ChaChaPoly.Nonce(data: nonceData) else { + return 0 + } + + guard let sealedBoxRestored = try? ChaChaPoly.SealedBox(nonce: nonce, ciphertext: ciphertext, tag: tag) else { + return 0 + } + + do { + let result = try ChaChaPoly.open(sealedBoxRestored, using: symmetricKey, authenticating: aad) + + assert(plaintextBufferLength >= result.count) + result.copyBytes(to: plaintextBuffer, count: result.count) + return 1 + } + catch CryptoKitError.authenticationFailure { + return -1 + } + catch { + return 0 + } +}