diff --git a/Cargo.lock b/Cargo.lock
index de0bc8ca..78fefb55 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -11,12 +11,33 @@ dependencies = [
  "memchr",
 ]
 
+[[package]]
+name = "async-signature"
+version = "0.6.0-pre.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f9bdb5df8dde2bd1ec515a0981636508bb37d55984d0bae3678d4ac859125431"
+dependencies = [
+ "signature",
+]
+
 [[package]]
 name = "autocfg"
 version = "1.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
 
+[[package]]
+name = "base16ct"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf"
+
+[[package]]
+name = "base64ct"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b"
+
 [[package]]
 name = "bindgen"
 version = "0.69.4"
@@ -52,6 +73,21 @@ version = "2.4.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf"
 
+[[package]]
+name = "block-buffer"
+version = "0.11.0-rc.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3fd016a0ddc7cb13661bf5576073ce07330a693f8608a1320b4e20561cc12cdc"
+dependencies = [
+ "hybrid-array",
+]
+
+[[package]]
+name = "byteorder"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
+
 [[package]]
 name = "cc"
 version = "1.0.86"
@@ -93,6 +129,57 @@ dependencies = [
  "cc",
 ]
 
+[[package]]
+name = "cms"
+version = "0.3.0-pre.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "956098b1603285c33972193d6f62c8389d3d8548693a4077baa08ff0a8da97c7"
+dependencies = [
+ "const-oid",
+ "der",
+ "spki",
+ "x509-cert",
+]
+
+[[package]]
+name = "const-oid"
+version = "0.10.0-rc.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9adcf94f05e094fca3005698822ec791cb4433ced416afda1c5ca3b8dfc05a2f"
+
+[[package]]
+name = "cpufeatures"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "crypto-bigint"
+version = "0.6.0-rc.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d748d1f5b807ee6d0df5a548d0130417295c3aaed1dcbbb3d6a2e7106e11fcca"
+dependencies = [
+ "hybrid-array",
+ "num-traits",
+ "rand_core",
+ "subtle",
+ "zeroize",
+]
+
+[[package]]
+name = "crypto-common"
+version = "0.2.0-rc.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b0b8ce8218c97789f16356e7896b3714f26c2ee1079b79c0b7ae7064bb9089fa"
+dependencies = [
+ "getrandom",
+ "hybrid-array",
+ "rand_core",
+]
+
 [[package]]
 name = "cryptoki"
 version = "0.7.0"
@@ -107,7 +194,30 @@ dependencies = [
  "psa-crypto",
  "secrecy",
  "serial_test",
- "testresult",
+ "testresult 0.4.1",
+]
+
+[[package]]
+name = "cryptoki-rustcrypto"
+version = "0.1.0"
+dependencies = [
+ "cryptoki",
+ "der",
+ "ecdsa",
+ "k256",
+ "p256",
+ "p384",
+ "pkcs12",
+ "rand",
+ "rsa",
+ "serial_test",
+ "sha1",
+ "sha2",
+ "signature",
+ "spki",
+ "testresult 0.2.0",
+ "thiserror",
+ "x509-cert",
 ]
 
 [[package]]
@@ -118,12 +228,83 @@ dependencies = [
  "libloading 0.7.4",
 ]
 
+[[package]]
+name = "der"
+version = "0.8.0-rc.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "82db698b33305f0134faf590b9d1259dc171b5481ac41d5c8146c3b3ee7d4319"
+dependencies = [
+ "const-oid",
+ "der_derive",
+ "flagset",
+ "pem-rfc7468",
+ "zeroize",
+]
+
+[[package]]
+name = "der_derive"
+version = "0.8.0-rc.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "211bea8bb45f5f61bc857104606913ef8ac8b5ec698143aa2aa96a7ffdc94991"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.50",
+]
+
+[[package]]
+name = "digest"
+version = "0.11.0-pre.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf2e3d6615d99707295a9673e889bf363a04b2a466bd320c65a72536f7577379"
+dependencies = [
+ "block-buffer",
+ "const-oid",
+ "crypto-common",
+ "subtle",
+]
+
+[[package]]
+name = "ecdsa"
+version = "0.17.0-pre.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7e62f2041a28c40b8884b79fbd19bc7457d76c6397767831e9ff4029fc0473a9"
+dependencies = [
+ "der",
+ "digest",
+ "elliptic-curve",
+ "rfc6979",
+ "signature",
+ "spki",
+]
+
 [[package]]
 name = "either"
 version = "1.10.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a"
 
+[[package]]
+name = "elliptic-curve"
+version = "0.14.0-rc.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cc43715037532dc2d061e5c97e81b684c28993d52a4fa4eb7d2ce2826d78f2f2"
+dependencies = [
+ "base16ct",
+ "crypto-bigint",
+ "digest",
+ "ff",
+ "group",
+ "hkdf",
+ "hybrid-array",
+ "pem-rfc7468",
+ "pkcs8",
+ "rand_core",
+ "sec1",
+ "subtle",
+ "zeroize",
+]
+
 [[package]]
 name = "errno"
 version = "0.3.8"
@@ -134,18 +315,74 @@ dependencies = [
  "windows-sys 0.52.0",
 ]
 
+[[package]]
+name = "ff"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449"
+dependencies = [
+ "rand_core",
+ "subtle",
+]
+
+[[package]]
+name = "flagset"
+version = "0.4.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b3ea1ec5f8307826a5b71094dd91fc04d4ae75d5709b20ad351c7fb4815c86ec"
+
+[[package]]
+name = "getrandom"
+version = "0.2.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "wasi",
+]
+
 [[package]]
 name = "glob"
 version = "0.3.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
 
+[[package]]
+name = "group"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63"
+dependencies = [
+ "ff",
+ "rand_core",
+ "subtle",
+]
+
 [[package]]
 name = "hex"
 version = "0.4.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
 
+[[package]]
+name = "hkdf"
+version = "0.13.0-pre.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "00176ff81091018d42ff82e8324f8e5adb0b7e0468d1358f653972562dbff031"
+dependencies = [
+ "hmac",
+]
+
+[[package]]
+name = "hmac"
+version = "0.13.0-pre.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e4b1fb14e4df79f9406b434b60acef9f45c26c50062cccf1346c6103b8c47d58"
+dependencies = [
+ "digest",
+]
+
 [[package]]
 name = "home"
 version = "0.5.9"
@@ -155,6 +392,16 @@ dependencies = [
  "windows-sys 0.52.0",
 ]
 
+[[package]]
+name = "hybrid-array"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "45a9a965bb102c1c891fb017c09a05c965186b1265a207640f323ddd009f9deb"
+dependencies = [
+ "typenum",
+ "zeroize",
+]
+
 [[package]]
 name = "instant"
 version = "0.1.12"
@@ -173,11 +420,28 @@ dependencies = [
  "either",
 ]
 
+[[package]]
+name = "k256"
+version = "0.14.0-pre.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6995f4341b819603e1b836b530ba1c33bbb677d0a3d68ed122a55081abfc82dd"
+dependencies = [
+ "cfg-if",
+ "ecdsa",
+ "elliptic-curve",
+ "once_cell",
+ "sha2",
+ "signature",
+]
+
 [[package]]
 name = "lazy_static"
 version = "1.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
+dependencies = [
+ "spin",
+]
 
 [[package]]
 name = "lazycell"
@@ -187,9 +451,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
 
 [[package]]
 name = "libc"
-version = "0.2.153"
+version = "0.2.162"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
+checksum = "18d287de67fe55fd7e1581fe933d965a5a9477b38e949cfa9f8574ef01506398"
 
 [[package]]
 name = "libloading"
@@ -211,6 +475,12 @@ dependencies = [
  "windows-sys 0.48.0",
 ]
 
+[[package]]
+name = "libm"
+version = "0.2.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa"
+
 [[package]]
 name = "linux-raw-sys"
 version = "0.4.13"
@@ -255,13 +525,51 @@ dependencies = [
  "minimal-lexical",
 ]
 
+[[package]]
+name = "num-bigint-dig"
+version = "0.8.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151"
+dependencies = [
+ "byteorder",
+ "lazy_static",
+ "libm",
+ "num-integer",
+ "num-iter",
+ "num-traits",
+ "rand",
+ "smallvec",
+ "zeroize",
+]
+
+[[package]]
+name = "num-integer"
+version = "0.1.46"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
+name = "num-iter"
+version = "0.1.45"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf"
+dependencies = [
+ "autocfg",
+ "num-integer",
+ "num-traits",
+]
+
 [[package]]
 name = "num-traits"
-version = "0.2.18"
+version = "0.2.19"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a"
+checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
 dependencies = [
  "autocfg",
+ "libm",
 ]
 
 [[package]]
@@ -270,6 +578,30 @@ version = "1.19.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
 
+[[package]]
+name = "p256"
+version = "0.14.0-pre.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "71f3fd64a9cad9c26ed7f734b152196d5e56376b9957c832bcca0de48a708080"
+dependencies = [
+ "ecdsa",
+ "elliptic-curve",
+ "primeorder",
+ "sha2",
+]
+
+[[package]]
+name = "p384"
+version = "0.14.0-pre.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e19554fe6ee269c860a0f231cbba714e5cbef26a927c75d8e30ac9040a4b32e"
+dependencies = [
+ "ecdsa",
+ "elliptic-curve",
+ "primeorder",
+ "sha2",
+]
+
 [[package]]
 name = "parking_lot"
 version = "0.11.2"
@@ -301,6 +633,57 @@ version = "1.0.14"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c"
 
