diff --git a/Cargo.lock b/Cargo.lock index d4ad1c8..7d53068 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -323,6 +323,15 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" +[[package]] +name = "csbindgen" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c9788bbcf4986358656d0596c3343a90da3e77d804f0bb4dea1c60b9fbda0e1" +dependencies = [ + "syn", +] + [[package]] name = "cxx" version = "1.0.92" @@ -672,6 +681,15 @@ dependencies = [ "thiserror", ] +[[package]] +name = "jpki-ffi-dotnet" +version = "0.4.2" +dependencies = [ + "csbindgen", + "jpki", + "jpki-ffi-generic", +] + [[package]] name = "jpki-ffi-generic" version = "0.4.2" diff --git a/Cargo.toml b/Cargo.toml index dfd1d48..11a070d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,4 +4,5 @@ members = [ "cli", "ffi/generic", "ffi/android", + "ffi/dotnet", ] diff --git a/ffi/dotnet/.gitignore b/ffi/dotnet/.gitignore new file mode 100644 index 0000000..496d718 --- /dev/null +++ b/ffi/dotnet/.gitignore @@ -0,0 +1,4 @@ +/bin/ +/obj/ +*.g.cs +*.user diff --git a/ffi/dotnet/Ap/CryptoAp.cs b/ffi/dotnet/Ap/CryptoAp.cs new file mode 100644 index 0000000..6e25791 --- /dev/null +++ b/ffi/dotnet/Ap/CryptoAp.cs @@ -0,0 +1,68 @@ +using Siketyan.Jpki.Native; + +namespace Siketyan.Jpki.Ap; + +public class CryptoAp +{ + private readonly unsafe Native.CryptoAp* _ptr; + private readonly Card _card; + + public CryptoAp(Card card) + { + _card = card; + + unsafe + { + _ptr = NativeMethods.jpki_new_crypto_ap(card.Ptr); + } + } + + ~CryptoAp() + { + unsafe + { + NativeMethods.jpki_crypto_ap_close(_ptr); + } + } + + public Span ReadCertificateSign(ReadOnlySpan pin, bool ca) + { + unsafe + { + fixed (byte* b = pin) + { + return NativeMethods.jpki_crypto_ap_read_certificate_sign(_ptr, b, ca).AsSpan(); + } + } + } + + public Span ReadCertificateAuth(bool ca) + { + unsafe + { + return NativeMethods.jpki_crypto_ap_read_certificate_auth(_ptr, ca).AsSpan(); + } + } + + public Span Sign(ReadOnlySpan pin, ReadOnlySpan digest) + { + unsafe + { + fixed (byte* b = pin) + { + return NativeMethods.jpki_crypto_ap_sign(_ptr, b, ByteArray.FromSpan(digest)).AsSpan(); + } + } + } + + public Span Auth(ReadOnlySpan pin, ReadOnlySpan digest) + { + unsafe + { + fixed (byte* b = pin) + { + return NativeMethods.jpki_crypto_ap_auth(_ptr, b, ByteArray.FromSpan(digest)).AsSpan(); + } + } + } +} diff --git a/ffi/dotnet/Card.cs b/ffi/dotnet/Card.cs new file mode 100644 index 0000000..809f5d5 --- /dev/null +++ b/ffi/dotnet/Card.cs @@ -0,0 +1,25 @@ +using Siketyan.Jpki.Native; + +namespace Siketyan.Jpki; + +public class Card +{ + internal readonly unsafe Native.Card* Ptr; + + private readonly NfcCardAdapter _adapter; + + public Card(NfcCardAdapter adapter) + { + _adapter = adapter; + + unsafe + { + Ptr = NativeMethods.jpki_new_card(adapter.Ptr); + } + } + + public Ap.CryptoAp OpenCryptoAp() + { + return new Ap.CryptoAp(this); + } +} diff --git a/ffi/dotnet/Cargo.toml b/ffi/dotnet/Cargo.toml new file mode 100644 index 0000000..c401798 --- /dev/null +++ b/ffi/dotnet/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "jpki-ffi-dotnet" +description = "FFI binding of jpki-rs for C#, F#, and VB.NET." +version = "0.4.2" +license = "LGPL-2.1-or-later" +homepage = "https://github.com/siketyan/jpki-rs" +repository = "https://github.com/siketyan/jpki-rs.git" +authors = [ + # Thank you for your contribution! + # While contributing to this project, feel free to add your name here :) + "Naoki Ikeguchi ", +] +readme = "../../README.md" +edition = "2021" + +[dependencies] +jpki = { version = "=0.4.2", path = "../../core" } +jpki-ffi-generic = { version = "=0.4.2", path = "../generic" } + +[build-dependencies] +csbindgen = "1.3" diff --git a/ffi/dotnet/INfcCard.cs b/ffi/dotnet/INfcCard.cs new file mode 100644 index 0000000..e7dd77c --- /dev/null +++ b/ffi/dotnet/INfcCard.cs @@ -0,0 +1,6 @@ +namespace Siketyan.Jpki; + +public interface INfcCard +{ + public Span Handle(ReadOnlySpan command); +} diff --git a/ffi/dotnet/Jpki.cs b/ffi/dotnet/Jpki.cs new file mode 100644 index 0000000..c3f893d --- /dev/null +++ b/ffi/dotnet/Jpki.cs @@ -0,0 +1,11 @@ +using Siketyan.Jpki.Native; + +namespace Siketyan.Jpki; + +public class Jpki +{ + static Jpki() + { + NativeMethods.jpki_init(); + } +} diff --git a/ffi/dotnet/Native/NativeMethods.cs b/ffi/dotnet/Native/NativeMethods.cs new file mode 100644 index 0000000..829933e --- /dev/null +++ b/ffi/dotnet/Native/NativeMethods.cs @@ -0,0 +1,43 @@ +namespace Siketyan.Jpki.Native; + +internal struct Card +{ +} + +internal struct CryptoAp +{ +} + +internal partial struct ByteArray +{ + public unsafe Span AsSpan() + { + return new Span(ptr, (int)len); + } + + public static unsafe ByteArray FromSpan(Span span) + { + fixed (byte* ptr = &span.GetPinnableReference()) + { + return new ByteArray + { + cap = (nuint)span.Length, + len = (nuint)span.Length, + ptr = ptr, + }; + } + } + + public static unsafe ByteArray FromSpan(ReadOnlySpan span) + { + fixed (byte* ptr = &span.GetPinnableReference()) + { + return new ByteArray + { + cap = (nuint)span.Length, + len = (nuint)span.Length, + ptr = ptr, + }; + } + } +} diff --git a/ffi/dotnet/NfcCard.cs b/ffi/dotnet/NfcCard.cs new file mode 100644 index 0000000..c3163d6 --- /dev/null +++ b/ffi/dotnet/NfcCard.cs @@ -0,0 +1,25 @@ +using Siketyan.Jpki.Native; + +namespace Siketyan.Jpki; + +public class NfcCardAdapter +{ + internal readonly unsafe NfcCard* Ptr; + + private readonly INfcCard _inner; + + public NfcCardAdapter(INfcCard inner) + { + _inner = inner; + + unsafe + { + Ptr = NativeMethods.jpki_new_nfc_card(Handle); + } + } + + private ByteArray Handle(ByteArray command) + { + return ByteArray.FromSpan(_inner.Handle(command.AsSpan())); + } +} diff --git a/ffi/dotnet/build.rs b/ffi/dotnet/build.rs new file mode 100644 index 0000000..6e12758 --- /dev/null +++ b/ffi/dotnet/build.rs @@ -0,0 +1,9 @@ +fn main() { + csbindgen::Builder::default() + .input_extern_file("../generic/src/lib.rs") + .csharp_dll_name("jpki") + .csharp_namespace("Siketyan.Jpki.Native") + .csharp_use_function_pointer(false) + .generate_csharp_file("./Native/NativeMethods.g.cs") + .unwrap(); +} diff --git a/ffi/dotnet/dotnet.csproj b/ffi/dotnet/dotnet.csproj new file mode 100644 index 0000000..60a88d8 --- /dev/null +++ b/ffi/dotnet/dotnet.csproj @@ -0,0 +1,19 @@ + + + + net7.0 + enable + enable + Siketyan.Jpki + Siketyan.Jpki + + + + true + + + + true + + + diff --git a/ffi/dotnet/src/lib.rs b/ffi/dotnet/src/lib.rs new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/ffi/dotnet/src/lib.rs @@ -0,0 +1 @@ + diff --git a/ffi/generic/src/lib.rs b/ffi/generic/src/lib.rs index e94d9ea..24b3556 100644 --- a/ffi/generic/src/lib.rs +++ b/ffi/generic/src/lib.rs @@ -78,13 +78,13 @@ impl ByteArray { } pub struct NfcCard { - delegate: extern "C" fn(ByteArray) -> ByteArray, + delegate_: extern "C" fn(command: ByteArray) -> ByteArray, } impl HandlerInCtx for NfcCard { fn handle_in_ctx(&self, _: (), command: &[u8], response: &mut [u8]) -> NfcResult { let command = Vec::from(command).into(); - let buf = Vec::from((self.delegate)(command)); + let buf = Vec::from((self.delegate_)(command)); let len = buf.len(); if response.len() < len { return Err(HandleError::NotEnoughBuffer(len)); @@ -116,9 +116,9 @@ pub extern "C" fn jpki_last_error() -> *mut c_char { /// This provided function will be called on transmitting APDU commands into the card. #[no_mangle] pub extern "C" fn jpki_new_nfc_card( - delegate: extern "C" fn(ByteArray) -> ByteArray, + delegate_: extern "C" fn(command: ByteArray) -> ByteArray, ) -> *mut NfcCard { - let card = NfcCard { delegate }; + let card = NfcCard { delegate_ }; Box::into_raw(Box::new(card)) }