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 abceed80bb2bf9..70717781d2983a 100644 --- a/src/native/libs/System.Security.Cryptography.Native.Apple/CMakeLists.txt +++ b/src/native/libs/System.Security.Cryptography.Native.Apple/CMakeLists.txt @@ -132,6 +132,14 @@ if (GEN_SHARED_LIB) ${NATIVE_LIBS_EXTRA} ) + add_custom_command(TARGET System.Security.Cryptography.Native.Apple POST_BUILD + COMMENT "Verifying System.Security.Cryptography.Native.Apple has no Swift ObjC classes" + COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/../verify-no-swift-objc-classes.sh + $ + ${CMAKE_NM} + VERBATIM + ) + if (NOT CLR_CMAKE_TARGET_APPLE_MOBILE) add_custom_command(TARGET System.Security.Cryptography.Native.Apple POST_BUILD COMMENT "Verifying System.Security.Cryptography.Native.Apple points against entrypoints.c " @@ -144,6 +152,14 @@ if (GEN_SHARED_LIB) endif() endif() +add_custom_command(TARGET System.Security.Cryptography.Native.Apple-Static POST_BUILD + COMMENT "Verifying System.Security.Cryptography.Native.Apple static archive has no Swift ObjC classes" + COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/../verify-no-swift-objc-classes.sh + $ + ${CMAKE_NM} + VERBATIM +) + if (GEN_SHARED_LIB) install_with_stripped_symbols (System.Security.Cryptography.Native.Apple PROGRAMS .) endif() 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 index 28ee4fed9d2f85..d42609bdb77c0d 100644 --- a/src/native/libs/System.Security.Cryptography.Native.Apple/pal_swiftbindings.swift +++ b/src/native/libs/System.Security.Cryptography.Native.Apple/pal_swiftbindings.swift @@ -4,11 +4,8 @@ import CryptoKit import Foundation -final class HashBox { +struct HashBox { var value: any HashFunction - init(_ value: any HashFunction) { - self.value = value - } } enum X25519Key { @@ -23,13 +20,6 @@ enum X25519Key { } } -final class X25519KeyBox { - var value: X25519Key - init(_ value: X25519Key) { - self.value = value - } -} - protocol NonceProtocol { init(data: D) throws where D : DataProtocol } @@ -72,6 +62,18 @@ extension ChaChaPoly.SealedBox: SealedBoxProtocol { extension ChaChaPoly: AEADSymmetricAlgorithm {} +private func allocatePointer(to value: T) -> UnsafeMutableRawPointer { + let pointer = UnsafeMutablePointer.allocate(capacity: 1) + pointer.initialize(to: value) + return UnsafeMutableRawPointer(pointer) +} + +private func deallocatePointer(_ pointer: UnsafeMutableRawPointer, to type: T.Type) { + let typedPointer = pointer.assumingMemoryBound(to: type) + typedPointer.deinitialize(count: 1) + typedPointer.deallocate() +} + func encrypt( _ algorithm: Algorithm.Type, key: UnsafeBufferPointer, @@ -422,24 +424,19 @@ public func AppleCryptoNative_DigestCreate(algorithm: Int32, pcbDigest: UnsafeMu switch hashAlgorithm { case .md5: pcbDigest.pointee = Int32(Insecure.MD5.byteCount) - let box = HashBox(Insecure.MD5()) - return Unmanaged.passRetained(box).toOpaque() + return allocatePointer(to: HashBox(value: Insecure.MD5())) case .sha1: pcbDigest.pointee = Int32(Insecure.SHA1.byteCount) - let box = HashBox(Insecure.SHA1()) - return Unmanaged.passRetained(box).toOpaque() + return allocatePointer(to: HashBox(value: Insecure.SHA1())) case .sha256: pcbDigest.pointee = Int32(SHA256.byteCount) - let box = HashBox(SHA256()) - return Unmanaged.passRetained(box).toOpaque() + return allocatePointer(to: HashBox(value: SHA256())) case .sha384: pcbDigest.pointee = Int32(SHA384.byteCount) - let box = HashBox(SHA384()) - return Unmanaged.passRetained(box).toOpaque() + return allocatePointer(to: HashBox(value: SHA384())) case .sha512: pcbDigest.pointee = Int32(SHA512.byteCount) - let box = HashBox(SHA512()) - return Unmanaged.passRetained(box).toOpaque() + return allocatePointer(to: HashBox(value: SHA512())) default: pcbDigest.pointee = 0 return nil @@ -456,11 +453,11 @@ public func AppleCryptoNative_DigestUpdate(ctx: UnsafeMutableRawPointer?, pBuf: return -1 } - let box = Unmanaged.fromOpaque(ctx).takeUnretainedValue() + let box = ctx.assumingMemoryBound(to: HashBox.self) let source = Data(bytesNoCopy: pBuf, count: Int(cBuf), deallocator: Data.Deallocator.none) - var hash = box.value + var hash = box.pointee.value hash.update(data: source) - box.value = hash + box.pointee.value = hash return 1 } @@ -470,23 +467,23 @@ public func AppleCryptoNative_DigestReset(ctx: UnsafeMutableRawPointer?) -> Int3 return -1 } - let box = Unmanaged.fromOpaque(ctx).takeUnretainedValue() + let box = ctx.assumingMemoryBound(to: HashBox.self) - switch box.value { + switch box.pointee.value { case is Insecure.MD5: - box.value = Insecure.MD5() + box.pointee.value = Insecure.MD5() return 1 case is Insecure.SHA1: - box.value = Insecure.SHA1() + box.pointee.value = Insecure.SHA1() return 1 case is SHA256: - box.value = SHA256() + box.pointee.value = SHA256() return 1 case is SHA384: - box.value = SHA384() + box.pointee.value = SHA384() return 1 case is SHA512: - box.value = SHA512() + box.pointee.value = SHA512() return 1 default: return -2 @@ -499,10 +496,10 @@ public func AppleCryptoNative_DigestFinal(ctx: UnsafeMutableRawPointer?, pOutput return -1 } - let box = Unmanaged.fromOpaque(ctx).takeUnretainedValue() + let box = ctx.assumingMemoryBound(to: HashBox.self) let destination = UnsafeMutableRawBufferPointer(start: pOutput, count: Int(cbOutput)) - let hash = box.value.finalize() + let hash = box.pointee.value.finalize() let copied = hash.withUnsafeBytes { digest in return digest.copyBytes(to: destination) == digest.count } @@ -517,7 +514,7 @@ public func AppleCryptoNative_DigestFinal(ctx: UnsafeMutableRawPointer?, pOutput @_silgen_name("AppleCryptoNative_DigestFree") public func AppleCryptoNative_DigestFree(ptr: UnsafeMutableRawPointer?) { if let ptr { - Unmanaged.fromOpaque(ptr).release() + deallocatePointer(ptr, to: HashBox.self) } } @@ -527,11 +524,10 @@ public func AppleCryptoNative_DigestClone(ctx: UnsafeMutableRawPointer?) -> Unsa return nil } - let box = Unmanaged.fromOpaque(ctx).takeUnretainedValue() - let digest = box.value + let box = ctx.assumingMemoryBound(to: HashBox.self) + let digest = box.pointee.value let clone = digest - let cloneBox = HashBox(clone) - return Unmanaged.passRetained(cloneBox).toOpaque() + return allocatePointer(to: HashBox(value: clone)) } @_silgen_name("AppleCryptoNative_DigestCurrent") @@ -540,9 +536,9 @@ public func AppleCryptoNative_DigestCurrent(ctx: UnsafeMutableRawPointer?, pOutp return -1 } - let box = Unmanaged.fromOpaque(ctx).takeUnretainedValue() + let box = ctx.assumingMemoryBound(to: HashBox.self) let destination = UnsafeMutableRawBufferPointer(start: pOutput, count: Int(cbOutput)) - let unboxed = box.value + let unboxed = box.pointee.value let clone = unboxed let hash = clone.finalize() let copied = hash.withUnsafeBytes { digest in @@ -593,14 +589,14 @@ public func AppleCryptoNative_X25519DeriveRawSecretAgreement( return -1 } - let keyBox = Unmanaged.fromOpaque(keyPtr).takeUnretainedValue() - let peerBox = Unmanaged.fromOpaque(peerKeyPtr).takeUnretainedValue() + let keyBox = keyPtr.assumingMemoryBound(to: X25519Key.self) + let peerBox = peerKeyPtr.assumingMemoryBound(to: X25519Key.self) - guard case .privateKey(let key) = keyBox.value else { + guard case .privateKey(let key) = keyBox.pointee else { return -1 } - let peerKey = peerBox.value.getPublic() + let peerKey = peerBox.pointee.getPublic() return deriveRawSecretAgreement(key: key, peerKey: peerKey, pOutput: pOutput, cbOutput: cbOutput) } @@ -615,9 +611,9 @@ public func AppleCryptoNative_X25519DeriveRawSecretAgreementWithBytes( return -1 } - let keyBox = Unmanaged.fromOpaque(keyPtr).takeUnretainedValue() + let keyBox = keyPtr.assumingMemoryBound(to: X25519Key.self) - guard case .privateKey(let key) = keyBox.value else { + guard case .privateKey(let key) = keyBox.pointee else { return -1 } @@ -633,7 +629,7 @@ public func AppleCryptoNative_X25519DeriveRawSecretAgreementWithBytes( @_silgen_name("AppleCryptoNative_X25519FreeKey") public func AppleCryptoNative_X25519FreeKey(ptr: UnsafeMutableRawPointer?) { if let ptr { - Unmanaged.fromOpaque(ptr).release() + deallocatePointer(ptr, to: X25519Key.self) } } @@ -646,9 +642,9 @@ public func AppleCryptoNative_X25519ExportPrivateKey( return -1 } - let box = Unmanaged.fromOpaque(keyPtr).takeUnretainedValue() + let box = keyPtr.assumingMemoryBound(to: X25519Key.self) - guard case .privateKey(let key) = box.value else { + guard case .privateKey(let key) = box.pointee else { return -1 } @@ -673,8 +669,8 @@ public func AppleCryptoNative_X25519ExportPublicKey( return -1 } - let box = Unmanaged.fromOpaque(keyPtr).takeUnretainedValue() - let key = box.value.getPublic() + let box = keyPtr.assumingMemoryBound(to: X25519Key.self) + let key = box.pointee.getPublic() let destination = UnsafeMutableRawBufferPointer(start: pOutput, count: Int(cbOutput)) let copied = key.rawRepresentation.withUnsafeBytes { pubKey in @@ -691,8 +687,7 @@ public func AppleCryptoNative_X25519ExportPublicKey( @_silgen_name("AppleCryptoNative_X25519GenerateKey") public func AppleCryptoNative_X25519GenerateKey() -> UnsafeMutableRawPointer? { let key = Curve25519.KeyAgreement.PrivateKey.init() - let box = X25519KeyBox(X25519Key.privateKey(key)) - return Unmanaged.passRetained(box).toOpaque() + return allocatePointer(to: X25519Key.privateKey(key)) } @_silgen_name("AppleCryptoNative_X25519ImportPrivateKey") @@ -707,8 +702,7 @@ public func AppleCryptoNative_X25519ImportPrivateKey(pKey: UnsafeMutableRawPoint return nil } - let box = X25519KeyBox(X25519Key.privateKey(key)) - return Unmanaged.passRetained(box).toOpaque() + return allocatePointer(to: X25519Key.privateKey(key)) } @_silgen_name("AppleCryptoNative_X25519ImportPublicKey") @@ -723,6 +717,5 @@ public func AppleCryptoNative_X25519ImportPublicKey(pKey: UnsafeMutableRawPointe return nil } - let box = X25519KeyBox(X25519Key.publicKey(key)) - return Unmanaged.passRetained(box).toOpaque() + return allocatePointer(to: X25519Key.publicKey(key)) } diff --git a/src/native/libs/verify-no-swift-objc-classes.sh b/src/native/libs/verify-no-swift-objc-classes.sh new file mode 100755 index 00000000000000..a39f27bd868e44 --- /dev/null +++ b/src/native/libs/verify-no-swift-objc-classes.sh @@ -0,0 +1,32 @@ +#!/usr/bin/env bash + +if (( $# != 2 )); then + echo "Usage:" + echo "verify-no-swift-objc-classes.sh " + exit 1 +fi + +library=$1 +nmCommand=$2 + +if ! nmOutput=$("$nmCommand" -m "$library"); then + echo "ERROR: failed to inspect $library for Swift ObjC class definitions." >&2 + exit 2 +fi + +swiftObjCClasses=() +while IFS= read -r line; do + # Swift classes are registered as ObjC classes with process-global names. Local + # definitions in this library can collide if another copy is loaded into the + # same process, so keep Swift bindings limited to value types and functions. + if [[ $line != *"(undefined)"* && $line =~ (__DATA__TtC|__METACLASS_DATA__TtC|__IVARS__TtC|_OBJC_CLASS_\$__TtC) ]]; then + swiftObjCClasses+=("$line") + fi +done <<< "$nmOutput" + +if (( ${#swiftObjCClasses[@]} != 0 )); then + echo "ERROR: $library contains Swift ObjC class definitions." >&2 + echo "Swift classes have process-global ObjC runtime names and must not be used in this native library." >&2 + printf '%s\n' "${swiftObjCClasses[@]}" >&2 + exit 2 +fi