+[[package]]
+name = "pem-rfc7468"
+version = "1.0.0-rc.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c2dfbfa5c6f0906884269722c5478e72fd4d6c0e24fe600332c6d62359567ce1"
+dependencies = [
+ "base64ct",
+]
+
+[[package]]
+name = "pkcs1"
+version = "0.8.0-rc.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "226eb25e2c46c166ce498ac0f606ac623142d640064879ff445938accddff1e2"
+dependencies = [
+ "der",
+ "pkcs8",
+ "spki",
+]
+
+[[package]]
+name = "pkcs12"
+version = "0.2.0-pre"
+source = "git+https://github.com/RustCrypto/formats.git#48eac00018b31215141f98550e342da7d40062d6"
+dependencies = [
+ "cms",
+ "const-oid",
+ "der",
+ "spki",
+ "x509-cert",
+]
+
+[[package]]
+name = "pkcs8"
+version = "0.11.0-rc.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eacd2c7141f32aef1cfd1ad0defb5287a3d94592d7ab57c1ae20e3f9f1f0db1f"
+dependencies = [
+ "der",
+ "spki",
+]
+
+[[package]]
+name = "ppv-lite86"
+version = "0.2.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04"
+dependencies = [
+ "zerocopy",
+]
+
 [[package]]
 name = "prettyplease"
 version = "0.2.16"
@@ -311,6 +694,15 @@ dependencies = [
  "syn 2.0.50",
 ]
 
+[[package]]
+name = "primeorder"
+version = "0.14.0-pre.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b794117b388378d55629f78f61e64e182baa200bf59c1a8205e0c46508ce5873"
+dependencies = [
+ "elliptic-curve",
+]
+
 [[package]]
 name = "proc-macro2"
 version = "1.0.78"
@@ -353,6 +745,36 @@ dependencies = [
  "proc-macro2",
 ]
 
+[[package]]
+name = "rand"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
+dependencies = [
+ "libc",
+ "rand_chacha",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
+dependencies = [
+ "ppv-lite86",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
+dependencies = [
+ "getrandom",
+]
+
 [[package]]
 name = "redox_syscall"
 version = "0.2.16"
@@ -391,6 +813,35 @@ version = "0.8.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
 
+[[package]]
+name = "rfc6979"
+version = "0.5.0-pre.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "871ee76a3eee98b0f805e5d1caf26929f4565073c580c053a55f886fc15dea49"
+dependencies = [
+ "hmac",
+ "subtle",
+]
+
+[[package]]
+name = "rsa"
+version = "0.10.0-pre.3"
+dependencies = [
+ "const-oid",
+ "digest",
+ "num-bigint-dig",
+ "num-integer",
+ "num-traits",
+ "pkcs1",
+ "pkcs8",
+ "rand_core",
+ "sha2",
+ "signature",
+ "spki",
+ "subtle",
+ "zeroize",
+]
+
 [[package]]
 name = "rustc-hash"
 version = "1.1.0"
@@ -425,6 +876,20 @@ version = "1.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
 
+[[package]]
+name = "sec1"
+version = "0.8.0-rc.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d1988446eff153796413a73669dfaa4caa3f5ce8b25fac89e3821a39c611772e"
+dependencies = [
+ "base16ct",
+ "der",
+ "hybrid-array",
+ "pkcs8",
+ "subtle",
+ "zeroize",
+]
+
 [[package]]
 name = "secrecy"
 version = "0.8.0"
@@ -477,18 +942,84 @@ dependencies = [
  "syn 1.0.109",
 ]
 
+[[package]]
+name = "sha1"
+version = "0.11.0-pre.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9540978cef7a8498211c1b1c14e5ce920fe5bd524ea84f4a3d72d4602515ae93"
+dependencies = [
+ "cfg-if",
+ "cpufeatures",
+ "digest",
+]
+
+[[package]]
+name = "sha2"
+version = "0.11.0-pre.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "540c0893cce56cdbcfebcec191ec8e0f470dd1889b6e7a0b503e310a94a168f5"
+dependencies = [
+ "cfg-if",
+ "cpufeatures",
+ "digest",
+]
+
 [[package]]
 name = "shlex"
 version = "1.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
 
+[[package]]
+name = "signature"
+version = "2.3.0-pre.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "054d71959c7051b9042c26af337f05cc930575ed2604d7d3ced3158383e59734"
+dependencies = [
+ "digest",
+ "rand_core",
+ "signature_derive",
+]
+
+[[package]]
+name = "signature_derive"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ab0381d1913eeaf4c7bc4094016c9a8de6c1120663afe32a90ff268ad7f80486"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.50",
+]
+
 [[package]]
 name = "smallvec"
 version = "1.13.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7"
 
+[[package]]
+name = "spin"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
+
+[[package]]
+name = "spki"
+version = "0.8.0-rc.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "37ac66481418fd7afdc584adcf3be9aa572cf6c2858814494dc2a01755f050bc"
+dependencies = [
+ "base64ct",
+ "der",
+]
+
+[[package]]
+name = "subtle"
+version = "2.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
+
 [[package]]
 name = "syn"
 version = "1.0.109"
@@ -511,12 +1042,65 @@ dependencies = [
  "unicode-ident",
 ]
 
+[[package]]
+name = "testresult"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "649113ab56eab59f78f02314a05b24bda0200d322c9eb1c60d0af8554e94c5ef"
+
 [[package]]
 name = "testresult"
 version = "0.4.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "614b328ff036a4ef882c61570f72918f7e9c5bee1da33f8e7f91e01daee7e56c"
 
+[[package]]
+name = "thiserror"
+version = "1.0.65"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5d11abd9594d9b38965ef50805c5e469ca9cc6f197f883f717e0269a3057b3d5"
+dependencies = [
+ "thiserror-impl",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "1.0.65"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae71770322cbd277e69d762a16c444af02aa0575ac0d174f0b9562d3b37f8602"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.50",
+]
+
+[[package]]
+name = "tls_codec"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b5e78c9c330f8c85b2bae7c8368f2739157db9991235123aa1b15ef9502bfb6a"
+dependencies = [
+ "tls_codec_derive",
+ "zeroize",
+]
+
+[[package]]
+name = "tls_codec_derive"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8d9ef545650e79f30233c0003bcc2504d7efac6dad25fca40744de773fe2049c"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.50",
+]
+
+[[package]]
+name = "typenum"
+version = "1.17.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
+
 [[package]]
 name = "unicode-ident"
 version = "1.0.12"
@@ -533,6 +1117,12 @@ dependencies = [
  "winapi-util",
 ]
 
+[[package]]
+name = "wasi"
+version = "0.11.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
+
 [[package]]
 name = "which"
 version = "4.4.2"
@@ -708,11 +1298,47 @@ version = "0.52.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04"
 
+[[package]]
+name = "x509-cert"
+version = "0.3.0-pre.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2db382aa43c1fb5c419a960f72c3847ab0f383f635fc2e25f0bd6c5fb94371d1"
+dependencies = [
+ "async-signature",
+ "const-oid",
+ "der",
+ "sha1",
+ "signature",
+ "spki",
+ "tls_codec",
+]
+
+[[package]]
+name = "zerocopy"
+version = "0.7.35"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
+dependencies = [
+ "byteorder",
+ "zerocopy-derive",
+]
+
+[[package]]
+name = "zerocopy-derive"
+version = "0.7.35"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.50",
+]
+
 [[package]]
 name = "zeroize"
-version = "1.7.0"
+version = "1.8.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d"
+checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde"
 dependencies = [
  "zeroize_derive",
 ]
diff --git a/Cargo.toml b/Cargo.toml
index 99938e6c..9a3f5d3a 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,2 +1,6 @@
 [workspace]
