dotnet / runtimelab

This repo is for experimentation and exploring new ideas that may or may not make it into the main dotnet/runtime repo.
MIT License
1.36k stars 188 forks source link

[Discussion] Generate C# bindings for CryptoKit framework #2583

Open kotlarmilos opened 1 month ago

kotlarmilos commented 1 month ago

Objective

Our goal is to generate CryptoKit APIs bindings used for encryption and decryption. We aim to replace the existing Objective-C bindings with direct calls into Swift.

Dev template

The CryptoKit framework implements enum types with inner structs. Besides the Swift primitive types, the framework depends on Data frozen struct from Foundation framework. Additionally, the implementation contains generics and optional parameters in function signatures. To implement the C# projections, we identified the following implementation phases.

Frozen structs: Utilize runtime lowering support within the runtime to project parameters as UnsafeRawBufferPointer/UnsafeMutableBufferPointer<T> and call into a Swift wrapper that calls the CryptoKit API.

import CryptoKit
import Foundation

public func AppleCryptoNative_ChaCha20Poly1305Encrypt(
    keyBuffer: UnsafeRawBufferPointer,
    nonceBuffer: UnsafeRawBufferPointer,
    plaintextBuffer: UnsafeRawBufferPointer,
    ciphertextBuffer: UnsafeMutableBufferPointer<UInt8>,
    tagBuffer: UnsafeMutableBufferPointer<UInt8>,
    aadBuffer: UnsafeRawBufferPointer
 ) -> Int32 {
    let nonce = try! ChaChaPoly.Nonce(data: nonceBuffer)
    let symmetricKey = SymmetricKey(data: keyBuffer)

    guard let result = try? ChaChaPoly.seal(plaintextBuffer, using: symmetricKey, nonce: nonce, authenticating: aadBuffer) else {
        return 0
    }

    assert(ciphertextBuffer.count >= result.ciphertext.count)
    assert(tagBuffer.count >= result.tag.count)

    result.ciphertext.copyBytes(to: ciphertextBuffer, count: result.ciphertext.count)
    result.tag.copyBytes(to: tagBuffer, count: result.tag.count)
    return 1
 }

Surface type constructors: Surface type constructors for symmetric key and nonce, making them accessible as C# types. This requires support for SwiftSelf in instance methods and SwiftError for methods that can throw errors.

    let nonce = try! ChaChaPoly.Nonce(data: nonceBuffer)
    let symmetricKey = SymmetricKey(data: keyBuffer)
    guard let result = try! ChaChaPoly.seal(plaintextBuffer, using: symmetricKey, nonce: nonce, authenticating: aadBuffer)

Implement optional and generics projections: Implement direct calls into Swift methods by adding implicit metadata parameters and conformance witness tables for generics. This includes changes from https://github.com/dotnet/runtime/issues/100543.