-members = ["cryptoki", "cryptoki-sys"]
+members = ["cryptoki", "cryptoki-sys", "cryptoki-rustcrypto"]
+
+[patch.crates-io]
+pkcs12 = { git = "https://github.com/RustCrypto/formats.git" }
+rsa = { path = "../RSA" }
diff --git a/cryptoki-rustcrypto/Cargo.toml b/cryptoki-rustcrypto/Cargo.toml
new file mode 100644
index 00000000..8d47dc9c
--- /dev/null
+++ b/cryptoki-rustcrypto/Cargo.toml
@@ -0,0 +1,34 @@
+[package]
+name = "cryptoki-rustcrypto"
+version = "0.1.0"
+edition = "2018"
+authors = ["Contributors to the Parsec project"]
+description = "Compatibility layer from PKCS #11 to the RustCrypto ecosystem"
+readme = "README.md"
+keywords = ["pkcs11", "cryptoki", "hsm"]
+categories = ["cryptography", "hardware-support"]
+license = "Apache-2.0"
+repository = "https://github.com/parallaxsecond/rust-cryptoki"
+
+[dependencies]
+cryptoki = { path = "../cryptoki", version = "0.7.0" }
+der = "=0.8.0-rc.1"
+ecdsa = "=0.17.0-pre.9"
+#p224 = { version = "=0.14.0-pre.2", features = ["pkcs8"] }
+p256 = { version = "=0.14.0-pre.2", features = ["pkcs8"] }
+p384 = { version = "=0.14.0-pre.2", features = ["pkcs8"] }
+k256 = { version = "=0.14.0-pre.2", features = ["pkcs8"] }
+pkcs12 = { version = "=0.2.0-pre" }
+rsa = { version = "=0.10.0-pre.3", features = ["sha2"] }
+signature = { version = "=2.3.0-pre.4", features = ["derive", "digest"] }
+sha1 = { version = "=0.11.0-pre.4", features = ["oid"] }
+sha2 = { version = "=0.11.0-pre.4", features = ["oid"] }
+spki = "=0.8.0-rc.1"
+x509-cert = "=0.3.0-pre.0"
+thiserror = "1.0"
+
+[dev-dependencies]
+rand = "0.8.5"
+serial_test = "0.5.1"
+testresult = "0.2.0"
+x509-cert = { version = "=0.3.0-pre.0", features = ["builder"] }
diff --git a/cryptoki-rustcrypto/src/ecdsa.rs b/cryptoki-rustcrypto/src/ecdsa.rs
new file mode 100644
index 00000000..d9a0b0fc
--- /dev/null
+++ b/cryptoki-rustcrypto/src/ecdsa.rs
@@ -0,0 +1,278 @@
+// Copyright 2023 Contributors to the Parsec project.
+// SPDX-License-Identifier: Apache-2.0
+
+use cryptoki::{
+    mechanism::Mechanism,
+    object::{Attribute, AttributeType, KeyType, ObjectClass, ObjectHandle},
+};
+use der::{
+    asn1::{ObjectIdentifier, OctetString, OctetStringRef},
+    oid::AssociatedOid,
+    AnyRef, Decode, Encode,
+};
+use ecdsa::{
+    elliptic_curve::{
+        array::ArraySize,
+        ops::Invert,
+        point::PointCompression,
+        sec1::{FromEncodedPoint, ModulusSize, ToEncodedPoint},
+        subtle::CtOption,
+        AffinePoint, CurveArithmetic, FieldBytesSize, PublicKey, Scalar, SecretKey,
+    },
+    hazmat::DigestPrimitive,
+    PrimeCurve, Signature, SignatureSize, VerifyingKey,
+};
+use signature::{digest::Digest, DigestSigner};
+use spki::{
+    AlgorithmIdentifier, AlgorithmIdentifierRef, AssociatedAlgorithmIdentifier,
+    SignatureAlgorithmIdentifier,
+};
+use std::{convert::TryFrom, ops::Add};
+use thiserror::Error;
+
+use crate::{CryptokiImport, SessionLike};
+
+pub fn read_key<S: SessionLike, C: SignAlgorithm>(
+    session: &S,
+    template: impl Into<Vec<Attribute>>,
+) -> Result<PublicKey<C>, Error>
+where
+    FieldBytesSize<C>: ModulusSize,
+    AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>,
+{
+    let mut template = template.into();
+    template.push(Attribute::Class(ObjectClass::PUBLIC_KEY));
+    template.push(Attribute::KeyType(KeyType::EC));
+    template.push(Attribute::EcParams(C::OID.to_der().unwrap()));
+
+    let keys = session.find_objects(&template)?;
+    if let Some(public_key) = keys.first() {
+        let attribute_pub = session.get_attributes(*public_key, &[AttributeType::EcPoint])?;
+
+        let mut ec_point = None;
+        for attribute in attribute_pub {
+            match attribute {
+                Attribute::EcPoint(p) if ec_point.is_none() => {
+                    ec_point = Some(p);
+                    break;
+                }
+                _ => {}
+            }
+        }
+
+        let ec_point = ec_point.ok_or(Error::MissingAttribute(AttributeType::EcPoint))?;
+
+        // documented as "DER-encoding of ANSI X9.62 ECPoint value Q"
+        // https://docs.oasis-open.org/pkcs11/pkcs11-spec/v3.1/os/pkcs11-spec-v3.1-os.html#_Toc111203418
+        // https://www.rfc-editor.org/rfc/rfc5480#section-2.2
+        let ec_point = OctetStringRef::from_der(&ec_point).unwrap();
+
+        Ok(PublicKey::<C>::from_sec1_bytes(ec_point.as_bytes())?)
+    } else {
+        Err(Error::MissingKey)
+    }
+}
+
+impl<C> CryptokiImport for SecretKey<C>
+where
+    C: PrimeCurve + CurveArithmetic,
+    Scalar<C>: Invert<Output = CtOption<Scalar<C>>>,
+    SignatureSize<C>: ArraySize,
+
+    C: AssociatedOid,
+{
+    fn put_key<S: SessionLike>(
+        &self,
+        session: &S,
+        template: impl Into<Vec<Attribute>>,
+    ) -> cryptoki::error::Result<ObjectHandle> {
+        let mut template = template.into();
+        template.push(Attribute::Class(ObjectClass::PRIVATE_KEY));
+        template.push(Attribute::KeyType(KeyType::EC));
+        template.push(Attribute::EcParams(C::OID.to_der().unwrap()));
+        template.push(Attribute::Value(self.to_bytes().as_slice().to_vec()));
+
+        let handle = session.create_object(&template)?;
+
+        Ok(handle)
+    }
+}
+
+impl<C> CryptokiImport for PublicKey<C>
+where
+    C: PrimeCurve + CurveArithmetic + PointCompression,
+    AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>,
+    FieldBytesSize<C>: ModulusSize,
+    C: AssociatedOid,
+{
+    fn put_key<S: SessionLike>(
+        &self,
+        session: &S,
+        template: impl Into<Vec<Attribute>>,
+    ) -> cryptoki::error::Result<ObjectHandle> {
+        let mut template = template.into();
+        template.push(Attribute::Class(ObjectClass::PUBLIC_KEY));
+        template.push(Attribute::KeyType(KeyType::EC));
+        template.push(Attribute::EcParams(C::OID.to_der().unwrap()));
+        let ec_point = OctetString::new(self.to_sec1_bytes()).unwrap();
+        template.push(Attribute::EcPoint(ec_point.to_der().unwrap()));
+
+        let handle = session.create_object(&template)?;
+
+        Ok(handle)
+    }
+}
+
+#[derive(Error, Debug)]
+pub enum Error {
+    #[error("Cryptoki error: {0}")]
+    Cryptoki(#[from] cryptoki::error::Error),
+
+    #[error("Private key missing attribute: {0}")]
+    MissingAttribute(AttributeType),
+
+    #[error("Elliptic curve error: {0}")]
+    Ecdsa(#[from] ecdsa::elliptic_curve::Error),
+
+    #[error("Key not found")]
+    MissingKey,
+}
+
+pub trait SignAlgorithm: PrimeCurve + CurveArithmetic + AssociatedOid + DigestPrimitive {
+    fn sign_mechanism() -> Mechanism<'static>;
+}
+
+macro_rules! impl_sign_algorithm {
+    ($ec:ty) => {
+        impl SignAlgorithm for $ec {
+            fn sign_mechanism() -> Mechanism<'static> {
+                Mechanism::Ecdsa
+            }
+        }
+    };
+}
+
+//impl_sign_algorithm!(p224::NistP224);
+impl_sign_algorithm!(p256::NistP256);
+impl_sign_algorithm!(p384::NistP384);
+impl_sign_algorithm!(k256::Secp256k1);
+
+#[derive(signature::Signer)]
+pub struct Signer<C: SignAlgorithm, S: SessionLike> {
+    session: S,
+    private_key: ObjectHandle,
+    verifying_key: VerifyingKey<C>,
+}
+
+impl<C: SignAlgorithm, S: SessionLike> Signer<C, S>
+where
+    FieldBytesSize<C>: ModulusSize,
+    AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>,
+{
+    pub fn new(session: S, label: &[u8]) -> Result<Self, Error> {
+        // First we'll lookup a private key with that label.
+        let template = vec![
+            Attribute::Label(label.to_vec()),
+            Attribute::Class(ObjectClass::PRIVATE_KEY),
+            Attribute::KeyType(KeyType::EC),
+            Attribute::EcParams(C::OID.to_der().unwrap()),
+            Attribute::Sign(true),
+        ];
+
+        let private_key = session.find_objects(&template)?.remove(0);
+        let attribute_priv = session.get_attributes(private_key, &[AttributeType::Id])?;
+
+        // Second we'll lookup a public key with the same label/ec params/ec point
+        let mut template = vec![Attribute::Private(false), Attribute::Label(label.to_vec())];
+        let mut id = None;
+        for attribute in attribute_priv {
+            match attribute {
+                Attribute::Id(i) if id.is_none() => {
+                    template.push(Attribute::Id(i.clone()));
+                    id = Some(i);
+                }
+                _ => {}
+            }
+        }
+
+        id.ok_or(Error::MissingAttribute(AttributeType::Id))?;
+
+        let public = read_key(&session, template)?;
+        let verifying_key = public.into();
+
+        Ok(Self {
+            session,
+            private_key,
+            verifying_key,
+        })
+    }
+
+    pub fn into_session(self) -> S {
+        self.session
+    }
+}
+
+impl<C: SignAlgorithm, S: SessionLike> AssociatedAlgorithmIdentifier for Signer<C, S>
+where
+    C: AssociatedOid,
+{
+    type Params = ObjectIdentifier;
+
+    const ALGORITHM_IDENTIFIER: AlgorithmIdentifier<ObjectIdentifier> =
+        PublicKey::<C>::ALGORITHM_IDENTIFIER;
+}
+
+impl<C: SignAlgorithm, S: SessionLike> signature::Keypair for Signer<C, S> {
+    type VerifyingKey = VerifyingKey<C>;
+
+    fn verifying_key(&self) -> Self::VerifyingKey {
+        self.verifying_key
+    }
+}
+
+impl<C: SignAlgorithm, S: SessionLike> DigestSigner<C::Digest, Signature<C>> for Signer<C, S>
+where
+    <<C as ecdsa::elliptic_curve::Curve>::FieldBytesSize as Add>::Output: ArraySize,
+{
+    fn try_sign_digest(&self, digest: C::Digest) -> Result<Signature<C>, signature::Error> {
+        let msg = digest.finalize();
+
+        let bytes = self
+            .session
+            .sign(&C::sign_mechanism(), self.private_key, &msg)
+            .map_err(Error::Cryptoki)
+            .map_err(Box::new)
+            .map_err(signature::Error::from_source)?;
+
+        let signature = Signature::try_from(bytes.as_slice())?;
+
+        Ok(signature)
+    }
+}
+
+impl<C: SignAlgorithm, S: SessionLike> SignatureAlgorithmIdentifier for Signer<C, S>
+where
+    AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>,
+    FieldBytesSize<C>: ModulusSize,
+    Signature<C>: AssociatedAlgorithmIdentifier<Params = AnyRef<'static>>,
+{
+    type Params = AnyRef<'static>;
+
+    const SIGNATURE_ALGORITHM_IDENTIFIER: AlgorithmIdentifierRef<'static> =
+        Signature::<C>::ALGORITHM_IDENTIFIER;
+}
+
+impl<C: SignAlgorithm, S: SessionLike> DigestSigner<C::Digest, ecdsa::der::Signature<C>>
+    for Signer<C, S>
+where
+    ecdsa::der::MaxSize<C>: ArraySize,
+    <FieldBytesSize<C> as Add>::Output: Add<ecdsa::der::MaxOverhead> + ArraySize,
+    Self: DigestSigner<C::Digest, Signature<C>>,
+{
+    fn try_sign_digest(
+        &self,
+        digest: C::Digest,
+    ) -> Result<ecdsa::der::Signature<C>, signature::Error> {
+        DigestSigner::<C::Digest, Signature<C>>::try_sign_digest(self, digest).map(Into::into)
+    }
+}
diff --git a/cryptoki-rustcrypto/src/lib.rs b/cryptoki-rustcrypto/src/lib.rs
new file mode 100644
index 00000000..de5a3d47
--- /dev/null
+++ b/cryptoki-rustcrypto/src/lib.rs
@@ -0,0 +1,126 @@
+// Copyright 2023 Contributors to the Parsec project.
+// SPDX-License-Identifier: Apache-2.0
+
+use cryptoki::{
+    error::Result,
+    mechanism::Mechanism,
+    object::{Attribute, AttributeType, ObjectHandle},
+    session::{Session, UserType},
+    types::AuthPin,
+};
+
+pub use cryptoki;
+
+pub mod ecdsa;
+pub mod rng;
+pub mod rsa;
+pub mod x509;
+
+pub trait SessionLike {
+    fn create_object(&self, template: &[Attribute]) -> Result<ObjectHandle>;
+    fn find_objects(&self, template: &[Attribute]) -> Result<Vec<ObjectHandle>>;
+    fn get_attributes(
+        &self,
+        object: ObjectHandle,
+        attributes: &[AttributeType],
+    ) -> Result<Vec<Attribute>>;
+    fn update_attributes(&self, object: ObjectHandle, template: &[Attribute]) -> Result<()>;
+    fn sign(&self, mechanism: &Mechanism, key: ObjectHandle, data: &[u8]) -> Result<Vec<u8>>;
+    fn generate_random_slice(&self, random_data: &mut [u8]) -> Result<()>;
+    fn wrap_key(
+        &self,
+        mechanism: &Mechanism,
+        wrapping_key: ObjectHandle,
+        key: ObjectHandle,
+    ) -> Result<Vec<u8>>;
+    fn login(&self, user_type: UserType, pin: Option<&AuthPin>) -> Result<()>;
+    fn logout(&self) -> Result<()>;
+}
+
+impl SessionLike for Session {
+    fn create_object(&self, template: &[Attribute]) -> Result<ObjectHandle> {
+        Session::create_object(self, template)
+    }
+    fn find_objects(&self, template: &[Attribute]) -> Result<Vec<ObjectHandle>> {
+        Session::find_objects(self, template)
+    }
+    fn get_attributes(
+        &self,
+        object: ObjectHandle,
+        attributes: &[AttributeType],
+    ) -> Result<Vec<Attribute>> {
+        Session::get_attributes(self, object, attributes)
+    }
+    fn update_attributes(&self, object: ObjectHandle, template: &[Attribute]) -> Result<()> {
+        Session::update_attributes(self, object, template)
+    }
+
+    fn sign(&self, mechanism: &Mechanism, key: ObjectHandle, data: &[u8]) -> Result<Vec<u8>> {
+        Session::sign(self, mechanism, key, data)
+    }
+    fn generate_random_slice(&self, random_data: &mut [u8]) -> Result<()> {
+        Session::generate_random_slice(self, random_data)
+    }
+    fn wrap_key(
+        &self,
+        mechanism: &Mechanism,
+        wrapping_key: ObjectHandle,
+        key: ObjectHandle,
+    ) -> Result<Vec<u8>> {
+        Session::wrap_key(self, mechanism, wrapping_key, key)
+    }
+    fn login(&self, user_type: UserType, pin: Option<&AuthPin>) -> Result<()> {
+        Session::login(self, user_type, pin)
+    }
+    fn logout(&self) -> Result<()> {
+        Session::logout(self)
+    }
+}
+
+impl<'s> SessionLike for &'s Session {
+    fn create_object(&self, template: &[Attribute]) -> Result<ObjectHandle> {
+        Session::create_object(self, template)
+    }
+    fn find_objects(&self, template: &[Attribute]) -> Result<Vec<ObjectHandle>> {
+        Session::find_objects(self, template)
+    }
+    fn get_attributes(
+        &self,
+        object: ObjectHandle,
+        attributes: &[AttributeType],
+    ) -> Result<Vec<Attribute>> {
+        Session::get_attributes(self, object, attributes)
+    }
+    fn update_attributes(&self, object: ObjectHandle, template: &[Attribute]) -> Result<()> {
+        Session::update_attributes(self, object, template)
+    }
+
+    fn sign(&self, mechanism: &Mechanism, key: ObjectHandle, data: &[u8]) -> Result<Vec<u8>> {
+        Session::sign(self, mechanism, key, data)
+    }
+    fn generate_random_slice(&self, random_data: &mut [u8]) -> Result<()> {
+        Session::generate_random_slice(self, random_data)
+    }
+    fn wrap_key(
+        &self,
+        mechanism: &Mechanism,
+        wrapping_key: ObjectHandle,
+        key: ObjectHandle,
+    ) -> Result<Vec<u8>> {
+        Session::wrap_key(self, mechanism, wrapping_key, key)
+    }
+    fn login(&self, user_type: UserType, pin: Option<&AuthPin>) -> Result<()> {
+        Session::login(self, user_type, pin)
+    }
+    fn logout(&self) -> Result<()> {
+        Session::logout(self)
+    }
+}
+
+pub trait CryptokiImport {
+    fn put_key<S: SessionLike>(
+        &self,
+        session: &S,
+        template: impl Into<Vec<Attribute>>,
+    ) -> Result<ObjectHandle>;
+}
diff --git a/cryptoki-rustcrypto/src/rng.rs b/cryptoki-rustcrypto/src/rng.rs
new file mode 100644
index 00000000..551f6415
--- /dev/null
+++ b/cryptoki-rustcrypto/src/rng.rs
@@ -0,0 +1,55 @@
+// Copyright 2023 Contributors to the Parsec project.
+// SPDX-License-Identifier: Apache-2.0
+
+use signature::rand_core::{CryptoRng, Error as RndError, RngCore};
+use thiserror::Error;
+
+use crate::SessionLike;
+
+#[derive(Debug, Error)]
+pub enum Error {}
+
+/// [`Rng`] is a PKCS#11-backed CSPRNG.
+///
+/// ## Panics
+///
+/// The [`RngCore::fill_bytes`] implementation may panic if the provider was
+/// unable to return enough bytes.
+pub struct Rng<S: SessionLike>(S);
+
+// TODO(baloo): check for CKF_RNG bit flag (CK_TOKEN_INFO struct -> flags)
+impl<S: SessionLike> Rng<S> {
+    pub fn new(session: S) -> Result<Self, Error> {
+        Ok(Self(session))
+    }
+}
+
+macro_rules! impl_next_uint {
+    ($self:ident, $u:ty) => {{
+        let mut buf = <$u>::MIN.to_be_bytes();
+        $self.fill_bytes(&mut buf[..]);
+
+        <$u>::from_be_bytes(buf)
+    }};
+}
+
+impl<S: SessionLike> RngCore for Rng<S> {
+    fn next_u32(&mut self) -> u32 {
+        impl_next_uint!(self, u32)
+    }
+
+    fn next_u64(&mut self) -> u64 {
+        impl_next_uint!(self, u64)
+    }
+
+    fn fill_bytes(&mut self, dest: &mut [u8]) {
+        self.try_fill_bytes(dest)
+            .expect("Cryptoki provider failed to generate random");
+    }
+
+    fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), RndError> {
+        self.0.generate_random_slice(dest).map_err(RndError::new)
+    }
+}
+
+impl<S: SessionLike> CryptoRng for Rng<S> {}
diff --git a/cryptoki-rustcrypto/src/rsa/mod.rs b/cryptoki-rustcrypto/src/rsa/mod.rs
new file mode 100644
index 00000000..ee6d0c6b
--- /dev/null
+++ b/cryptoki-rustcrypto/src/rsa/mod.rs
@@ -0,0 +1,146 @@
+// Copyright 2023 Contributors to the Parsec project.
+// SPDX-License-Identifier: Apache-2.0
+
+use cryptoki::{
+    mechanism::{
+        rsa::{PkcsMgfType, PkcsPssParams},
+        Mechanism, MechanismType,
+    },
+    object::{Attribute, AttributeType, KeyType, ObjectClass, ObjectHandle},
+};
+use der::oid::AssociatedOid;
+use rsa::{traits::PublicKeyParts, BigUint, RsaPublicKey};
+use signature::digest::Digest;
+use std::convert::TryInto;
+use thiserror::Error;
+
+use crate::{CryptokiImport, SessionLike};
+
+pub mod pkcs1v15;
+pub mod pss;
+
+pub fn read_key<S: SessionLike>(
+    session: &S,
+    template: impl Into<Vec<Attribute>>,
+) -> Result<RsaPublicKey, Error> {
+    let mut template: Vec<Attribute> = template.into();
+    template.push(Attribute::Class(ObjectClass::PUBLIC_KEY));
+    template.push(Attribute::KeyType(KeyType::RSA));
+
+    let keys = session.find_objects(&template)?;
+    if let Some(key) = keys.first() {
+        let attribute_priv = session.get_attributes(
+            *key,
+            &[AttributeType::Modulus, AttributeType::PublicExponent],
+        )?;
+
+        let mut modulus = None;
+        let mut public_exponent = None;
+
+        for attribute in attribute_priv {
+            match attribute {
+                Attribute::Modulus(m) if modulus.is_none() => {
+                    modulus = Some(m.clone());
+                }
+                Attribute::PublicExponent(e) if public_exponent.is_none() => {
+                    public_exponent = Some(e.clone());
+                }
+                _ => {}
+            }
+        }
+
+        let modulus = modulus
+            .ok_or(Error::MissingAttribute(AttributeType::Modulus))
+            .map(|v| BigUint::from_bytes_be(v.as_slice()))?;
+        let public_exponent = public_exponent
+            .ok_or(Error::MissingAttribute(AttributeType::PublicExponent))
+            .map(|v| BigUint::from_bytes_be(v.as_slice()))?;
+
+        Ok(RsaPublicKey::new(modulus, public_exponent)?)
+    } else {
+        Err(Error::MissingKey)
+    }
+}
+
+#[derive(Debug, Error)]
+pub enum Error {
+    #[error("Cryptoki error: {0}")]
+    Cryptoki(#[from] cryptoki::error::Error),
+
+    #[error("Private key missing attribute: {0}")]
+    MissingAttribute(AttributeType),
+
+    #[error("RSA error: {0}")]
+    Rsa(#[from] rsa::Error),
+
+    #[error("Key not found")]
+    MissingKey,
+}
+
+pub trait DigestSigning: Digest + AssociatedOid {
+    fn pkcs_mechanism() -> Mechanism<'static>;
+
+    fn pss_mechanism() -> Mechanism<'static>;
+}
+
+macro_rules! impl_digest_signing {
+    ($d:ty, $pkcs_mech:ident, $pss_mech:ident, $mt:ident, $mgf:ident) => {
+        impl DigestSigning for $d {
+            fn pkcs_mechanism() -> Mechanism<'static> {
+                Mechanism::$pkcs_mech
+            }
+
+            fn pss_mechanism() -> Mechanism<'static> {
+                Mechanism::$pss_mech(PkcsPssParams {
+                    hash_alg: MechanismType::$mt,
+                    mgf: PkcsMgfType::$mgf,
+                    // Safety:
+                    // the output_size of an hash function will not go over 2^32,
+                    // this unwrap is safe.
+                    s_len: Self::output_size().try_into().unwrap(),
+                })
+            }
+        }
+    };
+}
+
+impl_digest_signing!(sha1::Sha1, Sha1RsaPkcs, Sha1RsaPkcsPss, SHA1, MGF1_SHA1);
+impl_digest_signing!(
+    sha2::Sha256,
+    Sha256RsaPkcs,
+    Sha256RsaPkcsPss,
+    SHA256,
+    MGF1_SHA256
+);
+impl_digest_signing!(
+    sha2::Sha384,
+    Sha384RsaPkcs,
+    Sha384RsaPkcsPss,
+    SHA384,
+    MGF1_SHA384
+);
+impl_digest_signing!(
+    sha2::Sha512,
+    Sha512RsaPkcs,
+    Sha512RsaPkcsPss,
+    SHA512,
+    MGF1_SHA512
+);
+
+impl CryptokiImport for RsaPublicKey {
+    fn put_key<S: SessionLike>(
+        &self,
+        session: &S,
+        template: impl Into<Vec<Attribute>>,
+    ) -> cryptoki::error::Result<ObjectHandle> {
+        let mut template = template.into();
+        template.push(Attribute::Class(ObjectClass::PUBLIC_KEY));
+        template.push(Attribute::KeyType(KeyType::RSA));
+        template.push(Attribute::Modulus(self.n().to_bytes_be()));
+        template.push(Attribute::PublicExponent(self.e().to_bytes_be()));
+
+        let handle = session.create_object(&template)?;
+
+        Ok(handle)
+    }
+}
diff --git a/cryptoki-rustcrypto/src/rsa/pkcs1v15.rs b/cryptoki-rustcrypto/src/rsa/pkcs1v15.rs
new file mode 100644
index 00000000..71f288c2
--- /dev/null
+++ b/cryptoki-rustcrypto/src/rsa/pkcs1v15.rs
@@ -0,0 +1,150 @@
+// Copyright 2023 Contributors to the Parsec project.
+// SPDX-License-Identifier: Apache-2.0
+
+use cryptoki::{
+    mechanism::Mechanism,
+    object::{Attribute, AttributeType, KeyType, ObjectClass, ObjectHandle},
+};
+use rsa::pkcs1v15::{RsaSignatureAssociatedOid, Signature, VerifyingKey};
+use spki::{
+    der::{asn1::OctetString, oid::AssociatedOid, referenced::RefToOwned, AnyRef, Encode},
+    AlgorithmIdentifier, AlgorithmIdentifierRef, AssociatedAlgorithmIdentifier,
+    SignatureAlgorithmIdentifier,
+};
+use std::convert::TryFrom;
+
+use super::{read_key, DigestSigning, Error};
+use crate::SessionLike;
+
+pub struct Signer<D: DigestSigning, S: SessionLike> {
+    session: S,
+    private_key: ObjectHandle,
+    verifying_key: VerifyingKey<D>,
+}
+
+impl<D: DigestSigning, S: SessionLike> Signer<D, S> {
+    pub fn new(session: S, label: &[u8]) -> Result<Self, Error> {
+        // First we'll lookup a private key with that label.
+        let template = vec![
+            Attribute::Token(true),
+            Attribute::Private(true),
+            Attribute::Label(label.to_vec()),
+            Attribute::Class(ObjectClass::PRIVATE_KEY),
+            Attribute::KeyType(KeyType::RSA),
+            Attribute::Sign(true),
+        ];
+
+        let private_key = session.find_objects(&template)?.remove(0);
+        let attribute_priv = session.get_attributes(
+            private_key,
+            &[AttributeType::Modulus, AttributeType::PublicExponent],
+        )?;
+
+        // Second we'll lookup a public key with the same label/modulus/public exponent
+        let mut template = vec![Attribute::Private(false), Attribute::Label(label.to_vec())];
+        let mut modulus = None;
+        let mut public_exponent = None;
+        for attribute in attribute_priv {
+            match attribute {
+                Attribute::Modulus(m) if modulus.is_none() => {
+                    modulus = Some(m.clone());
+                    template.push(Attribute::Modulus(m));
+                }
+                Attribute::PublicExponent(e) if public_exponent.is_none() => {
+                    public_exponent = Some(e.clone());
+                    template.push(Attribute::PublicExponent(e));
+                }
+                _ => {}
+            }
+        }
+
+        let public_key = read_key(&session, template)?;
+
+        let verifying_key = VerifyingKey::new(public_key);
+
+        Ok(Self {
+            session,
+            private_key,
+            verifying_key,
+        })
+    }
+
+    pub fn into_session(self) -> S {
+        self.session
+    }
+}
+
+impl<D, S> AssociatedAlgorithmIdentifier for Signer<D, S>
+where
+    D: DigestSigning,
+    S: SessionLike,
+{
+    type Params = <VerifyingKey<D> as AssociatedAlgorithmIdentifier>::Params;
+    const ALGORITHM_IDENTIFIER: AlgorithmIdentifier<Self::Params> =
+        <VerifyingKey<D> as AssociatedAlgorithmIdentifier>::ALGORITHM_IDENTIFIER;
+}
+
+impl<D: DigestSigning, S: SessionLike> signature::Keypair for Signer<D, S> {
+    type VerifyingKey = VerifyingKey<D>;
+
+    fn verifying_key(&self) -> Self::VerifyingKey {
+        self.verifying_key.clone()
+    }
+}
+
+impl<D: DigestSigning, S: SessionLike> signature::Signer<Signature> for Signer<D, S> {
+    fn try_sign(&self, msg: &[u8]) -> Result<Signature, signature::Error> {
+        let bytes = self
+            .session
+            .sign(&D::pkcs_mechanism(), self.private_key, msg)
+            .map_err(Error::Cryptoki)
+            .map_err(Box::new)
+            .map_err(signature::Error::from_source)?;
+
+        let signature = Signature::try_from(bytes.as_slice())?;
+
+        Ok(signature)
+    }
+}
+
+impl<D, S> SignatureAlgorithmIdentifier for Signer<D, S>
+where
+    S: SessionLike,
+    D: DigestSigning + RsaSignatureAssociatedOid,
+{
+    type Params = <VerifyingKey<D> as SignatureAlgorithmIdentifier>::Params;
+
+    const SIGNATURE_ALGORITHM_IDENTIFIER: AlgorithmIdentifier<Self::Params> =
+        <VerifyingKey<D> as SignatureAlgorithmIdentifier>::SIGNATURE_ALGORITHM_IDENTIFIER;
+}
+
+impl<D, S> signature::hazmat::PrehashSigner<Signature> for Signer<D, S>
+where
+    S: SessionLike,
+    D: DigestSigning + RsaSignatureAssociatedOid,
+{
+    fn sign_prehash(&self, prehash: &[u8]) -> Result<Signature, signature::Error> {
+        let payload = pkcs12::DigestInfo {
+            algorithm: (AlgorithmIdentifierRef {
+                oid: <D as AssociatedOid>::OID,
+                parameters: Some(AnyRef::NULL),
+            })
+            .ref_to_owned(),
+            digest: OctetString::new(prehash).unwrap(),
+        };
+
+        let msg = payload.to_der().unwrap();
+        println!("msg: {msg:x?}");
+
+        let bytes = self
+            .session
+            .sign(&Mechanism::RsaPkcs, self.private_key, &msg)
+            .map_err(Error::Cryptoki)
+            .map_err(Box::new)
+            .map_err(signature::Error::from_source)?;
+
+        let signature = Signature::try_from(bytes.as_slice())?;
+
+        Ok(signature)
+    }
+}
diff --git a/cryptoki-rustcrypto/src/rsa/pss.rs b/cryptoki-rustcrypto/src/rsa/pss.rs
new file mode 100644
index 00000000..303248f8
--- /dev/null
+++ b/cryptoki-rustcrypto/src/rsa/pss.rs
@@ -0,0 +1,110 @@
+// Copyright 2023 Contributors to the Parsec project.
+// SPDX-License-Identifier: Apache-2.0
+
+use cryptoki::object::{Attribute, AttributeType, KeyType, ObjectClass, ObjectHandle};
+use der::AnyRef;
+use rsa::{
+    pkcs1,
+    pkcs8::{self},
+    pss::{get_default_pss_signature_algo_id, Signature, VerifyingKey},
+};
+use spki::{
+    AlgorithmIdentifierOwned, AlgorithmIdentifierRef, AssociatedAlgorithmIdentifier,
+    DynSignatureAlgorithmIdentifier,
+};
+use std::convert::TryFrom;
+
+use super::{read_key, DigestSigning, Error};
+use crate::SessionLike;
+
+pub struct Signer<D: DigestSigning, S: SessionLike> {
+    session: S,
+    private_key: ObjectHandle,
+    verifying_key: VerifyingKey<D>,
+}
+
+impl<D: DigestSigning, S: SessionLike> Signer<D, S> {
+    pub fn new(session: S, label: &[u8]) -> Result<Self, Error> {
+        // First we'll lookup a private key with that label.
+        let template = vec![
+            Attribute::Token(true),
+            Attribute::Private(true),
+            Attribute::Label(label.to_vec()),
+            Attribute::Class(ObjectClass::PRIVATE_KEY),
+            Attribute::KeyType(KeyType::RSA),
+            Attribute::Sign(true),
+        ];
+
+        let private_key = session.find_objects(&template)?.remove(0);
+        let attribute_priv = session.get_attributes(
+            private_key,
+            &[AttributeType::Modulus, AttributeType::PublicExponent],
+        )?;
+
+        // Second we'll lookup a public key with the same label/modulus/public exponent
+        let mut template = vec![Attribute::Private(false), Attribute::Label(label.to_vec())];
+        let mut modulus = None;
+        let mut public_exponent = None;
+        for attribute in attribute_priv {
+            match attribute {
+                Attribute::Modulus(m) if modulus.is_none() => {
+                    modulus = Some(m.clone());
+                    template.push(Attribute::Modulus(m));
+                }
+                Attribute::PublicExponent(e) if public_exponent.is_none() => {
+                    public_exponent = Some(e.clone());
+                    template.push(Attribute::PublicExponent(e));
+                }
+                _ => {}
+            }
+        }
+
+        let public_key = read_key(&session, template)?;
+
+        let verifying_key = VerifyingKey::new(public_key);
+
+        Ok(Self {
+            session,
+            private_key,
+            verifying_key,
+        })
+    }
+
+    pub fn into_session(self) -> S {
+        self.session
+    }
+}
+
+impl<D: DigestSigning, S: SessionLike> AssociatedAlgorithmIdentifier for Signer<D, S> {
+    type Params = AnyRef<'static>;
+    const ALGORITHM_IDENTIFIER: AlgorithmIdentifierRef<'static> = pkcs1::ALGORITHM_ID;
+}
+
+impl<D: DigestSigning, S: SessionLike> signature::Keypair for Signer<D, S> {
+    type VerifyingKey = VerifyingKey<D>;
+
+    fn verifying_key(&self) -> Self::VerifyingKey {
+        self.verifying_key.clone()
+    }
+}
+
+impl<D: DigestSigning, S: SessionLike> signature::Signer<Signature> for Signer<D, S> {
+    fn try_sign(&self, msg: &[u8]) -> Result<Signature, signature::Error> {
+        let bytes = self
+            .session
+            .sign(&D::pss_mechanism(), self.private_key, msg)
+            .map_err(Error::Cryptoki)
+            .map_err(Box::new)
+            .map_err(signature::Error::from_source)?;
+
+        let signature = Signature::try_from(bytes.as_slice())?;
+
+        Ok(signature)
+    }
+}
+
+impl<D: DigestSigning, S: SessionLike> DynSignatureAlgorithmIdentifier for Signer<D, S> {
+    fn signature_algorithm_identifier(&self) -> pkcs8::spki::Result<AlgorithmIdentifierOwned> {
+        get_default_pss_signature_algo_id::<D>()
+    }
+}
diff --git a/cryptoki-rustcrypto/src/x509.rs b/cryptoki-rustcrypto/src/x509.rs
new file mode 100644
index 00000000..e3cf4d65
--- /dev/null
+++ b/cryptoki-rustcrypto/src/x509.rs
@@ -0,0 +1,97 @@
+// Copyright 2023 Contributors to the Parsec project.
+// SPDX-License-Identifier: Apache-2.0
+
+use cryptoki::object::{Attribute, AttributeType, CertificateType, ObjectClass, ObjectHandle};
+use thiserror::Error;
+use x509_cert::{
+    certificate::{CertificateInner, Profile},
+    der::{Decode, Encode},
+};
+
+use crate::SessionLike;
+
+#[derive(Debug, Error)]
+pub enum Error {
+    #[error("Cryptoki error: {0}")]
+    Cryptoki(#[from] cryptoki::error::Error),
+
+    #[error("Missing attribute: {0}")]
+    MissingAttribute(AttributeType),
+
+    #[error(transparent)]
+    Der(#[from] x509_cert::der::Error),
+
+    #[error("No such certificate found")]
+    MissingCert,
+}
+
+pub trait CertPkcs11 {
+    fn pkcs11_store<S: SessionLike, T: Into<Vec<Attribute>>>(
+        &self,
+        session: &S,
+        base_template: T,
+    ) -> Result<ObjectHandle, Error>;
+
+    fn pkcs11_load<S: SessionLike, T: Into<Vec<Attribute>>>(
+        session: &S,
+        template: T,
+    ) -> Result<Self, Error>
+    where
+        Self: Sized;
+}
+
+impl<P> CertPkcs11 for CertificateInner<P>
+where
+    P: Profile,
+{
+    fn pkcs11_store<S: SessionLike, T: Into<Vec<Attribute>>>(
+        &self,
+        session: &S,
+        base_template: T,
+    ) -> Result<ObjectHandle, Error> {
+        let mut template = base_template.into();
+        template.push(Attribute::Class(ObjectClass::CERTIFICATE));
+        template.push(Attribute::CertificateType(CertificateType::X_509));
+        template.push(Attribute::Token(true));
+        template.push(Attribute::Value(self.to_der()?));
+        if !self.tbs_certificate().subject().is_empty() {
+            template.push(Attribute::Subject(
+                self.tbs_certificate().subject().to_der()?,
+            ));
+        }
+
+        Ok(session.create_object(&template)?)
+    }
+
+    fn pkcs11_load<S: SessionLike, T: Into<Vec<Attribute>>>(
+        session: &S,
+        template: T,
+    ) -> Result<Self, Error> {
+        let mut template = template.into();
+        template.push(Attribute::Class(ObjectClass::CERTIFICATE));
+        template.push(Attribute::CertificateType(CertificateType::X_509));
+
+        let certs = session.find_objects(&template)?;
+        if let Some(cert) = certs.first() {
+            let attributes = session.get_attributes(*cert, &[AttributeType::Value])?;
+
+            let mut value = None;
+            for attribute in attributes {
+                match attribute {
+                    Attribute::Value(v) if value.is_none() => {
+                        value = Some(v);
+                    }
+                    _ => {}
+                }
+            }
+
+            let value = value.ok_or(Error::MissingAttribute(AttributeType::Value))?;
+
+            let cert = Self::from_der(&value)?;
+
+            Ok(cert)
+        } else {
+            Err(Error::MissingCert)
+        }
+    }
+}
diff --git a/cryptoki-rustcrypto/tests/common.rs b/cryptoki-rustcrypto/tests/common.rs
new file mode 100644
index 00000000..9da0703b
--- /dev/null
+++ b/cryptoki-rustcrypto/tests/common.rs
@@ -0,0 +1,44 @@
+// Copyright 2023 Contributors to the Parsec project.
+// SPDX-License-Identifier: Apache-2.0
+
+use cryptoki::context::{CInitializeArgs, Pkcs11};
+use cryptoki::session::UserType;
+use cryptoki::slot::Slot;
+use cryptoki::types::AuthPin;
+use std::env;
+
+// The default user pin
+pub static USER_PIN: &str = "fedcba";
+// The default SO pin
+pub static SO_PIN: &str = "abcdef";
+
+pub fn get_pkcs11() -> Pkcs11 {
+    Pkcs11::new(
+        env::var("PKCS11_SOFTHSM2_MODULE")
+            .unwrap_or_else(|_| "/usr/local/lib/softhsm/libsofthsm2.so".to_string()),
+    )
+    .unwrap()
+}
+
+pub fn init_pins() -> (Pkcs11, Slot) {
+    let pkcs11 = get_pkcs11();
+
+    // initialize the library
+    pkcs11.initialize(CInitializeArgs::OsThreads).unwrap();
+
+    // find a slot, get the first one
+    let slot = pkcs11.get_slots_with_token().unwrap().remove(0);
+
+    let so_pin = AuthPin::new(SO_PIN.into());
+    pkcs11.init_token(slot, &so_pin, "Test Token").unwrap();
+
+    {
+        // open a session
+        let session = pkcs11.open_rw_session(slot).unwrap();
+        // log in the session
+        session.login(UserType::So, Some(&so_pin)).unwrap();
+        session.init_pin(&AuthPin::new(USER_PIN.into())).unwrap();
+    }
+
+    (pkcs11, slot)
+}
diff --git a/cryptoki-rustcrypto/tests/ecdsa.rs b/cryptoki-rustcrypto/tests/ecdsa.rs
new file mode 100644
index 00000000..35d35fdf
--- /dev/null
+++ b/cryptoki-rustcrypto/tests/ecdsa.rs
@@ -0,0 +1,118 @@
+// Copyright 2023 Contributors to the Parsec project.
+// SPDX-License-Identifier: Apache-2.0
+
+mod common;
+
+use crate::common::USER_PIN;
+use common::init_pins;
+use cryptoki::{
+    mechanism::Mechanism,
+    object::{Attribute, KeyType},
+    session::UserType,
+    types::AuthPin,
+};
+use cryptoki_rustcrypto::{ecdsa, CryptokiImport};
+use der::Encode;
+use p256::pkcs8::AssociatedOid;
+use serial_test::serial;
+use signature::{Keypair, Signer, Verifier};
+use testresult::TestResult;
+
+#[test]
+#[serial]
+fn sign_verify() -> TestResult {
+    let (pkcs11, slot) = init_pins();
+
+    // open a session
+    let session = pkcs11.open_rw_session(slot)?;
+
+    // log in the session
+    session.login(UserType::User, Some(&AuthPin::new(USER_PIN.into())))?;
+
+    // get mechanism
+    let mechanism = Mechanism::EccKeyPairGen;
+
+    let secp256r1_oid: Vec<u8> = p256::NistP256::OID.to_der().unwrap();
+    println!("oid: {:x?}", secp256r1_oid);
+
+    let label = b"demo-signer";
+
+    // pub key template
+    let pub_key_template = vec![
+        Attribute::Token(false),
+        Attribute::Private(false),
+        Attribute::KeyType(KeyType::EC),
+        Attribute::Verify(true),
+        Attribute::EcParams(secp256r1_oid.clone()),
+        Attribute::Label(label.to_vec()),
+    ];
+
+    // priv key template
+    let priv_key_template = vec![
+        Attribute::Token(false),
+        Attribute::Private(true),
+        Attribute::Sign(true),
+        Attribute::Label(label.to_vec()),
+    ];
+
+    // generate a key pair
+    let (public, private) =
+        session.generate_key_pair(&mechanism, &pub_key_template, &priv_key_template)?;
+
+    // data to sign
+    let data = [0xFF, 0x55, 0xDD];
+
+    let signer =
+        ecdsa::Signer::<p256::NistP256, _>::new(&session, label).expect("Lookup keys from HSM");
+
+    let signature: p256::ecdsa::Signature = signer.sign(&data);
+
+    let verifying_key = signer.verifying_key();
+    verifying_key.verify(&data, &signature)?;
+
+    // delete keys
+    session.destroy_object(public)?;
+    session.destroy_object(private)?;
+
+    Ok(())
+}
+
+#[test]
+#[serial]
+fn test_import() -> TestResult {
+    let (pkcs11, slot) = init_pins();
+
+    // open a session
+    let session = pkcs11.open_rw_session(slot)?;
+
+    // log in the session
+    session.login(UserType::User, Some(&AuthPin::new(USER_PIN.into())))?;
+
+    let mut rng = rand::thread_rng();
+    let private = p256::ecdsa::SigningKey::random(&mut rng);
+
+    let label = b"demo-import";
+
+    let template = vec![Attribute::Token(false), Attribute::Label(label.to_vec())];
+
+    let private_handle = p256::SecretKey::from(&private).put_key(&session, template.clone())?;
+    let public_handle =
+        p256::PublicKey::from(private.verifying_key()).put_key(&session, template)?;
+
+    // data to sign
+    let data = [0xFF, 0x55, 0xDD];
+
+    let signer =
+        ecdsa::Signer::<p256::NistP256, _>::new(&session, label).expect("Lookup keys from HSM");
+
+    let signature: p256::ecdsa::Signature = signer.sign(&data);
+
+    let verifying_key = private.verifying_key();
+    verifying_key.verify(&data, &signature)?;
+
+    // delete keys
+    session.destroy_object(private_handle)?;
+    session.destroy_object(public_handle)?;
+
+    Ok(())
+}
diff --git a/cryptoki-rustcrypto/tests/rng.rs b/cryptoki-rustcrypto/tests/rng.rs
new file mode 100644
index 00000000..38184d6a
--- /dev/null
+++ b/cryptoki-rustcrypto/tests/rng.rs
@@ -0,0 +1,57 @@
+// Copyright 2023 Contributors to the Parsec project.
+// SPDX-License-Identifier: Apache-2.0
+
+mod common;
+
+use crate::common::USER_PIN;
+use common::init_pins;
+use cryptoki::{session::UserType, types::AuthPin};
+use cryptoki_rustcrypto::rng::Rng;
+use serial_test::serial;
+use signature::rand_core::{CryptoRngCore, RngCore};
+use testresult::TestResult;
+
+// This test is meant to ensure we provide [`rand_core::CryptoRngCore`].
+// This is the trait consumers will use throughout the RustCrypto ecosystem
+// to express interest in a CSPRNG.
+#[test]
+#[serial]
+fn ensure_crypto_rng_core() -> TestResult {
+    fn just_making_sure<R: CryptoRngCore>(_r: &mut R) {
+        // Hi! just making sure you provide a CSPRNG.
+    }
+    let (pkcs11, slot) = init_pins();
+
+    // open a session
+    let session = pkcs11.open_rw_session(slot)?;
+
+    // log in the session
+    session.login(UserType::User, Some(&AuthPin::new(USER_PIN.into())))?;
+
+    let mut rng = Rng::new(session).unwrap();
+    just_making_sure(&mut rng);
+
+    Ok(())
+}
+
+#[test]
+#[serial]
+fn generate_random() -> TestResult {
+    let (pkcs11, slot) = init_pins();
+
+    // open a session
+    let session = pkcs11.open_rw_session(slot)?;
+
+    // log in the session
+    session.login(UserType::User, Some(&AuthPin::new(USER_PIN.into())))?;
+
+    let mut rng = Rng::new(session).unwrap();
+    rng.next_u32();
+    rng.next_u64();
+
+    let mut buf = vec![0; 123];
+    rng.fill_bytes(&mut buf);
+    rng.try_fill_bytes(&mut buf).unwrap();
+
+    Ok(())
+}
diff --git a/cryptoki-rustcrypto/tests/rsa.rs b/cryptoki-rustcrypto/tests/rsa.rs
new file mode 100644
index 00000000..0ca285b9
--- /dev/null
+++ b/cryptoki-rustcrypto/tests/rsa.rs
@@ -0,0 +1,175 @@
+// Copyright 2023 Contributors to the Parsec project.
+// SPDX-License-Identifier: Apache-2.0
+
+mod common;
+
+use crate::common::USER_PIN;
+use common::init_pins;
+use cryptoki::{mechanism::Mechanism, object::Attribute, session::UserType, types::AuthPin};
+use cryptoki_rustcrypto::rsa::{pkcs1v15, pss};
+use rand::{thread_rng, RngCore};
+use serial_test::serial;
+use sha2::{Digest, Sha256};
+use signature::{hazmat::PrehashSigner, Keypair, Signer, Verifier};
+use testresult::TestResult;
+
+#[test]
+#[serial]
+fn pkcs1v15_sign_verify() -> TestResult {
+    let (pkcs11, slot) = init_pins();
+
+    // open a session
+    let session = pkcs11.open_rw_session(slot)?;
+
+    // log in the session
+    session.login(UserType::User, Some(&AuthPin::new(USER_PIN.into())))?;
+
+    // get mechanism
+    let mechanism = Mechanism::RsaPkcsKeyPairGen;
+
+    let public_exponent: Vec<u8> = vec![0x01, 0x00, 0x01];
+    let modulus_bits = 1024;
+
+    let label = b"demo-signer";
+
+    // pub key template
+    let pub_key_template = vec![
+        Attribute::Token(true),
+        Attribute::Private(false),
+        Attribute::Label(label.to_vec()),
+        Attribute::PublicExponent(public_exponent),
+        Attribute::ModulusBits(modulus_bits.into()),
+    ];
+
+    // priv key template
+    let priv_key_template = vec![Attribute::Token(true), Attribute::Label(label.to_vec())];
+
+    // generate a key pair
+    let (public, private) =
+        session.generate_key_pair(&mechanism, &pub_key_template, &priv_key_template)?;
+
+    // data to sign
+    let data = [0xFF, 0x55, 0xDD];
+
+    let signer = pkcs1v15::Signer::<Sha256, _>::new(&session, label).expect("Lookup keys from HSM");
+
+    let signature = signer.sign(&data);
+
+    let verifying_key = signer.verifying_key();
+    verifying_key.verify(&data, &signature)?;
+
+    // delete keys
+    session.destroy_object(public)?;
+    session.destroy_object(private)?;
+
+    Ok(())
+}
+
+#[test]
+#[serial]
+fn pss_sign_verify() -> TestResult {
+    let (pkcs11, slot) = init_pins();
+
+    // open a session
+    let session = pkcs11.open_rw_session(slot)?;
+
+    // log in the session
+    session.login(UserType::User, Some(&AuthPin::new(USER_PIN.into())))?;
+
+    // get mechanism
+    let mechanism = Mechanism::RsaPkcsKeyPairGen;
+
+    let public_exponent: Vec<u8> = vec![0x01, 0x00, 0x01];
+    let modulus_bits = 1024;
+
+    let label = b"demo-signer";
+
+    // pub key template
+    let pub_key_template = vec![
+        Attribute::Token(true),
+        Attribute::Private(false),
+        Attribute::Label(label.to_vec()),
+        Attribute::PublicExponent(public_exponent),
+        Attribute::ModulusBits(modulus_bits.into()),
+    ];
+
+    // priv key template
+    let priv_key_template = vec![Attribute::Token(true), Attribute::Label(label.to_vec())];
+
+    // generate a key pair
+    let (public, private) =
+        session.generate_key_pair(&mechanism, &pub_key_template, &priv_key_template)?;
+
+    // data to sign
+    let data = [0xFF, 0x55, 0xDD];
+
+    let signer = pss::Signer::<Sha256, _>::new(&session, label).expect("Lookup keys from HSM");
+
+    let signature = signer.sign(&data);
+
+    let verifying_key = signer.verifying_key();
+    verifying_key.verify(&data, &signature)?;
+
+    // delete keys
+    session.destroy_object(public)?;
+    session.destroy_object(private)?;
+
+    Ok(())
+}
+
+#[test]
+#[serial]
+fn pkcs1v15_sign_verify_prehashed() -> TestResult {
+    let (pkcs11, slot) = init_pins();
+
+    // open a session
+    let session = pkcs11.open_rw_session(slot)?;
+
+    // log in the session
+    session.login(UserType::User, Some(&AuthPin::new(USER_PIN.into())))?;
+
+    // get mechanism
+    let mechanism = Mechanism::RsaPkcsKeyPairGen;
+
+    let public_exponent: Vec<u8> = vec![0x01, 0x00, 0x01];
+    let modulus_bits = 1024;
+
+    let label = b"demo-signer";
+
+    // pub key template
+    let pub_key_template = vec![
+        Attribute::Token(true),
+        Attribute::Private(false),
+        Attribute::Label(label.to_vec()),
+        Attribute::PublicExponent(public_exponent),
+        Attribute::ModulusBits(modulus_bits.into()),
+    ];
+
+    // priv key template
+    let priv_key_template = vec![Attribute::Token(true), Attribute::Label(label.to_vec())];
+
+    // generate a key pair
+    let (public, private) =
+        session.generate_key_pair(&mechanism, &pub_key_template, &priv_key_template)?;
+
+    // data to sign
+    let mut data = [0u8; 7123];
+    thread_rng().fill_bytes(&mut data[..]);
+
+    let prehashed = Sha256::digest(&data[..]);
+
+    let signer = pkcs1v15::Signer::<Sha256, _>::new(&session, label).expect("Lookup keys from HSM");
+
+    let signature1 = signer.sign(&data);
+    let signature2 = signer.sign_prehash(&prehashed).expect("Sign prehash");
+
+    let verifying_key = signer.verifying_key();
+    verifying_key.verify(&data, &signature1)?;
+    verifying_key.verify(&data, &signature2)?;
+
+    // delete keys
+    session.destroy_object(public)?;
+    session.destroy_object(private)?;
+
+    Ok(())
+}
diff --git a/cryptoki-rustcrypto/tests/verisign.der b/cryptoki-rustcrypto/tests/verisign.der
new file mode 100644
index 00000000..d94106f8
Binary files /dev/null and b/cryptoki-rustcrypto/tests/verisign.der differ
diff --git a/cryptoki-rustcrypto/tests/x509-ca.rs b/cryptoki-rustcrypto/tests/x509-ca.rs
new file mode 100644
index 00000000..c6ab5b7b
--- /dev/null
+++ b/cryptoki-rustcrypto/tests/x509-ca.rs
@@ -0,0 +1,154 @@
+// Copyright 2023 Contributors to the Parsec project.
+// SPDX-License-Identifier: Apache-2.0
+
+mod common;
+
+use crate::common::USER_PIN;
+use common::init_pins;
+use cryptoki::{
+    mechanism::Mechanism,
+    object::{Attribute, KeyType},
+    session::UserType,
+    types::AuthPin,
+};
+use cryptoki_rustcrypto::{ecdsa, rsa::pss};
+use der::{pem::LineEnding, Encode, EncodePem};
+use p256::pkcs8::AssociatedOid;
+use serial_test::serial;
+use signature::Keypair;
+use spki::SubjectPublicKeyInfoOwned;
+use std::{str::FromStr, time::Duration};
+use testresult::TestResult;
+use x509_cert::{
+    builder::{profile::cabf, Builder, CertificateBuilder},
+    name::Name,
+    serial_number::SerialNumber,
+    time::Validity,
+};
+
+#[test]
+#[serial]
+fn pss_create_ca() -> TestResult {
+    let (pkcs11, slot) = init_pins();
+
+    // open a session
+    let session = pkcs11.open_rw_session(slot)?;
+
+    // log in the session
+    session.login(UserType::User, Some(&AuthPin::new(USER_PIN.into())))?;
+
+    // get mechanism
+    let mechanism = Mechanism::RsaPkcsKeyPairGen;
+
+    let public_exponent: Vec<u8> = vec![0x01, 0x00, 0x01];
+    let modulus_bits = 1024;
+
+    let label = b"demo-signer";
+
+    // pub key template
+    let pub_key_template = vec![
+        Attribute::Token(true),
+        Attribute::Private(false),
+        Attribute::Label(label.to_vec()),
+        Attribute::PublicExponent(public_exponent),
+        Attribute::ModulusBits(modulus_bits.into()),
+    ];
+
+    // priv key template
+    let priv_key_template = vec![Attribute::Token(true), Attribute::Label(label.to_vec())];
+
+    // generate a key pair
+    let (public, private) =
+        session.generate_key_pair(&mechanism, &pub_key_template, &priv_key_template)?;
+
+    let signer =
+        pss::Signer::<sha2::Sha256, _>::new(&session, label).expect("Lookup keys from HSM");
+
+    let serial_number = SerialNumber::from(42u32);
+    let validity = Validity::from_now(Duration::new(5, 0)).unwrap();
+    let subject =
+        Name::from_str("CN=World domination corporation,O=World domination Inc,C=US").unwrap();
+    let profile = cabf::Root::new(false, subject).expect("Create root profile");
+    let pub_key = SubjectPublicKeyInfoOwned::from_key(&signer.verifying_key()).unwrap();
+
+    let builder = CertificateBuilder::new(profile, serial_number, validity, pub_key)
+        .expect("Create certificate");
+
+    let certificate = builder.build(&signer).unwrap();
+
+    let pem = certificate.to_pem(LineEnding::LF).expect("generate pem");
+    println!("{}", pem);
+
+    // delete keys
+    session.destroy_object(public)?;
+    session.destroy_object(private)?;
+
+    Ok(())
+}
+
+#[test]
+#[serial]
+fn ecdsa_create_ca() -> TestResult {
+    let (pkcs11, slot) = init_pins();
+
+    // open a session
+    let session = pkcs11.open_rw_session(slot)?;
+
+    // log in the session
+    session.login(UserType::User, Some(&AuthPin::new(USER_PIN.into())))?;
+
+    // get mechanism
+    let mechanism = Mechanism::EccKeyPairGen;
+
+    let secp256r1_oid: Vec<u8> = p256::NistP256::OID.to_der().unwrap();
+
+    let label = b"demo-signer";
+
+    // pub key template
+    let pub_key_template = vec![
+        Attribute::Token(true),
+        Attribute::Private(false),
+        Attribute::KeyType(KeyType::EC),
+        Attribute::Verify(true),
+        Attribute::EcParams(secp256r1_oid.clone()),
+        Attribute::Label(label.to_vec()),
+    ];
+
+    // priv key template
+    let priv_key_template = vec![
+        Attribute::Token(true),
+        Attribute::Private(true),
+        Attribute::Sign(true),
+        Attribute::Label(label.to_vec()),
+    ];
+
+    // generate a key pair
+    let (public, private) =
+        session.generate_key_pair(&mechanism, &pub_key_template, &priv_key_template)?;
+
+    let signer =
+        ecdsa::Signer::<p256::NistP256, _>::new(&session, label).expect("Lookup keys from HSM");
+
+    let serial_number = SerialNumber::from(42u32);
+    let validity = Validity::from_now(Duration::new(5, 0)).unwrap();
+    let subject =
+        Name::from_str("CN=World domination corporation,O=World domination Inc,C=US").unwrap();
+    let profile = cabf::Root::new(false, subject).expect("create root profile");
+    let pub_key = SubjectPublicKeyInfoOwned::from_key(&signer.verifying_key()).unwrap();
+
+    let builder = CertificateBuilder::new(profile, serial_number, validity, pub_key)
+        .expect("Create certificate");
+
+    let certificate = builder
+        .build::<_, p256::ecdsa::DerSignature>(&signer)
+        .unwrap();
+
+    let pem = certificate.to_pem(LineEnding::LF).expect("generate pem");
+    println!("{}", pem);
+
+    // delete keys
+    session.destroy_object(public)?;
+    session.destroy_object(private)?;
+
+    Ok(())
+}
diff --git a/cryptoki-rustcrypto/tests/x509.rs b/cryptoki-rustcrypto/tests/x509.rs
new file mode 100644
index 00000000..b0842ad8
--- /dev/null
+++ b/cryptoki-rustcrypto/tests/x509.rs
@@ -0,0 +1,41 @@
+// Copyright 2023 Contributors to the Parsec project.
+// SPDX-License-Identifier: Apache-2.0
+
+mod common;
+
+use crate::common::USER_PIN;
+use common::init_pins;
+use cryptoki::{object::Attribute, session::UserType, types::AuthPin};
+use cryptoki_rustcrypto::x509::CertPkcs11;
+use der::Decode;
+use serial_test::serial;
+use testresult::TestResult;
+use x509_cert::Certificate;
+
+const VERISIGN_CERT: &[u8] = include_bytes!("./verisign.der");
+
+#[test]
+#[serial]
+fn test_x509() -> TestResult {
+    let (pkcs11, slot) = init_pins();
+
+    // open a session
+    let session = pkcs11.open_rw_session(slot)?;
+
+    // log in the session
+    session.login(UserType::User, Some(&AuthPin::new(USER_PIN.into())))?;
+
+    let cert = Certificate::from_der(VERISIGN_CERT).expect("read certificate from der");
+
+    let base_template = vec![Attribute::Label(b"demo-cert".to_vec())];
+
+    cert.pkcs11_store(&session, base_template.clone())
+        .expect("Store cert with the PKCS11 provider");
+
+    let new = Certificate::pkcs11_load(&session, base_template)
+        .expect("Lookup cert from PKCS11 provider");
+
+    assert_eq!(cert, new);
+
+    Ok(())
+}