diff --git a/.gitignore b/.gitignore index 2ae70ecd..a6427941 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,4 @@ kotlin/mobilesdkrs/src/main/jniLibs/* kotlin/local.properties .direnv .envrc +**.idea diff --git a/Cargo.lock b/Cargo.lock index 6898c002..f98c6833 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -23,9 +23,9 @@ dependencies = [ [[package]] name = "addr2line" -version = "0.24.1" +version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5fb1d8e4442bd405fdfd1dacb42792696b0cf9cb15882e5d097b742a676d375" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" dependencies = [ "gimli", ] @@ -95,7 +95,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" dependencies = [ "cfg-if", + "getrandom", "once_cell", + "serde", "version_check", "zerocopy", ] @@ -334,6 +336,19 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "async-compat" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bab94bde396a3f7b4962e396fdad640e241ed797d4d8d77fc8c237d14c58fc0" +dependencies = [ + "futures-core", + "futures-io", + "once_cell", + "pin-project-lite", + "tokio", +] + [[package]] name = "async-executor" version = "1.13.1" @@ -548,6 +563,21 @@ dependencies = [ "serde", ] +[[package]] +name = "bit-set" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" + [[package]] name = "bitflags" version = "1.3.2" @@ -718,6 +748,12 @@ version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" +[[package]] +name = "bytecount" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ce89b21cab1437276d2650d57e971f9d548a2d9037cc231abdc0562b97498ce" + [[package]] name = "bytemuck" version = "1.18.0" @@ -804,9 +840,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.1.24" +version = "1.1.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "812acba72f0a070b003d3697490d2b55b837230ae7c6c6497f05cc2ddbb8d938" +checksum = "58e804ac3194a48bb129643eb1d62fcc20d18c6b8c181704489353d13120bcd1" dependencies = [ "shlex", ] @@ -894,9 +930,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.19" +version = "4.5.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7be5744db7978a28d9df86a214130d106a89ce49644cbc4e3f0c22c3fba30615" +checksum = "b97f376d85a664d5837dbae44bf546e6477a679ff6610010f17276f686d867e8" dependencies = [ "clap_builder", "clap_derive", @@ -913,9 +949,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.19" +version = "4.5.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5fbc17d3ef8278f55b282b2a2e75ae6f6c7d4bb70ed3d0382375104bfafdb4b" +checksum = "19bc80abd44e4bed93ca373a0704ccbd1b710dc5749406201bb018272808dc54" dependencies = [ "anstream", "anstyle", @@ -1621,6 +1657,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" dependencies = [ "base16ct", + "base64ct", "crypto-bigint 0.5.5", "digest 0.10.7", "ff", @@ -1631,6 +1668,7 @@ dependencies = [ "pkcs8 0.10.2", "rand_core", "sec1", + "serde_json", "serdect", "subtle", "zeroize", @@ -1754,6 +1792,17 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "fancy-regex" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "531e46835a22af56d1e3b66f04844bed63158bc094a628bec1d321d9b4c44bf2" +dependencies = [ + "bit-set", + "regex-automata", + "regex-syntax", +] + [[package]] name = "fastrand" version = "2.1.1" @@ -1837,6 +1886,16 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fraction" +version = "0.15.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f158e3ff0a1b334408dc9fb811cd99b446986f4d8b741bb08f9df1604085ae7" +dependencies = [ + "lazy_static", + "num", +] + [[package]] name = "fs-err" version = "2.11.0" @@ -1860,9 +1919,9 @@ checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" [[package]] name = "futures" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" dependencies = [ "futures-channel", "futures-core", @@ -1875,9 +1934,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ "futures-core", "futures-sink", @@ -1885,15 +1944,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" [[package]] name = "futures-executor" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" dependencies = [ "futures-core", "futures-task", @@ -1902,9 +1961,9 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" [[package]] name = "futures-lite" @@ -1921,9 +1980,9 @@ dependencies = [ [[package]] name = "futures-macro" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", @@ -1932,15 +1991,15 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" [[package]] name = "futures-task" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" [[package]] name = "futures-timer" @@ -1950,9 +2009,9 @@ checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" [[package]] name = "futures-util" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ "futures-channel", "futures-core", @@ -2002,9 +2061,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.31.0" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32085ea23f3234fc7846555e85283ba4de91e21016dc0455a16286d87a292d64" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] name = "glob" @@ -2336,7 +2395,7 @@ dependencies = [ "http 1.1.0", "hyper 1.4.1", "hyper-util", - "rustls 0.23.13", + "rustls 0.23.14", "rustls-pki-types", "tokio", "tokio-rustls 0.26.0", @@ -2484,9 +2543,9 @@ dependencies = [ [[package]] name = "ipnet" -version = "2.10.0" +version = "2.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "187674a687eed5fe42285b40c6291f9a01517d415fad1c3cbc6a9f778af7fcd4" +checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708" [[package]] name = "iref" @@ -2517,6 +2576,15 @@ version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +[[package]] +name = "iso8601" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "924e5d73ea28f59011fec52a0d12185d496a9b075d360657aed2a5707f701153" +dependencies = [ + "nom", +] + [[package]] name = "isomdl" version = "0.1.0" @@ -2621,9 +2689,9 @@ checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "js-sys" -version = "0.3.70" +version = "0.3.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a" +checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9" dependencies = [ "wasm-bindgen", ] @@ -2824,6 +2892,47 @@ dependencies = [ "utf8-decode", ] +[[package]] +name = "jsonpath_lib" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaa63191d68230cccb81c5aa23abd53ed64d83337cacbb25a7b8c7979523774f" +dependencies = [ + "log", + "serde", + "serde_json", +] + +[[package]] +name = "jsonschema" +version = "0.18.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa0f4bea31643be4c6a678e9aa4ae44f0db9e5609d5ca9dc9083d06eb3e9a27a" +dependencies = [ + "ahash 0.8.11", + "anyhow", + "base64 0.22.1", + "bytecount", + "clap", + "fancy-regex", + "fraction", + "getrandom", + "iso8601", + "itoa", + "memchr", + "num-cmp", + "once_cell", + "parking_lot", + "percent-encoding", + "regex", + "reqwest 0.12.8", + "serde", + "serde_json", + "time", + "url", + "uuid", +] + [[package]] name = "k256" version = "0.13.4" @@ -3204,6 +3313,7 @@ dependencies = [ "cose-rs 0.1.0 (git+https://github.com/spruceid/cose-rs?rev=0018c9b)", "either", "futures", + "futures-util", "hex", "isomdl 0.1.0 (git+https://github.com/spruceid/isomdl?rev=1f4f762)", "json-syntax", @@ -3212,6 +3322,7 @@ dependencies = [ "num-bigint", "num-traits", "oid4vci", + "openid4vp", "p256", "pem-rfc7468 0.7.0", "reqwest 0.11.27", @@ -3229,6 +3340,7 @@ dependencies = [ "tracing", "uniffi", "url", + "urlencoding", "uuid", "w3c-vc-barcodes", "x509-cert 0.2.5", @@ -3322,6 +3434,20 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "num" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + [[package]] name = "num-bigint" version = "0.4.6" @@ -3349,6 +3475,21 @@ dependencies = [ "zeroize", ] +[[package]] +name = "num-cmp" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63335b2e2c34fae2fb0aa2cecfd9f0832a1e24b3b32ecec612c3426d46dc8aaa" + +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", +] + [[package]] name = "num-conv" version = "0.1.0" @@ -3429,9 +3570,9 @@ dependencies = [ [[package]] name = "object" -version = "0.36.4" +version = "0.36.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "084f1a5821ac4c651660a94a7153d27ac9d8a53736203f58b31945ded098070a" +checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" dependencies = [ "memchr", ] @@ -3453,7 +3594,7 @@ dependencies = [ "serde_json", "serde_path_to_error", "serde_urlencoded", - "serde_with 3.10.0", + "serde_with 3.11.0", "sha2 0.10.8", "ssi-claims", "ssi-dids-core", @@ -3466,12 +3607,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.20.1" +version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82881c4be219ab5faaf2ad5e5e5ecdff8c66bd7402ca3160975c93b24961afd1" -dependencies = [ - "portable-atomic", -] +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" [[package]] name = "opaque-debug" @@ -3479,6 +3617,42 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" +[[package]] +name = "openid4vp" +version = "0.1.0" +source = "git+https://github.com/spruceid/openid4vp?rev=372f5f5#372f5f59c3176786cda6ebcdf41944c5ff7fb932" +dependencies = [ + "anyhow", + "async-trait", + "base64 0.21.7", + "http 1.1.0", + "json-syntax", + "jsonpath_lib", + "jsonschema", + "openid4vp-frontend", + "p256", + "rand", + "reqwest 0.12.8", + "serde", + "serde_json", + "serde_urlencoded", + "ssi", + "tokio", + "tracing", + "url", + "uuid", + "x509-cert 0.2.5", +] + +[[package]] +name = "openid4vp-frontend" +version = "0.1.0" +source = "git+https://github.com/spruceid/openid4vp?rev=372f5f5#372f5f59c3176786cda6ebcdf41944c5ff7fb932" +dependencies = [ + "serde", + "serde_json", +] + [[package]] name = "openssl" version = "0.10.66" @@ -3674,18 +3848,18 @@ checksum = "b687ff7b5da449d39e418ad391e5e08da53ec334903ddbb921db208908fc372c" [[package]] name = "pin-project" -version = "1.1.5" +version = "1.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" +checksum = "baf123a161dde1e524adf36f90bc5d8d3462824a9c43553ad07a8183161189ec" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.5" +version = "1.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" +checksum = "a4502d8515ca9f32f1fb543d987f63d95a14934883db45bdb48060b6b69257f8" dependencies = [ "proc-macro2", "quote", @@ -3786,12 +3960,6 @@ dependencies = [ "universal-hash", ] -[[package]] -name = "portable-atomic" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc9c68a3f6da06753e9335d63e27f6b9754dd1920d941135b7ea8224f141adb2" - [[package]] name = "powerfmt" version = "0.2.0" @@ -3881,9 +4049,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.86" +version = "1.0.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +checksum = "b3e4daa0dcf6feba26f985457cdf104d4b4256fc5a09547140f3631bb076b19a" dependencies = [ "unicode-ident", ] @@ -3899,7 +4067,7 @@ dependencies = [ "quinn-proto", "quinn-udp", "rustc-hash", - "rustls 0.23.13", + "rustls 0.23.14", "socket2", "thiserror", "tokio", @@ -3916,7 +4084,7 @@ dependencies = [ "rand", "ring", "rustc-hash", - "rustls 0.23.13", + "rustls 0.23.14", "slab", "thiserror", "tinyvec", @@ -4131,6 +4299,7 @@ dependencies = [ "base64 0.22.1", "bytes", "encoding_rs", + "futures-channel", "futures-core", "futures-util", "h2 0.4.6", @@ -4150,7 +4319,7 @@ dependencies = [ "percent-encoding", "pin-project-lite", "quinn", - "rustls 0.23.13", + "rustls 0.23.14", "rustls-pemfile 2.2.0", "rustls-pki-types", "serde", @@ -4328,9 +4497,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.13" +version = "0.23.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2dabaac7466917e566adb06783a81ca48944c6898a1b08b9374106dd671f4c8" +checksum = "415d9944693cb90382053259f89fbb077ea730ad7273047ec63b19bc9b160ba8" dependencies = [ "once_cell", "ring", @@ -4405,9 +4574,9 @@ checksum = "700de91d5fd6091442d00fdd9ee790af6d4f0f480562b0f5a1e8f59e90aafe73" [[package]] name = "schannel" -version = "0.1.24" +version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9aaafd5a2b6e3d657ff009d82fbd630b6bd54dd4eb06f21693925cdf80f9b8b" +checksum = "01227be5826fa0690321a2ba6c5cd57a19cf3f6a09e76973b58e61de6ab9d1c1" dependencies = [ "windows-sys 0.59.0", ] @@ -4566,6 +4735,7 @@ version = "1.0.128" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" dependencies = [ + "indexmap 2.6.0", "itoa", "memchr", "ryu", @@ -4632,9 +4802,9 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.10.0" +version = "3.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9720086b3357bcb44fce40117d769a4d068c70ecfa190850a980a71755f66fcc" +checksum = "8e28bdad6db2b8340e449f7108f020b3b092e8583a9e3fb82713e1d4e71fe817" dependencies = [ "base64 0.22.1", "chrono", @@ -4644,7 +4814,7 @@ dependencies = [ "serde", "serde_derive", "serde_json", - "serde_with_macros 3.10.0", + "serde_with_macros 3.11.0", "time", ] @@ -4674,9 +4844,9 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.10.0" +version = "3.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f1abbfe725f27678f4663bcacb75a83e829fd464c25d78dd038a3a29e307cec" +checksum = "9d846214a9854ef724f3da161b426242d8de7c1fc7de2f89bb1efcb154dca79d" dependencies = [ "darling 0.20.10", "proc-macro2", @@ -5974,7 +6144,7 @@ version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" dependencies = [ - "rustls 0.23.13", + "rustls 0.23.14", "rustls-pki-types", "tokio", ] @@ -6116,9 +6286,9 @@ dependencies = [ [[package]] name = "unicode-bidi" -version = "0.3.15" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" +checksum = "5ab17db44d7388991a428b2ee655ce0c212e862eff1768a455c58f9aad6e7893" [[package]] name = "unicode-ident" @@ -6143,9 +6313,9 @@ checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[package]] name = "uniffi" -version = "0.28.1" +version = "0.28.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2db87def739fe4183947f8419d572d1849a4a09355eba4e988a2105cfd0ac6a7" +checksum = "51ce6280c581045879e11b400bae14686a819df22b97171215d15549efa04ddb" dependencies = [ "anyhow", "camino", @@ -6159,9 +6329,9 @@ dependencies = [ [[package]] name = "uniffi_bindgen" -version = "0.28.1" +version = "0.28.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a112599c9556d1581e4a3d72019a74c2c3e122cc27f4af12577a429c4d5e614" +checksum = "5e9f25730c9db2e878521d606f54e921edb719cdd94d735e7f97705d6796d024" dependencies = [ "anyhow", "askama", @@ -6183,9 +6353,9 @@ dependencies = [ [[package]] name = "uniffi_build" -version = "0.28.1" +version = "0.28.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2b12684401d2a8508ca9c72a95bbc45906417e42fc80942abaf033bbf01aa33" +checksum = "88dba57ac699bd8ec53d6a352c8dd0e479b33f698c5659831bb1e4ce468c07bd" dependencies = [ "anyhow", "camino", @@ -6194,9 +6364,9 @@ dependencies = [ [[package]] name = "uniffi_checksum_derive" -version = "0.28.1" +version = "0.28.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a22dbe67c1c957ac6e7611bdf605a6218aa86b0eebeb8be58b70ae85ad7d73dc" +checksum = "d2c801f0f05b06df456a2da4c41b9c2c4fdccc6b9916643c6c67275c4c9e4d07" dependencies = [ "quote", "syn 2.0.79", @@ -6204,13 +6374,13 @@ dependencies = [ [[package]] name = "uniffi_core" -version = "0.28.1" +version = "0.28.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a0c35aaad30e3a9e6d4fe34e358d64dbc92ee09045b48591b05fc9f12e0905b" +checksum = "61049e4db6212d0ede80982adf0e1d6fa224e6118387324c5cfbe3083dfb2252" dependencies = [ "anyhow", + "async-compat", "bytes", - "camino", "log", "once_cell", "paste", @@ -6219,9 +6389,9 @@ dependencies = [ [[package]] name = "uniffi_macros" -version = "0.28.1" +version = "0.28.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db66474c5c61b0f7afc3b4995fecf9b72b340daa5ca0ef3da7778d75eb5482ea" +checksum = "b40fd2249e0c5dcbd2bfa3c263db1ec981f7273dca7f4132bf06a272359a586c" dependencies = [ "bincode", "camino", @@ -6237,9 +6407,9 @@ dependencies = [ [[package]] name = "uniffi_meta" -version = "0.28.1" +version = "0.28.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d898893f102e0e39b8bcb7e3d2188f4156ba280db32db9e8af1f122d057e9526" +checksum = "c9ad57039b4fafdbf77428d74fff40e0908e5a1731e023c19cfe538f6d4a8ed6" dependencies = [ "anyhow", "bytes", @@ -6249,9 +6419,9 @@ dependencies = [ [[package]] name = "uniffi_testing" -version = "0.28.1" +version = "0.28.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c6aa4f0cf9d12172d84fc00a35a6c1f3522b526daad05ae739f709f6941b9b6" +checksum = "21fa171d4d258dc51bbd01893cc9608c1b62273d2f9ea55fb64f639e77824567" dependencies = [ "anyhow", "camino", @@ -6262,9 +6432,9 @@ dependencies = [ [[package]] name = "uniffi_udl" -version = "0.28.1" +version = "0.28.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b044e9c519e0bb51e516ab6f6d8f4f4dcf900ce30d5ad07c03f924e2824f28e" +checksum = "f52299e247419e7e2934bef2f94d7cccb0e6566f3248b1d48b160d8f369a2668" dependencies = [ "anyhow", "textwrap", @@ -6307,6 +6477,12 @@ dependencies = [ "serde", ] +[[package]] +name = "urlencoding" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" + [[package]] name = "utf8-decode" version = "1.0.1" @@ -6328,6 +6504,7 @@ dependencies = [ "atomic", "getrandom", "serde", + "wasm-bindgen", ] [[package]] @@ -6382,9 +6559,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.93" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" +checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e" dependencies = [ "cfg-if", "once_cell", @@ -6393,9 +6570,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.93" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" +checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358" dependencies = [ "bumpalo", "log", @@ -6408,9 +6585,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.43" +version = "0.4.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61e9300f63a621e96ed275155c108eb6f843b6a26d053f122ab69724559dc8ed" +checksum = "cc7ec4f8827a71586374db3e87abdb5a2bb3a15afed140221307c3ec06b1f63b" dependencies = [ "cfg-if", "js-sys", @@ -6420,9 +6597,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.93" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" +checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -6430,9 +6607,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.93" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" +checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" dependencies = [ "proc-macro2", "quote", @@ -6443,15 +6620,15 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.93" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" +checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" [[package]] name = "web-sys" -version = "0.3.70" +version = "0.3.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26fdeaafd9bd129f65e7c031593c24d62186301e0c72c8978fa1678be7d532c0" +checksum = "f6488b90108c040df0fe62fa815cbdee25124641df01814dd7282749234c6112" dependencies = [ "js-sys", "wasm-bindgen", diff --git a/Cargo.toml b/Cargo.toml index e1017c59..79efc8e5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,16 +21,16 @@ cose-rs = { git = "https://github.com/spruceid/cose-rs", rev = "0018c9b", featur ] } isomdl = { git = "https://github.com/spruceid/isomdl", rev = "1f4f762" } oid4vci = { git = "https://github.com/spruceid/oid4vci-rs", rev = "d95fe3a" } -# oid4vci = { path = "../oid4vci-rs" } +openid4vp = { git = "https://github.com/spruceid/openid4vp", rev = "372f5f5" } ssi = { version = "0.9", features = ["secp256r1", "secp384r1"] } -# ssi = { path = "../ssi" } async-trait = "0.1" base64 = "0.22.0" either = "1.13" futures = "0.3" +futures-util = "0.3.31" hex = "0.4.3" -json-syntax = { version = "0.12.5", features = ["serde_json"] } +json-syntax = "0.12.5" log = { version = "0.4", features = ["std", "serde"] } miniz_oxide = "0.7.2" num-bigint = "0.4.4" @@ -41,9 +41,9 @@ reqwest = { version = "0.11", features = ["blocking"] } serde = { version = "1.0.204", features = ["derive"] } serde_cbor = "0.11.2" serde_json = "1.0.111" +thiserror = "1.0.56" signature = "2.2.0" ssi-contexts = "0.1.6" -thiserror = "1.0.56" time = { version = "0.3.36", features = [ "macros", "formatting", @@ -53,18 +53,19 @@ time = { version = "0.3.36", features = [ time-macros = "0.2.18" tokio = { version = "1", features = ["full"] } tracing = "0.1.40" -uniffi = { version = "0.28.1", features = ["cli"] } +uniffi = { version = "0.28.1", features = ["cli", "tokio"] } url = { version = "2.5", features = ["serde"] } uuid = { version = "1.6.1", features = ["v4"] } w3c-vc-barcodes = { git = "https://github.com/spruceid/w3c-vc-barcodes", rev = "c1c99da" } x509-cert = { version = "0.2.5" } +urlencoding = "2.1.3" + [target.'cfg(target_os = "android")'.dependencies] android_logger = "0.13" [dev-dependencies] rstest = "0.22.0" -tokio = "1.39.2" uniffi = { version = "0.28.1", features = ["bindgen-tests"] } [build-dependencies] diff --git a/MobileSdkRs/Sources/MobileSdkRs/mobile_sdk_rs.swift b/MobileSdkRs/Sources/MobileSdkRs/mobile_sdk_rs.swift index 7a986611..2e4802e9 100644 --- a/MobileSdkRs/Sources/MobileSdkRs/mobile_sdk_rs.swift +++ b/MobileSdkRs/Sources/MobileSdkRs/mobile_sdk_rs.swift @@ -1057,6 +1057,204 @@ public func FfiConverterTypeGrants_lower(_ value: Grants) -> UnsafeMutableRawPoi +/** + * A Holder is an entity that possesses one or more Verifiable Credentials. + * The Holder is typically the subject of the credentials, but not always. + * The Holder has the ability to generate Verifiable Presentations from + * these credentials and share them with Verifiers. + */ +public protocol HolderProtocol : AnyObject { + + /** + * Given an authorization request URL, return a permission request, + * which provides a list of requested credentials and requested fields + * that align with the presentation definition of the request. + * + * This will fetch the presentation definition from the verifier. + */ + func authorizationRequest(url: Url) async throws -> PermissionRequest + + func submitPermissionResponse(response: PermissionResponse) async throws -> Url? + +} + +/** + * A Holder is an entity that possesses one or more Verifiable Credentials. + * The Holder is typically the subject of the credentials, but not always. + * The Holder has the ability to generate Verifiable Presentations from + * these credentials and share them with Verifiers. + */ +open class Holder: + HolderProtocol { + fileprivate let pointer: UnsafeMutableRawPointer! + + /// Used to instantiate a [FFIObject] without an actual pointer, for fakes in tests, mostly. + public struct NoPointer { + public init() {} + } + + // TODO: We'd like this to be `private` but for Swifty reasons, + // we can't implement `FfiConverter` without making this `required` and we can't + // make it `required` without making it `public`. + required public init(unsafeFromRawPointer pointer: UnsafeMutableRawPointer) { + self.pointer = pointer + } + + /// This constructor can be used to instantiate a fake object. + /// - Parameter noPointer: Placeholder value so we can have a constructor separate from the default empty one that may be implemented for classes extending [FFIObject]. + /// + /// - Warning: + /// Any object instantiated with this constructor cannot be passed to an actual Rust-backed object. Since there isn't a backing [Pointer] the FFI lower functions will crash. + public init(noPointer: NoPointer) { + self.pointer = nil + } + + public func uniffiClonePointer() -> UnsafeMutableRawPointer { + return try! rustCall { uniffi_mobile_sdk_rs_fn_clone_holder(self.pointer, $0) } + } + /** + * Uses VDC collection to retrieve the credentials for a given presentation definition. + */ +public convenience init(vdcCollection: VdcCollection, trustedDids: [String])async throws { + let pointer = + try await uniffiRustCallAsync( + rustFutureFunc: { + uniffi_mobile_sdk_rs_fn_constructor_holder_new(FfiConverterTypeVdcCollection.lower(vdcCollection),FfiConverterSequenceString.lower(trustedDids) + ) + }, + pollFunc: ffi_mobile_sdk_rs_rust_future_poll_pointer, + completeFunc: ffi_mobile_sdk_rs_rust_future_complete_pointer, + freeFunc: ffi_mobile_sdk_rs_rust_future_free_pointer, + liftFunc: FfiConverterTypeHolder.lift, + errorHandler: FfiConverterTypeOID4VPError.lift + ) + + .uniffiClonePointer() + self.init(unsafeFromRawPointer: pointer) +} + + deinit { + guard let pointer = pointer else { + return + } + + try! rustCall { uniffi_mobile_sdk_rs_fn_free_holder(pointer, $0) } + } + + + /** + * Construct a new holder with provided credentials + * instead of a VDC collection. + * + * This constructor will use the provided credentials for the presentation, + * instead of searching for credentials in the VDC collection. + */ +public static func newWithCredentials(providedCredentials: [ParsedCredential], trustedDids: [String])async throws -> Holder { + return + try await uniffiRustCallAsync( + rustFutureFunc: { + uniffi_mobile_sdk_rs_fn_constructor_holder_new_with_credentials(FfiConverterSequenceTypeParsedCredential.lower(providedCredentials),FfiConverterSequenceString.lower(trustedDids) + ) + }, + pollFunc: ffi_mobile_sdk_rs_rust_future_poll_pointer, + completeFunc: ffi_mobile_sdk_rs_rust_future_complete_pointer, + freeFunc: ffi_mobile_sdk_rs_rust_future_free_pointer, + liftFunc: FfiConverterTypeHolder.lift, + errorHandler: FfiConverterTypeOID4VPError.lift + ) +} + + + + /** + * Given an authorization request URL, return a permission request, + * which provides a list of requested credentials and requested fields + * that align with the presentation definition of the request. + * + * This will fetch the presentation definition from the verifier. + */ +open func authorizationRequest(url: Url)async throws -> PermissionRequest { + return + try await uniffiRustCallAsync( + rustFutureFunc: { + uniffi_mobile_sdk_rs_fn_method_holder_authorization_request( + self.uniffiClonePointer(), + FfiConverterTypeUrl.lower(url) + ) + }, + pollFunc: ffi_mobile_sdk_rs_rust_future_poll_pointer, + completeFunc: ffi_mobile_sdk_rs_rust_future_complete_pointer, + freeFunc: ffi_mobile_sdk_rs_rust_future_free_pointer, + liftFunc: FfiConverterTypePermissionRequest.lift, + errorHandler: FfiConverterTypeOID4VPError.lift + ) +} + +open func submitPermissionResponse(response: PermissionResponse)async throws -> Url? { + return + try await uniffiRustCallAsync( + rustFutureFunc: { + uniffi_mobile_sdk_rs_fn_method_holder_submit_permission_response( + self.uniffiClonePointer(), + FfiConverterTypePermissionResponse.lower(response) + ) + }, + pollFunc: ffi_mobile_sdk_rs_rust_future_poll_rust_buffer, + completeFunc: ffi_mobile_sdk_rs_rust_future_complete_rust_buffer, + freeFunc: ffi_mobile_sdk_rs_rust_future_free_rust_buffer, + liftFunc: FfiConverterOptionTypeUrl.lift, + errorHandler: FfiConverterTypeOID4VPError.lift + ) +} + + +} + +public struct FfiConverterTypeHolder: FfiConverter { + + typealias FfiType = UnsafeMutableRawPointer + typealias SwiftType = Holder + + public static func lift(_ pointer: UnsafeMutableRawPointer) throws -> Holder { + return Holder(unsafeFromRawPointer: pointer) + } + + public static func lower(_ value: Holder) -> UnsafeMutableRawPointer { + return value.uniffiClonePointer() + } + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> Holder { + let v: UInt64 = try readInt(&buf) + // The Rust code won't compile if a pointer won't fit in a UInt64. + // We have to go via `UInt` because that's the thing that's the size of a pointer. + let ptr = UnsafeMutableRawPointer(bitPattern: UInt(truncatingIfNeeded: v)) + if (ptr == nil) { + throw UniffiInternalError.unexpectedNullPointer + } + return try lift(ptr!) + } + + public static func write(_ value: Holder, into buf: inout [UInt8]) { + // This fiddling is because `Int` is the thing that's the same size as a pointer. + // The Rust code won't compile if a pointer won't fit in a `UInt64`. + writeInt(&buf, UInt64(bitPattern: Int64(Int(bitPattern: lower(value))))) + } +} + + + + +public func FfiConverterTypeHolder_lift(_ pointer: UnsafeMutableRawPointer) throws -> Holder { + return try FfiConverterTypeHolder.lift(pointer) +} + +public func FfiConverterTypeHolder_lower(_ value: Holder) -> UnsafeMutableRawPointer { + return FfiConverterTypeHolder.lower(value) +} + + + + /** * Http client wrapper type that could either be a synchronous or asynchronous * external (Kotlin, Swift, etc) client implementation, receveid as a dynamic @@ -2728,6 +2926,16 @@ public protocol ParsedCredentialProtocol : AnyObject { */ func asMsoMdoc() -> Mdoc? + /** + * Return the credential as an SD-JWT, if it is of that format. + */ + func asSdJwt() -> Vcdm2SdJwt? + + /** + * Return the format of the credential. + */ + func format() -> CredentialFormat + /** * Get the local ID for this credential. */ @@ -2743,6 +2951,11 @@ public protocol ParsedCredentialProtocol : AnyObject { */ func keyAlias() -> KeyAlias? + /** + * Return the CredentialType from the parsed credential. + */ + func type() -> CredentialType + } /** @@ -2829,6 +3042,17 @@ public static func newMsoMdoc(mdoc: Mdoc) -> ParsedCredential { FfiConverterTypeMdoc.lower(mdoc),$0 ) }) +} + + /** + * Construct a new `sd_jwt_vc` credential. + */ +public static func newSdJwt(sdJwtVc: Vcdm2SdJwt) -> ParsedCredential { + return try! FfiConverterTypeParsedCredential.lift(try! rustCall() { + uniffi_mobile_sdk_rs_fn_constructor_parsedcredential_new_sd_jwt( + FfiConverterTypeVCDM2SdJwt.lower(sdJwtVc),$0 + ) +}) } /** @@ -2872,6 +3096,26 @@ open func asMsoMdoc() -> Mdoc? { uniffi_mobile_sdk_rs_fn_method_parsedcredential_as_mso_mdoc(self.uniffiClonePointer(),$0 ) }) +} + + /** + * Return the credential as an SD-JWT, if it is of that format. + */ +open func asSdJwt() -> Vcdm2SdJwt? { + return try! FfiConverterOptionTypeVCDM2SdJwt.lift(try! rustCall() { + uniffi_mobile_sdk_rs_fn_method_parsedcredential_as_sd_jwt(self.uniffiClonePointer(),$0 + ) +}) +} + + /** + * Return the format of the credential. + */ +open func format() -> CredentialFormat { + return try! FfiConverterTypeCredentialFormat.lift(try! rustCall() { + uniffi_mobile_sdk_rs_fn_method_parsedcredential_format(self.uniffiClonePointer(),$0 + ) +}) } /** @@ -2904,6 +3148,16 @@ open func keyAlias() -> KeyAlias? { }) } + /** + * Return the CredentialType from the parsed credential. + */ +open func type() -> CredentialType { + return try! FfiConverterTypeCredentialType.lift(try! rustCall() { + uniffi_mobile_sdk_rs_fn_method_parsedcredential_type(self.uniffiClonePointer(),$0 + ) +}) +} + } @@ -2952,73 +3206,35 @@ public func FfiConverterTypeParsedCredential_lower(_ value: ParsedCredential) -> -/** - * Interface: StorageManagerInterface - * - * The StorageManagerInterface provides access to functions defined in Kotlin and Swift for - * managing persistent storage on the device. - * - * When dealing with UniFFI exported functions and objects, this will need to be Boxed as: - * Box - * - * We use the older callback_interface to keep the required version level of our Android API - * low. - */ -public protocol StorageManagerInterface : AnyObject { +public protocol PermissionRequestProtocol : AnyObject { /** - * Function: add - * - * Adds a key-value pair to storage. Should the key already exist, the value will be - * replaced - * - * Arguments: - * key - The key to add - * value - The value to add under the key. + * Construct a new permission response for the given credential. */ - func add(key: Key, value: Value) throws + func createPermissionResponse(selectedCredential: ParsedCredential) -> PermissionResponse /** - * Function: get - * - * Callback function pointer to native (kotlin/swift) code for - * getting a key. + * Return the filtered list of credentials that matched + * the presentation definition. */ - func get(key: Key) throws -> Value? + func credentials() -> [ParsedCredential] /** - * Function: list - * - * Callback function pointer for listing available keys. + * Return the purpose of the presentation request. */ - func list() throws -> [Key] + func purpose() -> String? /** - * Function: remove + * Return the requested fields for a given credential. * - * Callback function pointer to native (kotlin/swift) code for - * removing a key. This referenced function MUST be idempotent. In - * particular, it must treat removing a non-existent key as a normal and - * expected circumstance, simply returning () and not an error. + * NOTE: This will return only the requested fields for a given credential. */ - func remove(key: Key) throws + func requestedFields(credential: ParsedCredential) -> [RequestedField] } -/** - * Interface: StorageManagerInterface - * - * The StorageManagerInterface provides access to functions defined in Kotlin and Swift for - * managing persistent storage on the device. - * - * When dealing with UniFFI exported functions and objects, this will need to be Boxed as: - * Box - * - * We use the older callback_interface to keep the required version level of our Android API - * low. - */ -open class StorageManagerInterfaceImpl: - StorageManagerInterface { +open class PermissionRequest: + PermissionRequestProtocol { fileprivate let pointer: UnsafeMutableRawPointer! /// Used to instantiate a [FFIObject] without an actual pointer, for fakes in tests, mostly. @@ -3043,7 +3259,7 @@ open class StorageManagerInterfaceImpl: } public func uniffiClonePointer() -> UnsafeMutableRawPointer { - return try! rustCall { uniffi_mobile_sdk_rs_fn_clone_storagemanagerinterface(self.pointer, $0) } + return try! rustCall { uniffi_mobile_sdk_rs_fn_clone_permissionrequest(self.pointer, $0) } } // No primary constructor declared for this class. @@ -3052,50 +3268,511 @@ open class StorageManagerInterfaceImpl: return } - try! rustCall { uniffi_mobile_sdk_rs_fn_free_storagemanagerinterface(pointer, $0) } + try! rustCall { uniffi_mobile_sdk_rs_fn_free_permissionrequest(pointer, $0) } } /** - * Function: add - * - * Adds a key-value pair to storage. Should the key already exist, the value will be - * replaced - * - * Arguments: - * key - The key to add - * value - The value to add under the key. + * Construct a new permission response for the given credential. */ -open func add(key: Key, value: Value)throws {try rustCallWithError(FfiConverterTypeStorageManagerError.lift) { - uniffi_mobile_sdk_rs_fn_method_storagemanagerinterface_add(self.uniffiClonePointer(), - FfiConverterTypeKey.lower(key), - FfiConverterTypeValue.lower(value),$0 +open func createPermissionResponse(selectedCredential: ParsedCredential) -> PermissionResponse { + return try! FfiConverterTypePermissionResponse.lift(try! rustCall() { + uniffi_mobile_sdk_rs_fn_method_permissionrequest_create_permission_response(self.uniffiClonePointer(), + FfiConverterTypeParsedCredential.lower(selectedCredential),$0 ) -} +}) } /** - * Function: get - * - * Callback function pointer to native (kotlin/swift) code for - * getting a key. + * Return the filtered list of credentials that matched + * the presentation definition. */ -open func get(key: Key)throws -> Value? { - return try FfiConverterOptionTypeValue.lift(try rustCallWithError(FfiConverterTypeStorageManagerError.lift) { - uniffi_mobile_sdk_rs_fn_method_storagemanagerinterface_get(self.uniffiClonePointer(), - FfiConverterTypeKey.lower(key),$0 +open func credentials() -> [ParsedCredential] { + return try! FfiConverterSequenceTypeParsedCredential.lift(try! rustCall() { + uniffi_mobile_sdk_rs_fn_method_permissionrequest_credentials(self.uniffiClonePointer(),$0 ) }) } /** - * Function: list - * - * Callback function pointer for listing available keys. + * Return the purpose of the presentation request. */ -open func list()throws -> [Key] { +open func purpose() -> String? { + return try! FfiConverterOptionString.lift(try! rustCall() { + uniffi_mobile_sdk_rs_fn_method_permissionrequest_purpose(self.uniffiClonePointer(),$0 + ) +}) +} + + /** + * Return the requested fields for a given credential. + * + * NOTE: This will return only the requested fields for a given credential. + */ +open func requestedFields(credential: ParsedCredential) -> [RequestedField] { + return try! FfiConverterSequenceTypeRequestedField.lift(try! rustCall() { + uniffi_mobile_sdk_rs_fn_method_permissionrequest_requested_fields(self.uniffiClonePointer(), + FfiConverterTypeParsedCredential.lower(credential),$0 + ) +}) +} + + +} + +public struct FfiConverterTypePermissionRequest: FfiConverter { + + typealias FfiType = UnsafeMutableRawPointer + typealias SwiftType = PermissionRequest + + public static func lift(_ pointer: UnsafeMutableRawPointer) throws -> PermissionRequest { + return PermissionRequest(unsafeFromRawPointer: pointer) + } + + public static func lower(_ value: PermissionRequest) -> UnsafeMutableRawPointer { + return value.uniffiClonePointer() + } + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> PermissionRequest { + let v: UInt64 = try readInt(&buf) + // The Rust code won't compile if a pointer won't fit in a UInt64. + // We have to go via `UInt` because that's the thing that's the size of a pointer. + let ptr = UnsafeMutableRawPointer(bitPattern: UInt(truncatingIfNeeded: v)) + if (ptr == nil) { + throw UniffiInternalError.unexpectedNullPointer + } + return try lift(ptr!) + } + + public static func write(_ value: PermissionRequest, into buf: inout [UInt8]) { + // This fiddling is because `Int` is the thing that's the same size as a pointer. + // The Rust code won't compile if a pointer won't fit in a `UInt64`. + writeInt(&buf, UInt64(bitPattern: Int64(Int(bitPattern: lower(value))))) + } +} + + + + +public func FfiConverterTypePermissionRequest_lift(_ pointer: UnsafeMutableRawPointer) throws -> PermissionRequest { + return try FfiConverterTypePermissionRequest.lift(pointer) +} + +public func FfiConverterTypePermissionRequest_lower(_ value: PermissionRequest) -> UnsafeMutableRawPointer { + return FfiConverterTypePermissionRequest.lower(value) +} + + + + +/** + * This struct is used to represent the response to a permission request. + * + * Use the [PermissionResponse::new] method to create a new instance of the PermissionResponse. + * + * The Requested Fields are created by calling the [PermissionRequest::requested_fields] method, and then + * explicitly setting the permission to true or false, based on the holder's decision. + */ +public protocol PermissionResponseProtocol : AnyObject { + +} + +/** + * This struct is used to represent the response to a permission request. + * + * Use the [PermissionResponse::new] method to create a new instance of the PermissionResponse. + * + * The Requested Fields are created by calling the [PermissionRequest::requested_fields] method, and then + * explicitly setting the permission to true or false, based on the holder's decision. + */ +open class PermissionResponse: + PermissionResponseProtocol { + fileprivate let pointer: UnsafeMutableRawPointer! + + /// Used to instantiate a [FFIObject] without an actual pointer, for fakes in tests, mostly. + public struct NoPointer { + public init() {} + } + + // TODO: We'd like this to be `private` but for Swifty reasons, + // we can't implement `FfiConverter` without making this `required` and we can't + // make it `required` without making it `public`. + required public init(unsafeFromRawPointer pointer: UnsafeMutableRawPointer) { + self.pointer = pointer + } + + /// This constructor can be used to instantiate a fake object. + /// - Parameter noPointer: Placeholder value so we can have a constructor separate from the default empty one that may be implemented for classes extending [FFIObject]. + /// + /// - Warning: + /// Any object instantiated with this constructor cannot be passed to an actual Rust-backed object. Since there isn't a backing [Pointer] the FFI lower functions will crash. + public init(noPointer: NoPointer) { + self.pointer = nil + } + + public func uniffiClonePointer() -> UnsafeMutableRawPointer { + return try! rustCall { uniffi_mobile_sdk_rs_fn_clone_permissionresponse(self.pointer, $0) } + } + // No primary constructor declared for this class. + + deinit { + guard let pointer = pointer else { + return + } + + try! rustCall { uniffi_mobile_sdk_rs_fn_free_permissionresponse(pointer, $0) } + } + + + + + +} + +public struct FfiConverterTypePermissionResponse: FfiConverter { + + typealias FfiType = UnsafeMutableRawPointer + typealias SwiftType = PermissionResponse + + public static func lift(_ pointer: UnsafeMutableRawPointer) throws -> PermissionResponse { + return PermissionResponse(unsafeFromRawPointer: pointer) + } + + public static func lower(_ value: PermissionResponse) -> UnsafeMutableRawPointer { + return value.uniffiClonePointer() + } + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> PermissionResponse { + let v: UInt64 = try readInt(&buf) + // The Rust code won't compile if a pointer won't fit in a UInt64. + // We have to go via `UInt` because that's the thing that's the size of a pointer. + let ptr = UnsafeMutableRawPointer(bitPattern: UInt(truncatingIfNeeded: v)) + if (ptr == nil) { + throw UniffiInternalError.unexpectedNullPointer + } + return try lift(ptr!) + } + + public static func write(_ value: PermissionResponse, into buf: inout [UInt8]) { + // This fiddling is because `Int` is the thing that's the same size as a pointer. + // The Rust code won't compile if a pointer won't fit in a `UInt64`. + writeInt(&buf, UInt64(bitPattern: Int64(Int(bitPattern: lower(value))))) + } +} + + + + +public func FfiConverterTypePermissionResponse_lift(_ pointer: UnsafeMutableRawPointer) throws -> PermissionResponse { + return try FfiConverterTypePermissionResponse.lift(pointer) +} + +public func FfiConverterTypePermissionResponse_lower(_ value: PermissionResponse) -> UnsafeMutableRawPointer { + return FfiConverterTypePermissionResponse.lower(value) +} + + + + +public protocol RequestedFieldProtocol : AnyObject { + + /** + * Return the field name + */ + func name() -> String + + /** + * Return the purpose of the requested field. + */ + func purpose() -> String? + + /** + * Return the field required status + */ + func required() -> Bool + + /** + * Return the field retained status + */ + func retained() -> Bool + +} + +open class RequestedField: + RequestedFieldProtocol { + fileprivate let pointer: UnsafeMutableRawPointer! + + /// Used to instantiate a [FFIObject] without an actual pointer, for fakes in tests, mostly. + public struct NoPointer { + public init() {} + } + + // TODO: We'd like this to be `private` but for Swifty reasons, + // we can't implement `FfiConverter` without making this `required` and we can't + // make it `required` without making it `public`. + required public init(unsafeFromRawPointer pointer: UnsafeMutableRawPointer) { + self.pointer = pointer + } + + /// This constructor can be used to instantiate a fake object. + /// - Parameter noPointer: Placeholder value so we can have a constructor separate from the default empty one that may be implemented for classes extending [FFIObject]. + /// + /// - Warning: + /// Any object instantiated with this constructor cannot be passed to an actual Rust-backed object. Since there isn't a backing [Pointer] the FFI lower functions will crash. + public init(noPointer: NoPointer) { + self.pointer = nil + } + + public func uniffiClonePointer() -> UnsafeMutableRawPointer { + return try! rustCall { uniffi_mobile_sdk_rs_fn_clone_requestedfield(self.pointer, $0) } + } + // No primary constructor declared for this class. + + deinit { + guard let pointer = pointer else { + return + } + + try! rustCall { uniffi_mobile_sdk_rs_fn_free_requestedfield(pointer, $0) } + } + + + + + /** + * Return the field name + */ +open func name() -> String { + return try! FfiConverterString.lift(try! rustCall() { + uniffi_mobile_sdk_rs_fn_method_requestedfield_name(self.uniffiClonePointer(),$0 + ) +}) +} + + /** + * Return the purpose of the requested field. + */ +open func purpose() -> String? { + return try! FfiConverterOptionString.lift(try! rustCall() { + uniffi_mobile_sdk_rs_fn_method_requestedfield_purpose(self.uniffiClonePointer(),$0 + ) +}) +} + + /** + * Return the field required status + */ +open func required() -> Bool { + return try! FfiConverterBool.lift(try! rustCall() { + uniffi_mobile_sdk_rs_fn_method_requestedfield_required(self.uniffiClonePointer(),$0 + ) +}) +} + + /** + * Return the field retained status + */ +open func retained() -> Bool { + return try! FfiConverterBool.lift(try! rustCall() { + uniffi_mobile_sdk_rs_fn_method_requestedfield_retained(self.uniffiClonePointer(),$0 + ) +}) +} + + +} + +public struct FfiConverterTypeRequestedField: FfiConverter { + + typealias FfiType = UnsafeMutableRawPointer + typealias SwiftType = RequestedField + + public static func lift(_ pointer: UnsafeMutableRawPointer) throws -> RequestedField { + return RequestedField(unsafeFromRawPointer: pointer) + } + + public static func lower(_ value: RequestedField) -> UnsafeMutableRawPointer { + return value.uniffiClonePointer() + } + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> RequestedField { + let v: UInt64 = try readInt(&buf) + // The Rust code won't compile if a pointer won't fit in a UInt64. + // We have to go via `UInt` because that's the thing that's the size of a pointer. + let ptr = UnsafeMutableRawPointer(bitPattern: UInt(truncatingIfNeeded: v)) + if (ptr == nil) { + throw UniffiInternalError.unexpectedNullPointer + } + return try lift(ptr!) + } + + public static func write(_ value: RequestedField, into buf: inout [UInt8]) { + // This fiddling is because `Int` is the thing that's the same size as a pointer. + // The Rust code won't compile if a pointer won't fit in a `UInt64`. + writeInt(&buf, UInt64(bitPattern: Int64(Int(bitPattern: lower(value))))) + } +} + + + + +public func FfiConverterTypeRequestedField_lift(_ pointer: UnsafeMutableRawPointer) throws -> RequestedField { + return try FfiConverterTypeRequestedField.lift(pointer) +} + +public func FfiConverterTypeRequestedField_lower(_ value: RequestedField) -> UnsafeMutableRawPointer { + return FfiConverterTypeRequestedField.lower(value) +} + + + + +/** + * Interface: StorageManagerInterface + * + * The StorageManagerInterface provides access to functions defined in Kotlin and Swift for + * managing persistent storage on the device. + * + * When dealing with UniFFI exported functions and objects, this will need to be Boxed as: + * Box + * + * We use the older callback_interface to keep the required version level of our Android API + * low. + */ +public protocol StorageManagerInterface : AnyObject { + + /** + * Function: add + * + * Adds a key-value pair to storage. Should the key already exist, the value will be + * replaced + * + * Arguments: + * key - The key to add + * value - The value to add under the key. + */ + func add(key: Key, value: Value) throws + + /** + * Function: get + * + * Callback function pointer to native (kotlin/swift) code for + * getting a key. + */ + func get(key: Key) throws -> Value? + + /** + * Function: list + * + * Callback function pointer for listing available keys. + */ + func list() throws -> [Key] + + /** + * Function: remove + * + * Callback function pointer to native (kotlin/swift) code for + * removing a key. This referenced function MUST be idempotent. In + * particular, it must treat removing a non-existent key as a normal and + * expected circumstance, simply returning () and not an error. + */ + func remove(key: Key) throws + +} + +/** + * Interface: StorageManagerInterface + * + * The StorageManagerInterface provides access to functions defined in Kotlin and Swift for + * managing persistent storage on the device. + * + * When dealing with UniFFI exported functions and objects, this will need to be Boxed as: + * Box + * + * We use the older callback_interface to keep the required version level of our Android API + * low. + */ +open class StorageManagerInterfaceImpl: + StorageManagerInterface { + fileprivate let pointer: UnsafeMutableRawPointer! + + /// Used to instantiate a [FFIObject] without an actual pointer, for fakes in tests, mostly. + public struct NoPointer { + public init() {} + } + + // TODO: We'd like this to be `private` but for Swifty reasons, + // we can't implement `FfiConverter` without making this `required` and we can't + // make it `required` without making it `public`. + required public init(unsafeFromRawPointer pointer: UnsafeMutableRawPointer) { + self.pointer = pointer + } + + /// This constructor can be used to instantiate a fake object. + /// - Parameter noPointer: Placeholder value so we can have a constructor separate from the default empty one that may be implemented for classes extending [FFIObject]. + /// + /// - Warning: + /// Any object instantiated with this constructor cannot be passed to an actual Rust-backed object. Since there isn't a backing [Pointer] the FFI lower functions will crash. + public init(noPointer: NoPointer) { + self.pointer = nil + } + + public func uniffiClonePointer() -> UnsafeMutableRawPointer { + return try! rustCall { uniffi_mobile_sdk_rs_fn_clone_storagemanagerinterface(self.pointer, $0) } + } + // No primary constructor declared for this class. + + deinit { + guard let pointer = pointer else { + return + } + + try! rustCall { uniffi_mobile_sdk_rs_fn_free_storagemanagerinterface(pointer, $0) } + } + + + + + /** + * Function: add + * + * Adds a key-value pair to storage. Should the key already exist, the value will be + * replaced + * + * Arguments: + * key - The key to add + * value - The value to add under the key. + */ +open func add(key: Key, value: Value)throws {try rustCallWithError(FfiConverterTypeStorageManagerError.lift) { + uniffi_mobile_sdk_rs_fn_method_storagemanagerinterface_add(self.uniffiClonePointer(), + FfiConverterTypeKey.lower(key), + FfiConverterTypeValue.lower(value),$0 + ) +} +} + + /** + * Function: get + * + * Callback function pointer to native (kotlin/swift) code for + * getting a key. + */ +open func get(key: Key)throws -> Value? { + return try FfiConverterOptionTypeValue.lift(try rustCallWithError(FfiConverterTypeStorageManagerError.lift) { + uniffi_mobile_sdk_rs_fn_method_storagemanagerinterface_get(self.uniffiClonePointer(), + FfiConverterTypeKey.lower(key),$0 + ) +}) +} + + /** + * Function: list + * + * Callback function pointer for listing available keys. + */ +open func list()throws -> [Key] { return try FfiConverterSequenceTypeKey.lift(try rustCallWithError(FfiConverterTypeStorageManagerError.lift) { uniffi_mobile_sdk_rs_fn_method_storagemanagerinterface_list(self.uniffiClonePointer(),$0 ) @@ -3489,20 +4166,198 @@ open class TokenResponse: } -public struct FfiConverterTypeTokenResponse: FfiConverter { +public struct FfiConverterTypeTokenResponse: FfiConverter { + + typealias FfiType = UnsafeMutableRawPointer + typealias SwiftType = TokenResponse + + public static func lift(_ pointer: UnsafeMutableRawPointer) throws -> TokenResponse { + return TokenResponse(unsafeFromRawPointer: pointer) + } + + public static func lower(_ value: TokenResponse) -> UnsafeMutableRawPointer { + return value.uniffiClonePointer() + } + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> TokenResponse { + let v: UInt64 = try readInt(&buf) + // The Rust code won't compile if a pointer won't fit in a UInt64. + // We have to go via `UInt` because that's the thing that's the size of a pointer. + let ptr = UnsafeMutableRawPointer(bitPattern: UInt(truncatingIfNeeded: v)) + if (ptr == nil) { + throw UniffiInternalError.unexpectedNullPointer + } + return try lift(ptr!) + } + + public static func write(_ value: TokenResponse, into buf: inout [UInt8]) { + // This fiddling is because `Int` is the thing that's the same size as a pointer. + // The Rust code won't compile if a pointer won't fit in a `UInt64`. + writeInt(&buf, UInt64(bitPattern: Int64(Int(bitPattern: lower(value))))) + } +} + + + + +public func FfiConverterTypeTokenResponse_lift(_ pointer: UnsafeMutableRawPointer) throws -> TokenResponse { + return try FfiConverterTypeTokenResponse.lift(pointer) +} + +public func FfiConverterTypeTokenResponse_lower(_ value: TokenResponse) -> UnsafeMutableRawPointer { + return FfiConverterTypeTokenResponse.lower(value) +} + + + + +public protocol Vcdm2SdJwtProtocol : AnyObject { + + /** + * Return the ID for the SdJwt instance. + */ + func id() -> Uuid + + /** + * Return the key alias for the credential + */ + func keyAlias() -> KeyAlias? + + /** + * Return the revealed claims as a UTF-8 encoded JSON string. + */ + func revealedClaimsAsJsonString() throws -> String + + /** + * The type of this credential. Note that if there is more than one type (i.e. `types()` + * returns more than one value), then the types will be concatenated with a "+". + */ + func type() -> CredentialType + +} + +open class Vcdm2SdJwt: + Vcdm2SdJwtProtocol { + fileprivate let pointer: UnsafeMutableRawPointer! + + /// Used to instantiate a [FFIObject] without an actual pointer, for fakes in tests, mostly. + public struct NoPointer { + public init() {} + } + + // TODO: We'd like this to be `private` but for Swifty reasons, + // we can't implement `FfiConverter` without making this `required` and we can't + // make it `required` without making it `public`. + required public init(unsafeFromRawPointer pointer: UnsafeMutableRawPointer) { + self.pointer = pointer + } + + /// This constructor can be used to instantiate a fake object. + /// - Parameter noPointer: Placeholder value so we can have a constructor separate from the default empty one that may be implemented for classes extending [FFIObject]. + /// + /// - Warning: + /// Any object instantiated with this constructor cannot be passed to an actual Rust-backed object. Since there isn't a backing [Pointer] the FFI lower functions will crash. + public init(noPointer: NoPointer) { + self.pointer = nil + } + + public func uniffiClonePointer() -> UnsafeMutableRawPointer { + return try! rustCall { uniffi_mobile_sdk_rs_fn_clone_vcdm2sdjwt(self.pointer, $0) } + } + // No primary constructor declared for this class. + + deinit { + guard let pointer = pointer else { + return + } + + try! rustCall { uniffi_mobile_sdk_rs_fn_free_vcdm2sdjwt(pointer, $0) } + } + + + /** + * Create a new SdJwt instance from a compact SD-JWS string. + */ +public static func newFromCompactSdJwt(input: String)throws -> Vcdm2SdJwt { + return try FfiConverterTypeVCDM2SdJwt.lift(try rustCallWithError(FfiConverterTypeSdJwtError.lift) { + uniffi_mobile_sdk_rs_fn_constructor_vcdm2sdjwt_new_from_compact_sd_jwt( + FfiConverterString.lower(input),$0 + ) +}) +} + + /** + * Create a new SdJwt instance from a compact SD-JWS string with a provided key alias. + */ +public static func newFromCompactSdJwtWithKey(input: String, keyAlias: KeyAlias)throws -> Vcdm2SdJwt { + return try FfiConverterTypeVCDM2SdJwt.lift(try rustCallWithError(FfiConverterTypeSdJwtError.lift) { + uniffi_mobile_sdk_rs_fn_constructor_vcdm2sdjwt_new_from_compact_sd_jwt_with_key( + FfiConverterString.lower(input), + FfiConverterTypeKeyAlias.lower(keyAlias),$0 + ) +}) +} + + + + /** + * Return the ID for the SdJwt instance. + */ +open func id() -> Uuid { + return try! FfiConverterTypeUuid.lift(try! rustCall() { + uniffi_mobile_sdk_rs_fn_method_vcdm2sdjwt_id(self.uniffiClonePointer(),$0 + ) +}) +} + + /** + * Return the key alias for the credential + */ +open func keyAlias() -> KeyAlias? { + return try! FfiConverterOptionTypeKeyAlias.lift(try! rustCall() { + uniffi_mobile_sdk_rs_fn_method_vcdm2sdjwt_key_alias(self.uniffiClonePointer(),$0 + ) +}) +} + + /** + * Return the revealed claims as a UTF-8 encoded JSON string. + */ +open func revealedClaimsAsJsonString()throws -> String { + return try FfiConverterString.lift(try rustCallWithError(FfiConverterTypeSdJwtError.lift) { + uniffi_mobile_sdk_rs_fn_method_vcdm2sdjwt_revealed_claims_as_json_string(self.uniffiClonePointer(),$0 + ) +}) +} + + /** + * The type of this credential. Note that if there is more than one type (i.e. `types()` + * returns more than one value), then the types will be concatenated with a "+". + */ +open func type() -> CredentialType { + return try! FfiConverterTypeCredentialType.lift(try! rustCall() { + uniffi_mobile_sdk_rs_fn_method_vcdm2sdjwt_type(self.uniffiClonePointer(),$0 + ) +}) +} + + +} + +public struct FfiConverterTypeVCDM2SdJwt: FfiConverter { typealias FfiType = UnsafeMutableRawPointer - typealias SwiftType = TokenResponse + typealias SwiftType = Vcdm2SdJwt - public static func lift(_ pointer: UnsafeMutableRawPointer) throws -> TokenResponse { - return TokenResponse(unsafeFromRawPointer: pointer) + public static func lift(_ pointer: UnsafeMutableRawPointer) throws -> Vcdm2SdJwt { + return Vcdm2SdJwt(unsafeFromRawPointer: pointer) } - public static func lower(_ value: TokenResponse) -> UnsafeMutableRawPointer { + public static func lower(_ value: Vcdm2SdJwt) -> UnsafeMutableRawPointer { return value.uniffiClonePointer() } - public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> TokenResponse { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> Vcdm2SdJwt { let v: UInt64 = try readInt(&buf) // The Rust code won't compile if a pointer won't fit in a UInt64. // We have to go via `UInt` because that's the thing that's the size of a pointer. @@ -3513,7 +4368,7 @@ public struct FfiConverterTypeTokenResponse: FfiConverter { return try lift(ptr!) } - public static func write(_ value: TokenResponse, into buf: inout [UInt8]) { + public static func write(_ value: Vcdm2SdJwt, into buf: inout [UInt8]) { // This fiddling is because `Int` is the thing that's the same size as a pointer. // The Rust code won't compile if a pointer won't fit in a `UInt64`. writeInt(&buf, UInt64(bitPattern: Int64(Int(bitPattern: lower(value))))) @@ -3523,12 +4378,12 @@ public struct FfiConverterTypeTokenResponse: FfiConverter { -public func FfiConverterTypeTokenResponse_lift(_ pointer: UnsafeMutableRawPointer) throws -> TokenResponse { - return try FfiConverterTypeTokenResponse.lift(pointer) +public func FfiConverterTypeVCDM2SdJwt_lift(_ pointer: UnsafeMutableRawPointer) throws -> Vcdm2SdJwt { + return try FfiConverterTypeVCDM2SdJwt.lift(pointer) } -public func FfiConverterTypeTokenResponse_lower(_ value: TokenResponse) -> UnsafeMutableRawPointer { - return FfiConverterTypeTokenResponse.lower(value) +public func FfiConverterTypeVCDM2SdJwt_lower(_ value: Vcdm2SdJwt) -> UnsafeMutableRawPointer { + return FfiConverterTypeVCDM2SdJwt.lower(value) } @@ -4524,7 +5379,14 @@ public enum CredentialDecodingError { ) case JwtVc(JwtVcInitError ) - case UnsupportedCredentialFormat + case SdJwt(SdJwtError + ) + case UnsupportedCredentialFormat(String + ) + case Serialization(String + ) + case Deserialization(String + ) } @@ -4547,7 +5409,18 @@ public struct FfiConverterTypeCredentialDecodingError: FfiConverterRustBuffer { case 3: return .JwtVc( try FfiConverterTypeJwtVcInitError.read(from: &buf) ) - case 4: return .UnsupportedCredentialFormat + case 4: return .SdJwt( + try FfiConverterTypeSdJwtError.read(from: &buf) + ) + case 5: return .UnsupportedCredentialFormat( + try FfiConverterString.read(from: &buf) + ) + case 6: return .Serialization( + try FfiConverterString.read(from: &buf) + ) + case 7: return .Deserialization( + try FfiConverterString.read(from: &buf) + ) default: throw UniffiInternalError.unexpectedEnumCase } @@ -4575,9 +5448,25 @@ public struct FfiConverterTypeCredentialDecodingError: FfiConverterRustBuffer { FfiConverterTypeJwtVcInitError.write(v1, into: &buf) - case .UnsupportedCredentialFormat: + case let .SdJwt(v1): writeInt(&buf, Int32(4)) + FfiConverterTypeSdJwtError.write(v1, into: &buf) + + + case let .UnsupportedCredentialFormat(v1): + writeInt(&buf, Int32(5)) + FfiConverterString.write(v1, into: &buf) + + + case let .Serialization(v1): + writeInt(&buf, Int32(6)) + FfiConverterString.write(v1, into: &buf) + + case let .Deserialization(v1): + writeInt(&buf, Int32(7)) + FfiConverterString.write(v1, into: &buf) + } } } @@ -4600,6 +5489,8 @@ public enum CredentialEncodingError { ) case JsonVc(JsonVcEncodingError ) + case SdJwt(SdJwtError + ) } @@ -4619,6 +5510,9 @@ public struct FfiConverterTypeCredentialEncodingError: FfiConverterRustBuffer { case 2: return .JsonVc( try FfiConverterTypeJsonVcEncodingError.read(from: &buf) ) + case 3: return .SdJwt( + try FfiConverterTypeSdJwtError.read(from: &buf) + ) default: throw UniffiInternalError.unexpectedEnumCase } @@ -4640,6 +5534,11 @@ public struct FfiConverterTypeCredentialEncodingError: FfiConverterRustBuffer { writeInt(&buf, Int32(2)) FfiConverterTypeJsonVcEncodingError.write(v1, into: &buf) + + case let .SdJwt(v1): + writeInt(&buf, Int32(3)) + FfiConverterTypeSdJwtError.write(v1, into: &buf) + } } } @@ -4665,6 +5564,7 @@ public enum CredentialFormat { case jwtVcJson case jwtVcJsonLd case ldpVc + case vcdm2SdJwt case other(String ) } @@ -4685,7 +5585,9 @@ public struct FfiConverterTypeCredentialFormat: FfiConverterRustBuffer { case 4: return .ldpVc - case 5: return .other(try FfiConverterString.read(from: &buf) + case 5: return .vcdm2SdJwt + + case 6: return .other(try FfiConverterString.read(from: &buf) ) default: throw UniffiInternalError.unexpectedEnumCase @@ -4712,8 +5614,12 @@ public struct FfiConverterTypeCredentialFormat: FfiConverterRustBuffer { writeInt(&buf, Int32(4)) - case let .other(v1): + case .vcdm2SdJwt: writeInt(&buf, Int32(5)) + + + case let .other(v1): + writeInt(&buf, Int32(6)) FfiConverterString.write(v1, into: &buf) } @@ -4736,6 +5642,68 @@ extension CredentialFormat: Equatable, Hashable {} +public enum CredentialPresentationError { + + + + case Decoding(String + ) + case JsonPath(String + ) +} + + +public struct FfiConverterTypeCredentialPresentationError: FfiConverterRustBuffer { + typealias SwiftType = CredentialPresentationError + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> CredentialPresentationError { + let variant: Int32 = try readInt(&buf) + switch variant { + + + + + case 1: return .Decoding( + try FfiConverterString.read(from: &buf) + ) + case 2: return .JsonPath( + try FfiConverterString.read(from: &buf) + ) + + default: throw UniffiInternalError.unexpectedEnumCase + } + } + + public static func write(_ value: CredentialPresentationError, into buf: inout [UInt8]) { + switch value { + + + + + + case let .Decoding(v1): + writeInt(&buf, Int32(1)) + FfiConverterString.write(v1, into: &buf) + + + case let .JsonPath(v1): + writeInt(&buf, Int32(2)) + FfiConverterString.write(v1, into: &buf) + + } + } +} + + +extension CredentialPresentationError: Equatable, Hashable {} + +extension CredentialPresentationError: Foundation.LocalizedError { + public var errorDescription: String? { + String(reflecting: self) + } +} + + public enum DidError { @@ -5595,29 +6563,309 @@ public struct FfiConverterTypeMdocInitError: FfiConverterRustBuffer { writeInt(&buf, Int32(4)) - case .IssuerAuthPayloadDecoding: - writeInt(&buf, Int32(5)) + case .IssuerAuthPayloadDecoding: + writeInt(&buf, Int32(5)) + + + case .KeyAliasMissing: + writeInt(&buf, Int32(6)) + + + case .NamespacesMissing: + writeInt(&buf, Int32(7)) + + + case .DocumentUtf8Decoding: + writeInt(&buf, Int32(8)) + + } + } +} + + +extension MdocInitError: Equatable, Hashable {} + +extension MdocInitError: Foundation.LocalizedError { + public var errorDescription: String? { + String(reflecting: self) + } +} + + +/** + * The [OID4VPError] enum represents the errors that can occur + * when using the oid4vp foreign library. + */ +public enum Oid4vpError { + + + + case UnexpectedUniFfiCallbackError(String + ) + case RequestValidation(String + ) + case PresentationDefinitionResolution(String + ) + case Token(String + ) + case UnsupportedResponseMode(String + ) + case ResponseSubmission(String + ) + case CredentialCallback(String + ) + case PresentationSubmissionCreation(String + ) + case InvalidDidUrl(String + ) + case DidKeyGenerateUrl(String + ) + case CredentialEncodingError(String + ) + case CredentialDecodingError(String + ) + case JsonSyntaxParse(String + ) + case VdcCollection(VdcCollectionError + ) + case HttpClientInitialization(String + ) + case SigningAlgorithmNotFound(String + ) + case InvalidClientIdScheme(String + ) + case InputDescriptorNotFound + case VpTokenParse(String + ) + case VpTokenCreate(String + ) + case JwkParse(String + ) + case VdcCollectionNotInitialized + case AuthorizationRequestNotFound + case RequestSignerNotFound + case MetadataInitialization(String + ) +} + + +public struct FfiConverterTypeOID4VPError: FfiConverterRustBuffer { + typealias SwiftType = Oid4vpError + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> Oid4vpError { + let variant: Int32 = try readInt(&buf) + switch variant { + + + + + case 1: return .UnexpectedUniFfiCallbackError( + try FfiConverterString.read(from: &buf) + ) + case 2: return .RequestValidation( + try FfiConverterString.read(from: &buf) + ) + case 3: return .PresentationDefinitionResolution( + try FfiConverterString.read(from: &buf) + ) + case 4: return .Token( + try FfiConverterString.read(from: &buf) + ) + case 5: return .UnsupportedResponseMode( + try FfiConverterString.read(from: &buf) + ) + case 6: return .ResponseSubmission( + try FfiConverterString.read(from: &buf) + ) + case 7: return .CredentialCallback( + try FfiConverterString.read(from: &buf) + ) + case 8: return .PresentationSubmissionCreation( + try FfiConverterString.read(from: &buf) + ) + case 9: return .InvalidDidUrl( + try FfiConverterString.read(from: &buf) + ) + case 10: return .DidKeyGenerateUrl( + try FfiConverterString.read(from: &buf) + ) + case 11: return .CredentialEncodingError( + try FfiConverterString.read(from: &buf) + ) + case 12: return .CredentialDecodingError( + try FfiConverterString.read(from: &buf) + ) + case 13: return .JsonSyntaxParse( + try FfiConverterString.read(from: &buf) + ) + case 14: return .VdcCollection( + try FfiConverterTypeVdcCollectionError.read(from: &buf) + ) + case 15: return .HttpClientInitialization( + try FfiConverterString.read(from: &buf) + ) + case 16: return .SigningAlgorithmNotFound( + try FfiConverterString.read(from: &buf) + ) + case 17: return .InvalidClientIdScheme( + try FfiConverterString.read(from: &buf) + ) + case 18: return .InputDescriptorNotFound + case 19: return .VpTokenParse( + try FfiConverterString.read(from: &buf) + ) + case 20: return .VpTokenCreate( + try FfiConverterString.read(from: &buf) + ) + case 21: return .JwkParse( + try FfiConverterString.read(from: &buf) + ) + case 22: return .VdcCollectionNotInitialized + case 23: return .AuthorizationRequestNotFound + case 24: return .RequestSignerNotFound + case 25: return .MetadataInitialization( + try FfiConverterString.read(from: &buf) + ) + + default: throw UniffiInternalError.unexpectedEnumCase + } + } + + public static func write(_ value: Oid4vpError, into buf: inout [UInt8]) { + switch value { + + + + + + case let .UnexpectedUniFfiCallbackError(v1): + writeInt(&buf, Int32(1)) + FfiConverterString.write(v1, into: &buf) + + + case let .RequestValidation(v1): + writeInt(&buf, Int32(2)) + FfiConverterString.write(v1, into: &buf) + + + case let .PresentationDefinitionResolution(v1): + writeInt(&buf, Int32(3)) + FfiConverterString.write(v1, into: &buf) + + + case let .Token(v1): + writeInt(&buf, Int32(4)) + FfiConverterString.write(v1, into: &buf) + + + case let .UnsupportedResponseMode(v1): + writeInt(&buf, Int32(5)) + FfiConverterString.write(v1, into: &buf) + + + case let .ResponseSubmission(v1): + writeInt(&buf, Int32(6)) + FfiConverterString.write(v1, into: &buf) + + + case let .CredentialCallback(v1): + writeInt(&buf, Int32(7)) + FfiConverterString.write(v1, into: &buf) + + + case let .PresentationSubmissionCreation(v1): + writeInt(&buf, Int32(8)) + FfiConverterString.write(v1, into: &buf) + + + case let .InvalidDidUrl(v1): + writeInt(&buf, Int32(9)) + FfiConverterString.write(v1, into: &buf) + + + case let .DidKeyGenerateUrl(v1): + writeInt(&buf, Int32(10)) + FfiConverterString.write(v1, into: &buf) + + + case let .CredentialEncodingError(v1): + writeInt(&buf, Int32(11)) + FfiConverterString.write(v1, into: &buf) + + + case let .CredentialDecodingError(v1): + writeInt(&buf, Int32(12)) + FfiConverterString.write(v1, into: &buf) + + + case let .JsonSyntaxParse(v1): + writeInt(&buf, Int32(13)) + FfiConverterString.write(v1, into: &buf) + + + case let .VdcCollection(v1): + writeInt(&buf, Int32(14)) + FfiConverterTypeVdcCollectionError.write(v1, into: &buf) + + + case let .HttpClientInitialization(v1): + writeInt(&buf, Int32(15)) + FfiConverterString.write(v1, into: &buf) + + + case let .SigningAlgorithmNotFound(v1): + writeInt(&buf, Int32(16)) + FfiConverterString.write(v1, into: &buf) + + + case let .InvalidClientIdScheme(v1): + writeInt(&buf, Int32(17)) + FfiConverterString.write(v1, into: &buf) + + + case .InputDescriptorNotFound: + writeInt(&buf, Int32(18)) + + + case let .VpTokenParse(v1): + writeInt(&buf, Int32(19)) + FfiConverterString.write(v1, into: &buf) + + + case let .VpTokenCreate(v1): + writeInt(&buf, Int32(20)) + FfiConverterString.write(v1, into: &buf) + + + case let .JwkParse(v1): + writeInt(&buf, Int32(21)) + FfiConverterString.write(v1, into: &buf) + + case .VdcCollectionNotInitialized: + writeInt(&buf, Int32(22)) - case .KeyAliasMissing: - writeInt(&buf, Int32(6)) + case .AuthorizationRequestNotFound: + writeInt(&buf, Int32(23)) - case .NamespacesMissing: - writeInt(&buf, Int32(7)) + case .RequestSignerNotFound: + writeInt(&buf, Int32(24)) - case .DocumentUtf8Decoding: - writeInt(&buf, Int32(8)) + case let .MetadataInitialization(v1): + writeInt(&buf, Int32(25)) + FfiConverterString.write(v1, into: &buf) + } } } -extension MdocInitError: Equatable, Hashable {} +extension Oid4vpError: Equatable, Hashable {} -extension MdocInitError: Foundation.LocalizedError { +extension Oid4vpError: Foundation.LocalizedError { public var errorDescription: String? { String(reflecting: self) } @@ -5819,6 +7067,123 @@ extension Outcome: Equatable, Hashable {} +public enum PermissionRequestError { + + + + /** + * Permission denied for requested presentation. + */ + case PermissionDenied + /** + * RwLock error + */ + case RwLockError + /** + * Credential not found for input descriptor id. + */ + case CredentialNotFound(String + ) + /** + * Input descriptor not found for input descriptor id. + */ + case InputDescriptorNotFound(String + ) + /** + * Invalid selected credential for requested field. Selected + * credential does not match optional credentials. + */ + case InvalidSelectedCredential(String,String + ) + /** + * Credential Presentation Error + * + * failed to present the credential. + */ + case CredentialPresentation(String + ) +} + + +public struct FfiConverterTypePermissionRequestError: FfiConverterRustBuffer { + typealias SwiftType = PermissionRequestError + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> PermissionRequestError { + let variant: Int32 = try readInt(&buf) + switch variant { + + + + + case 1: return .PermissionDenied + case 2: return .RwLockError + case 3: return .CredentialNotFound( + try FfiConverterString.read(from: &buf) + ) + case 4: return .InputDescriptorNotFound( + try FfiConverterString.read(from: &buf) + ) + case 5: return .InvalidSelectedCredential( + try FfiConverterString.read(from: &buf), + try FfiConverterString.read(from: &buf) + ) + case 6: return .CredentialPresentation( + try FfiConverterString.read(from: &buf) + ) + + default: throw UniffiInternalError.unexpectedEnumCase + } + } + + public static func write(_ value: PermissionRequestError, into buf: inout [UInt8]) { + switch value { + + + + + + case .PermissionDenied: + writeInt(&buf, Int32(1)) + + + case .RwLockError: + writeInt(&buf, Int32(2)) + + + case let .CredentialNotFound(v1): + writeInt(&buf, Int32(3)) + FfiConverterString.write(v1, into: &buf) + + + case let .InputDescriptorNotFound(v1): + writeInt(&buf, Int32(4)) + FfiConverterString.write(v1, into: &buf) + + + case let .InvalidSelectedCredential(v1,v2): + writeInt(&buf, Int32(5)) + FfiConverterString.write(v1, into: &buf) + FfiConverterString.write(v2, into: &buf) + + + case let .CredentialPresentation(v1): + writeInt(&buf, Int32(6)) + FfiConverterString.write(v1, into: &buf) + + } + } +} + + +extension PermissionRequestError: Equatable, Hashable {} + +extension PermissionRequestError: Foundation.LocalizedError { + public var errorDescription: String? { + String(reflecting: self) + } +} + + public enum PopError { @@ -6013,60 +7378,98 @@ extension ResponseError: Foundation.LocalizedError { } -public enum SdJwtVcError { +public enum SdJwtError { - case JwtDecoding - case InvalidSdJwt - case Serialization + case SdJwtVcInitError(String + ) + case SdJwtDecoding(String + ) + case InvalidSdJwt(String + ) + case Serialization(String + ) + case CredentialEncoding(String + ) + case CredentialClaimMissing } -public struct FfiConverterTypeSdJwtVcError: FfiConverterRustBuffer { - typealias SwiftType = SdJwtVcError +public struct FfiConverterTypeSdJwtError: FfiConverterRustBuffer { + typealias SwiftType = SdJwtError - public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> SdJwtVcError { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> SdJwtError { let variant: Int32 = try readInt(&buf) switch variant { - case 1: return .JwtDecoding - case 2: return .InvalidSdJwt - case 3: return .Serialization + case 1: return .SdJwtVcInitError( + try FfiConverterString.read(from: &buf) + ) + case 2: return .SdJwtDecoding( + try FfiConverterString.read(from: &buf) + ) + case 3: return .InvalidSdJwt( + try FfiConverterString.read(from: &buf) + ) + case 4: return .Serialization( + try FfiConverterString.read(from: &buf) + ) + case 5: return .CredentialEncoding( + try FfiConverterString.read(from: &buf) + ) + case 6: return .CredentialClaimMissing default: throw UniffiInternalError.unexpectedEnumCase } } - public static func write(_ value: SdJwtVcError, into buf: inout [UInt8]) { + public static func write(_ value: SdJwtError, into buf: inout [UInt8]) { switch value { - case .JwtDecoding: + case let .SdJwtVcInitError(v1): writeInt(&buf, Int32(1)) + FfiConverterString.write(v1, into: &buf) + - - case .InvalidSdJwt: + case let .SdJwtDecoding(v1): writeInt(&buf, Int32(2)) + FfiConverterString.write(v1, into: &buf) + - - case .Serialization: + case let .InvalidSdJwt(v1): writeInt(&buf, Int32(3)) + FfiConverterString.write(v1, into: &buf) + + + case let .Serialization(v1): + writeInt(&buf, Int32(4)) + FfiConverterString.write(v1, into: &buf) + + + case let .CredentialEncoding(v1): + writeInt(&buf, Int32(5)) + FfiConverterString.write(v1, into: &buf) + + + case .CredentialClaimMissing: + writeInt(&buf, Int32(6)) } } } -extension SdJwtVcError: Equatable, Hashable {} +extension SdJwtError: Equatable, Hashable {} -extension SdJwtVcError: Foundation.LocalizedError { +extension SdJwtError: Foundation.LocalizedError { public var errorDescription: String? { String(reflecting: self) } @@ -6839,6 +8242,27 @@ fileprivate struct FfiConverterOptionTypeMdoc: FfiConverterRustBuffer { } } +fileprivate struct FfiConverterOptionTypeVCDM2SdJwt: FfiConverterRustBuffer { + typealias SwiftType = Vcdm2SdJwt? + + public static func write(_ value: SwiftType, into buf: inout [UInt8]) { + guard let value = value else { + writeInt(&buf, Int8(0)) + return + } + writeInt(&buf, Int8(1)) + FfiConverterTypeVCDM2SdJwt.write(value, into: &buf) + } + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> SwiftType { + switch try readInt(&buf) as Int8 { + case 0: return nil + case 1: return try FfiConverterTypeVCDM2SdJwt.read(from: &buf) + default: throw UniffiInternalError.unexpectedOptionalTag + } + } +} + fileprivate struct FfiConverterOptionTypeCredential: FfiConverterRustBuffer { typealias SwiftType = Credential? @@ -6944,6 +8368,27 @@ fileprivate struct FfiConverterOptionTypeKeyAlias: FfiConverterRustBuffer { } } +fileprivate struct FfiConverterOptionTypeUrl: FfiConverterRustBuffer { + typealias SwiftType = Url? + + public static func write(_ value: SwiftType, into buf: inout [UInt8]) { + guard let value = value else { + writeInt(&buf, Int8(0)) + return + } + writeInt(&buf, Int8(1)) + FfiConverterTypeUrl.write(value, into: &buf) + } + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> SwiftType { + switch try readInt(&buf) as Int8 { + case 0: return nil + case 1: return try FfiConverterTypeUrl.read(from: &buf) + default: throw UniffiInternalError.unexpectedOptionalTag + } + } +} + fileprivate struct FfiConverterOptionTypeValue: FfiConverterRustBuffer { typealias SwiftType = Value? @@ -6987,6 +8432,50 @@ fileprivate struct FfiConverterSequenceString: FfiConverterRustBuffer { } } +fileprivate struct FfiConverterSequenceTypeParsedCredential: FfiConverterRustBuffer { + typealias SwiftType = [ParsedCredential] + + public static func write(_ value: [ParsedCredential], into buf: inout [UInt8]) { + let len = Int32(value.count) + writeInt(&buf, len) + for item in value { + FfiConverterTypeParsedCredential.write(item, into: &buf) + } + } + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> [ParsedCredential] { + let len: Int32 = try readInt(&buf) + var seq = [ParsedCredential]() + seq.reserveCapacity(Int(len)) + for _ in 0 ..< len { + seq.append(try FfiConverterTypeParsedCredential.read(from: &buf)) + } + return seq + } +} + +fileprivate struct FfiConverterSequenceTypeRequestedField: FfiConverterRustBuffer { + typealias SwiftType = [RequestedField] + + public static func write(_ value: [RequestedField], into buf: inout [UInt8]) { + let len = Int32(value.count) + writeInt(&buf, len) + for item in value { + FfiConverterTypeRequestedField.write(item, into: &buf) + } + } + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> [RequestedField] { + let len: Int32 = try readInt(&buf) + var seq = [RequestedField]() + seq.reserveCapacity(Int(len)) + for _ in 0 ..< len { + seq.append(try FfiConverterTypeRequestedField.read(from: &buf)) + } + return seq + } +} + fileprivate struct FfiConverterSequenceTypeCredentialResponse: FfiConverterRustBuffer { typealias SwiftType = [CredentialResponse] @@ -7463,6 +8952,40 @@ public func FfiConverterTypeNamespace_lower(_ value: Namespace) -> RustBuffer { +/** + * Typealias from the type name used in the UDL file to the builtin type. This + * is needed because the UDL type name is used in function/method signatures. + */ +public typealias Url = String +public struct FfiConverterTypeUrl: FfiConverter { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> Url { + return try FfiConverterString.read(from: &buf) + } + + public static func write(_ value: Url, into buf: inout [UInt8]) { + return FfiConverterString.write(value, into: &buf) + } + + public static func lift(_ value: RustBuffer) throws -> Url { + return try FfiConverterString.lift(value) + } + + public static func lower(_ value: Url) -> RustBuffer { + return FfiConverterString.lower(value) + } +} + + +public func FfiConverterTypeUrl_lift(_ value: RustBuffer) throws -> Url { + return try FfiConverterTypeUrl.lift(value) +} + +public func FfiConverterTypeUrl_lower(_ value: Url) -> RustBuffer { + return FfiConverterTypeUrl.lower(value) +} + + + /** * Typealias from the type name used in the UDL file to the builtin type. This * is needed because the UDL type name is used in function/method signatures. @@ -7642,7 +9165,7 @@ public func uniffiForeignFutureHandleCountMobileSdkRs() -> Int { UNIFFI_FOREIGN_FUTURE_HANDLE_MAP.count } public func decodeRevealSdJwt(input: String)throws -> String { - return try FfiConverterString.lift(try rustCallWithError(FfiConverterTypeSdJwtVcError.lift) { + return try FfiConverterString.lift(try rustCallWithError(FfiConverterTypeSdJwtError.lift) { uniffi_mobile_sdk_rs_fn_func_decode_reveal_sd_jwt( FfiConverterString.lower(input),$0 ) @@ -7888,7 +9411,7 @@ private var initializationResult: InitializationResult = { if bindings_contract_version != scaffolding_contract_version { return InitializationResult.contractVersionMismatch } - if (uniffi_mobile_sdk_rs_checksum_func_decode_reveal_sd_jwt() != 24154) { + if (uniffi_mobile_sdk_rs_checksum_func_decode_reveal_sd_jwt() != 34951) { return InitializationResult.apiChecksumMismatch } if (uniffi_mobile_sdk_rs_checksum_func_establish_session() != 26937) { @@ -7942,6 +9465,12 @@ private var initializationResult: InitializationResult = { if (uniffi_mobile_sdk_rs_checksum_method_asynchttpclient_http_client() != 44924) { return InitializationResult.apiChecksumMismatch } + if (uniffi_mobile_sdk_rs_checksum_method_holder_authorization_request() != 45396) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_mobile_sdk_rs_checksum_method_holder_submit_permission_response() != 37701) { + return InitializationResult.apiChecksumMismatch + } if (uniffi_mobile_sdk_rs_checksum_method_jsonvc_credential_as_json_encoded_utf8_string() != 36585) { return InitializationResult.apiChecksumMismatch } @@ -8068,6 +9597,12 @@ private var initializationResult: InitializationResult = { if (uniffi_mobile_sdk_rs_checksum_method_parsedcredential_as_mso_mdoc() != 54804) { return InitializationResult.apiChecksumMismatch } + if (uniffi_mobile_sdk_rs_checksum_method_parsedcredential_as_sd_jwt() != 23438) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_mobile_sdk_rs_checksum_method_parsedcredential_format() != 39112) { + return InitializationResult.apiChecksumMismatch + } if (uniffi_mobile_sdk_rs_checksum_method_parsedcredential_id() != 46894) { return InitializationResult.apiChecksumMismatch } @@ -8077,6 +9612,33 @@ private var initializationResult: InitializationResult = { if (uniffi_mobile_sdk_rs_checksum_method_parsedcredential_key_alias() != 52023) { return InitializationResult.apiChecksumMismatch } + if (uniffi_mobile_sdk_rs_checksum_method_parsedcredential_type() != 60750) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_mobile_sdk_rs_checksum_method_permissionrequest_create_permission_response() != 11132) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_mobile_sdk_rs_checksum_method_permissionrequest_credentials() != 38374) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_mobile_sdk_rs_checksum_method_permissionrequest_purpose() != 28780) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_mobile_sdk_rs_checksum_method_permissionrequest_requested_fields() != 48174) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_mobile_sdk_rs_checksum_method_requestedfield_name() != 28018) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_mobile_sdk_rs_checksum_method_requestedfield_purpose() != 46977) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_mobile_sdk_rs_checksum_method_requestedfield_required() != 14409) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_mobile_sdk_rs_checksum_method_requestedfield_retained() != 21715) { + return InitializationResult.apiChecksumMismatch + } if (uniffi_mobile_sdk_rs_checksum_method_storagemanagerinterface_add() != 60217) { return InitializationResult.apiChecksumMismatch } @@ -8092,6 +9654,18 @@ private var initializationResult: InitializationResult = { if (uniffi_mobile_sdk_rs_checksum_method_synchttpclient_http_client() != 53085) { return InitializationResult.apiChecksumMismatch } + if (uniffi_mobile_sdk_rs_checksum_method_vcdm2sdjwt_id() != 13770) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_mobile_sdk_rs_checksum_method_vcdm2sdjwt_key_alias() != 49360) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_mobile_sdk_rs_checksum_method_vcdm2sdjwt_revealed_claims_as_json_string() != 39703) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_mobile_sdk_rs_checksum_method_vcdm2sdjwt_type() != 50079) { + return InitializationResult.apiChecksumMismatch + } if (uniffi_mobile_sdk_rs_checksum_method_vdccollection_add() != 43160) { return InitializationResult.apiChecksumMismatch } @@ -8110,6 +9684,12 @@ private var initializationResult: InitializationResult = { if (uniffi_mobile_sdk_rs_checksum_method_vdccollection_get() != 52546) { return InitializationResult.apiChecksumMismatch } + if (uniffi_mobile_sdk_rs_checksum_constructor_holder_new() != 41846) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_mobile_sdk_rs_checksum_constructor_holder_new_with_credentials() != 3358) { + return InitializationResult.apiChecksumMismatch + } if (uniffi_mobile_sdk_rs_checksum_constructor_ihttpclient_new_async() != 55307) { return InitializationResult.apiChecksumMismatch } @@ -8164,9 +9744,18 @@ private var initializationResult: InitializationResult = { if (uniffi_mobile_sdk_rs_checksum_constructor_parsedcredential_new_mso_mdoc() != 58058) { return InitializationResult.apiChecksumMismatch } + if (uniffi_mobile_sdk_rs_checksum_constructor_parsedcredential_new_sd_jwt() != 34266) { + return InitializationResult.apiChecksumMismatch + } if (uniffi_mobile_sdk_rs_checksum_constructor_parsedcredential_parse_from_credential() != 15018) { return InitializationResult.apiChecksumMismatch } + if (uniffi_mobile_sdk_rs_checksum_constructor_vcdm2sdjwt_new_from_compact_sd_jwt() != 56155) { + return InitializationResult.apiChecksumMismatch + } + if (uniffi_mobile_sdk_rs_checksum_constructor_vcdm2sdjwt_new_from_compact_sd_jwt_with_key() != 15244) { + return InitializationResult.apiChecksumMismatch + } if (uniffi_mobile_sdk_rs_checksum_constructor_vdccollection_new() != 31236) { return InitializationResult.apiChecksumMismatch } diff --git a/README.md b/README.md index 3558141a..ddf76831 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,18 @@ security firm before the v1.0 release. Use the [`release` Github Action](https://github.com/spruceid/mobile-sdk-rs/actions/workflows/release.yml) which is a manually triggered action. +## Pre-requisites + +Ensure you have the following rust build targets installed: + +```bash +rustup target install \ + armv7-linux-androideabi \ + aarch64-linux-android \ + i686-linux-android \ + x86_64-linux-android +``` + ## Build ### Kotlin diff --git a/kotlin/gradle/wrapper/gradle-wrapper.properties b/kotlin/gradle/wrapper/gradle-wrapper.properties index 42defcc9..1af9e093 100644 --- a/kotlin/gradle/wrapper/gradle-wrapper.properties +++ b/kotlin/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip networkTimeout=10000 +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/kotlin/mobilesdkrs/build.gradle b/kotlin/mobilesdkrs/build.gradle index c2cb99e3..5b8572db 100644 --- a/kotlin/mobilesdkrs/build.gradle +++ b/kotlin/mobilesdkrs/build.gradle @@ -116,17 +116,18 @@ publishing { password = System.getenv("GITHUB_TOKEN") } } + mavenLocal() } publications { - // debug(MavenPublication) { - // groupId = 'com.spruceid.mobile.sdk.rs' - // artifactId = "mobilesdkrs" - // version = System.getenv("VERSION") - - // afterEvaluate { - // from components.release - // } - // } + debug(MavenPublication) { + groupId = 'com.spruceid.mobile.sdk.rs' + artifactId = "mobilesdkrs" + version = System.getenv("VERSION") + + afterEvaluate { + from components.release + } + } release(MavenPublication) { groupId = 'com.spruceid.mobile.sdk.rs' artifactId = "mobilesdkrs" diff --git a/kotlin/mobilesdkrs/src/androidTest/java/com/spruceid/mobile/sdk/rs/ExampleInstrumentedTest.kt b/kotlin/mobilesdkrs/src/androidTest/java/com/spruceid/mobile/sdk/rs/ExampleInstrumentedTest.kt index 86873057..0d7412c9 100644 --- a/kotlin/mobilesdkrs/src/androidTest/java/com/spruceid/mobile/sdk/rs/ExampleInstrumentedTest.kt +++ b/kotlin/mobilesdkrs/src/androidTest/java/com/spruceid/mobile/sdk/rs/ExampleInstrumentedTest.kt @@ -17,6 +17,6 @@ import org.junit.Assert.* class UniffiInstrumentedTest { @Test fun uniffiFunction() { - assertEquals(helloFfi(), "Hello from Rust!") + // Add your FFI function test here. } } diff --git a/src/common.rs b/src/common.rs index a3b919c1..e31503e0 100644 --- a/src/common.rs +++ b/src/common.rs @@ -8,6 +8,18 @@ uniffi::custom_newtype!(CredentialType, String); #[derive(Debug, Serialize, Deserialize, PartialEq, Clone)] pub struct CredentialType(pub String); +impl From for CredentialType { + fn from(s: String) -> Self { + Self(s) + } +} + +impl From for String { + fn from(cred_type: CredentialType) -> Self { + cred_type.0 + } +} + uniffi::custom_newtype!(KeyAlias, String); #[derive(Debug, Serialize, Deserialize, PartialEq, Clone)] pub struct KeyAlias(pub String); diff --git a/src/credential/json_vc.rs b/src/credential/json_vc.rs index 24d50dd8..b04eb72b 100644 --- a/src/credential/json_vc.rs +++ b/src/credential/json_vc.rs @@ -1,5 +1,9 @@ +use super::{Credential, CredentialFormat, VcdmVersion}; +use crate::{oid4vp::permission_request::RequestedField, CredentialType, KeyAlias}; + use std::sync::Arc; +use openid4vp::core::presentation_definition::PresentationDefinition; use serde_json::Value as Json; use ssi::{ claims::vc::{v1::Credential as _, v2::Credential as _}, @@ -7,10 +11,6 @@ use ssi::{ }; use uuid::Uuid; -use crate::{CredentialType, KeyAlias}; - -use super::{Credential, VcdmVersion}; - #[derive(uniffi::Object, Debug, Clone)] /// A verifiable credential secured as JSON. pub struct JsonVc { @@ -117,6 +117,40 @@ impl JsonVc { key_alias, })) } + + /// Check if the credential satisfies a presentation definition. + pub fn check_presentation_definition(&self, definition: &PresentationDefinition) -> bool { + // If the credential does not match the definition requested format, + // then return false. + if !definition.format().is_empty() + && !definition.contains_format(CredentialFormat::LdpVc.to_string().as_str()) + { + return false; + } + + // Check the JSON-encoded credential against the definition. + definition.check_credential_validation(&self.raw) + } + + /// Returns the requested fields given a presentation definition. + pub fn requested_fields( + &self, + definition: &PresentationDefinition, + ) -> Vec> { + let Ok(json) = serde_json::to_value(&self.parsed) else { + // NOTE: if we cannot convert the credential to a JSON value, then we cannot + // check the presentation definition, so we return false. + log::debug!("credential could not be converted to JSON: {self:?}"); + return Vec::new(); + }; + + definition + .requested_fields(&json) + .into_iter() + .map(Into::into) + .map(Arc::new) + .collect() + } } impl TryFrom for Arc { diff --git a/src/credential/jwt_vc.rs b/src/credential/jwt_vc.rs index 4d897f65..bd6d73d7 100644 --- a/src/credential/jwt_vc.rs +++ b/src/credential/jwt_vc.rs @@ -1,6 +1,10 @@ +use super::{Credential, CredentialFormat, VcdmVersion}; +use crate::{oid4vp::permission_request::RequestedField, CredentialType, KeyAlias}; + use std::sync::Arc; use base64::prelude::*; +use openid4vp::core::presentation_definition::PresentationDefinition; use ssi::{ claims::{ jwt::IntoDecodedJwt, @@ -11,10 +15,6 @@ use ssi::{ }; use uuid::Uuid; -use crate::{CredentialType, KeyAlias}; - -use super::{Credential, VcdmVersion}; - #[derive(uniffi::Object, Debug, Clone)] /// A verifiable credential secured as a JWT. pub struct JwtVc { @@ -103,7 +103,7 @@ impl JwtVc { self.jws.as_bytes().to_vec() } - fn from_compact_jws_bytes( + pub(crate) fn from_compact_jws_bytes( id: Uuid, raw: Vec, key_alias: Option, @@ -151,6 +151,56 @@ impl JwtVc { fn convert_to_json_string(base64_encoded_bytes: &[u8]) -> Option { String::from_utf8(BASE64_STANDARD_NO_PAD.decode(base64_encoded_bytes).ok()?).ok() } + + /// Return the internal `AnyJsonCredential` type + pub fn credential(&self) -> AnyJsonCredential { + self.credential.clone() + } + + /// Check if the credential satisfies a presentation definition. + pub fn check_presentation_definition(&self, definition: &PresentationDefinition) -> bool { + // If the credential does not match the definition requested format, + // then return false. + if !definition.format().is_empty() + && !definition.contains_format(CredentialFormat::JwtVcJson.to_string().as_str()) + { + return false; + } + + let Ok(json) = serde_json::to_value(&self.credential) else { + // NOTE: if we cannot convert the credential to a JSON value, then we cannot + // check the presentation definition, so we return false. + // + tracing::debug!( + "failed to convert credential '{}' to json, so continuing to the next credential", + self.id() + ); + return false; + }; + + // Check the JSON-encoded credential against the definition. + definition.check_credential_validation(&json) + } + + /// Returns the requested fields given a presentation definition. + pub fn requested_fields( + &self, + definition: &PresentationDefinition, + ) -> Vec> { + let Ok(json) = serde_json::to_value(&self.credential) else { + // NOTE: if we cannot convert the credential to a JSON value, then we cannot + // check the presentation definition, so we return false. + log::debug!("credential could not be converted to JSON: {self:?}"); + return Vec::new(); + }; + + definition + .requested_fields(&json) + .into_iter() + .map(Into::into) + .map(Arc::new) + .collect() + } } impl TryFrom for Arc { @@ -161,6 +211,18 @@ impl TryFrom for Arc { } } +impl TryFrom<&Credential> for Arc { + type Error = JwtVcInitError; + + fn try_from(credential: &Credential) -> Result { + JwtVc::from_compact_jws_bytes( + credential.id, + credential.payload.clone(), + credential.key_alias.clone(), + ) + } +} + #[derive(Debug, uniffi::Error, thiserror::Error)] pub enum JwtVcInitError { #[error("failed to decode string as a JWS of the form ..")] diff --git a/src/credential/mod.rs b/src/credential/mod.rs index 958b7055..2d7faddd 100644 --- a/src/credential/mod.rs +++ b/src/credential/mod.rs @@ -1,20 +1,20 @@ -use std::sync::Arc; +pub mod json_vc; +pub mod jwt_vc; +pub mod mdoc; +pub mod vcdm2_sd_jwt; -use serde::{Deserialize, Serialize}; +use std::sync::Arc; +use crate::{oid4vp::permission_request::RequestedField, CredentialType, KeyAlias, Uuid}; use json_vc::{JsonVc, JsonVcEncodingError, JsonVcInitError}; use jwt_vc::{JwtVc, JwtVcInitError}; use mdoc::{Mdoc, MdocEncodingError, MdocInitError}; - -use crate::{CredentialType, KeyAlias, Uuid}; - -pub mod json_vc; -pub mod jwt_vc; -pub mod mdoc; -pub mod sd_jwt_vc; +use openid4vp::core::presentation_definition::PresentationDefinition; +use serde::{Deserialize, Serialize}; +use vcdm2_sd_jwt::{SdJwtError, VCDM2SdJwt}; /// An unparsed credential, retrieved from storage. -#[derive(Debug, Serialize, Deserialize, uniffi::Record)] +#[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)] pub struct Credential { /// The local ID of this credential. pub id: Uuid, @@ -28,18 +28,27 @@ pub struct Credential { pub key_alias: Option, } +// Internal helper methods. +impl Credential { + /// Convert the parsed credential into a specialized JSON credential. + pub fn try_into_parsed(&self) -> Result, CredentialDecodingError> { + self.to_owned().try_into() + } +} + /// A credential that has been parsed as a known variant. #[derive(Debug, Clone, uniffi::Object)] pub struct ParsedCredential { - inner: ParsedCredentialInner, + pub(crate) inner: ParsedCredentialInner, } /// A credential that has been parsed as a known variant. #[derive(Debug, Clone)] -enum ParsedCredentialInner { +pub(crate) enum ParsedCredentialInner { MsoMdoc(Arc), JwtVcJson(Arc), JwtVcJsonLd(Arc), + VCDM2SdJwt(Arc), LdpVc(Arc), // More to come, for example: // SdJwt(...), @@ -80,18 +89,25 @@ impl ParsedCredential { }) } + #[uniffi::constructor] + /// Construct a new `sd_jwt_vc` credential. + pub fn new_sd_jwt(sd_jwt_vc: Arc) -> Arc { + Arc::new(Self { + inner: ParsedCredentialInner::VCDM2SdJwt(sd_jwt_vc), + }) + } + #[uniffi::constructor] /// Parse a credential from the generic form retrieved from storage. pub fn parse_from_credential( credential: Credential, ) -> Result, CredentialDecodingError> { - match credential.format { - CredentialFormat::MsoMdoc => Ok(Self::new_mso_mdoc(credential.try_into()?)), - CredentialFormat::JwtVcJson => Ok(Self::new_jwt_vc_json(credential.try_into()?)), - CredentialFormat::JwtVcJsonLd => Ok(Self::new_jwt_vc_json_ld(credential.try_into()?)), - CredentialFormat::LdpVc => Ok(Self::new_ldp_vc(credential.try_into()?)), - _ => Err(CredentialDecodingError::UnsupportedCredentialFormat), - } + // NOTE: due to the Arc type needed in the constructor, + // given the uniffi::Object trait, we need to have an inner reference + // to the credential that provided the type conversion, which avoids the + // TryFrom> that cannot be implemented given the compiler + // constraints on foreign types. + credential.try_into_parsed() } /// Convert a parsed credential into the generic form for storage. @@ -105,6 +121,13 @@ impl ParsedCredential { payload: vc.to_compact_jws_bytes(), key_alias: vc.key_alias(), }), + ParsedCredentialInner::VCDM2SdJwt(sd_jwt) => Ok(Credential { + id: sd_jwt.id(), + format: CredentialFormat::VCDM2SdJwt, + r#type: sd_jwt.r#type(), + payload: sd_jwt.inner.as_bytes().into(), + key_alias: sd_jwt.key_alias(), + }), ParsedCredentialInner::JwtVcJsonLd(vc) => Ok(Credential { id: vc.id(), format: CredentialFormat::JwtVcJsonLd, @@ -122,6 +145,17 @@ impl ParsedCredential { } } + /// Return the format of the credential. + pub fn format(&self) -> CredentialFormat { + match &self.inner { + ParsedCredentialInner::MsoMdoc(_) => CredentialFormat::MsoMdoc, + ParsedCredentialInner::JwtVcJson(_) => CredentialFormat::JwtVcJson, + ParsedCredentialInner::JwtVcJsonLd(_) => CredentialFormat::JwtVcJsonLd, + ParsedCredentialInner::VCDM2SdJwt(_) => CredentialFormat::VCDM2SdJwt, + ParsedCredentialInner::LdpVc(_) => CredentialFormat::LdpVc, + } + } + /// Get the local ID for this credential. pub fn id(&self) -> Uuid { match &self.inner { @@ -129,6 +163,7 @@ impl ParsedCredential { ParsedCredentialInner::JwtVcJson(arc) => arc.id(), ParsedCredentialInner::JwtVcJsonLd(arc) => arc.id(), ParsedCredentialInner::LdpVc(arc) => arc.id(), + ParsedCredentialInner::VCDM2SdJwt(arc) => arc.id(), } } @@ -139,6 +174,18 @@ impl ParsedCredential { ParsedCredentialInner::JwtVcJson(arc) => arc.key_alias(), ParsedCredentialInner::JwtVcJsonLd(arc) => arc.key_alias(), ParsedCredentialInner::LdpVc(arc) => arc.key_alias(), + ParsedCredentialInner::VCDM2SdJwt(arc) => arc.key_alias(), + } + } + + /// Return the CredentialType from the parsed credential. + pub fn r#type(&self) -> CredentialType { + match &self.inner { + ParsedCredentialInner::MsoMdoc(arc) => CredentialType(arc.doctype()), + ParsedCredentialInner::JwtVcJson(arc) => arc.r#type(), + ParsedCredentialInner::JwtVcJsonLd(arc) => arc.r#type(), + ParsedCredentialInner::LdpVc(arc) => arc.r#type(), + ParsedCredentialInner::VCDM2SdJwt(arc) => arc.r#type(), } } @@ -166,6 +213,69 @@ impl ParsedCredential { _ => None, } } + + /// Return the credential as an SD-JWT, if it is of that format. + pub fn as_sd_jwt(&self) -> Option> { + match &self.inner { + ParsedCredentialInner::VCDM2SdJwt(sd_jwt) => Some(sd_jwt.clone()), + _ => None, + } + } +} + +// Intneral Parsed Credential methods +impl ParsedCredential { + /// Check if the credential satisfies a presentation definition. + pub fn check_presentation_definition(&self, definition: &PresentationDefinition) -> bool { + match &self.inner { + ParsedCredentialInner::JwtVcJson(vc) => vc.check_presentation_definition(definition), + ParsedCredentialInner::JwtVcJsonLd(vc) => vc.check_presentation_definition(definition), + ParsedCredentialInner::LdpVc(vc) => vc.check_presentation_definition(definition), + ParsedCredentialInner::VCDM2SdJwt(sd_jwt) => { + sd_jwt.check_presentation_definition(definition) + } + ParsedCredentialInner::MsoMdoc(_mdoc) => false, + } + } + + /// Return the requested fields for the credential, accordinging to the presentation definition. + pub fn requested_fields( + &self, + definition: &PresentationDefinition, + ) -> Vec> { + match &self.inner { + ParsedCredentialInner::VCDM2SdJwt(sd_jwt) => sd_jwt.requested_fields(definition), + ParsedCredentialInner::JwtVcJson(vc) => vc.requested_fields(definition), + ParsedCredentialInner::JwtVcJsonLd(vc) => vc.requested_fields(definition), + ParsedCredentialInner::LdpVc(vc) => vc.requested_fields(definition), + ParsedCredentialInner::MsoMdoc(_mdoc) => { + unimplemented!("Mdoc requested fields not implemented") + } + } + } +} + +impl TryFrom for Arc { + type Error = CredentialDecodingError; + + fn try_from(credential: Credential) -> Result { + match credential.format { + CredentialFormat::MsoMdoc => Ok(ParsedCredential::new_mso_mdoc(credential.try_into()?)), + CredentialFormat::JwtVcJson => { + Ok(ParsedCredential::new_jwt_vc_json(credential.try_into()?)) + } + CredentialFormat::JwtVcJsonLd => { + Ok(ParsedCredential::new_jwt_vc_json_ld(credential.try_into()?)) + } + CredentialFormat::VCDM2SdJwt => { + Ok(ParsedCredential::new_sd_jwt(credential.try_into()?)) + } + CredentialFormat::LdpVc => Ok(ParsedCredential::new_ldp_vc(credential.try_into()?)), + _ => Err(CredentialDecodingError::UnsupportedCredentialFormat( + credential.format.to_string(), + )), + } + } } #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, uniffi::Enum)] @@ -176,22 +286,38 @@ pub enum VcdmVersion { #[derive(Debug, uniffi::Error, thiserror::Error)] pub enum CredentialEncodingError { - #[error(transparent)] + #[error("MsoDoc encoding error: {0}")] MsoMdoc(#[from] MdocEncodingError), - #[error(transparent)] + #[error("JsonVc encoding error: {0}")] JsonVc(#[from] JsonVcEncodingError), + #[error("SD-JWT encoding error: {0}")] + SdJwt(#[from] SdJwtError), } #[derive(Debug, uniffi::Error, thiserror::Error)] pub enum CredentialDecodingError { - #[error(transparent)] + #[error("MsoDoc decoding error: {0}")] MsoMdoc(#[from] MdocInitError), - #[error(transparent)] + #[error("JsonVc decoding error: {0}")] JsonVc(#[from] JsonVcInitError), - #[error(transparent)] + #[error("JWT VC decoding error: {0}")] JwtVc(#[from] JwtVcInitError), - #[error("this credential format is not yet supported")] - UnsupportedCredentialFormat, + #[error("SD JWT VC decoding error: {0}")] + SdJwt(#[from] SdJwtError), + #[error("Credential format is not yet supported for type: {0}")] + UnsupportedCredentialFormat(String), + #[error("Serialization error: {0}")] + Serialization(String), + #[error("Deserialization error: {0}")] + Deserialization(String), +} + +#[derive(Debug, uniffi::Error, thiserror::Error)] +pub enum CredentialPresentationError { + #[error("Credential decoding error: {0}")] + Decoding(String), + #[error("JSON path selector error: {0}")] + JsonPath(String), } /// The format of the credential. @@ -203,10 +329,25 @@ pub enum CredentialFormat { #[serde(rename = "jwt_vc_json-ld")] JwtVcJsonLd, LdpVc, + #[serde(rename = "vcdm2_sd_jwt")] + VCDM2SdJwt, #[serde(untagged)] Other(String), // For ease of expansion. } +impl std::fmt::Display for CredentialFormat { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + CredentialFormat::MsoMdoc => write!(f, "mso_mdoc"), + CredentialFormat::JwtVcJson => write!(f, "jwt_vc_json"), + CredentialFormat::JwtVcJsonLd => write!(f, "jwt_vc_json-ld"), + CredentialFormat::LdpVc => write!(f, "ldp_vc"), + CredentialFormat::VCDM2SdJwt => write!(f, "vcdm2_sd_jwt"), + CredentialFormat::Other(s) => write!(f, "{s}"), + } + } +} + #[cfg(test)] mod test { use super::*; @@ -216,6 +357,7 @@ mod test { #[case::jwt_vc_json(r#""jwt_vc_json""#, CredentialFormat::JwtVcJson)] #[case::jwt_vc_json_ld(r#""jwt_vc_json-ld""#, CredentialFormat::JwtVcJsonLd)] #[case::ldp_vc(r#""ldp_vc""#, CredentialFormat::LdpVc)] + #[case::ldp_vc(r#""vcdm2_sd_jwt""#, CredentialFormat::VCDM2SdJwt)] #[case::other(r#""something_else""#, CredentialFormat::Other("something_else".into()))] fn credential_format_roundtrips(#[case] expected: String, #[case] value: CredentialFormat) { let serialized = serde_json::to_string(&value).unwrap(); diff --git a/src/credential/sd_jwt_vc.rs b/src/credential/sd_jwt_vc.rs deleted file mode 100644 index a3c24ba9..00000000 --- a/src/credential/sd_jwt_vc.rs +++ /dev/null @@ -1,94 +0,0 @@ -use ssi::claims::{ - jwt::{AnyClaims, JWTClaims}, - sd_jwt::{RevealedSdJwt, SdJwtBuf}, -}; - -#[uniffi::export] -pub fn decode_reveal_sd_jwt(input: String) -> Result { - let jwt: SdJwtBuf = SdJwtBuf::new(input).map_err(|_| SdJwtVcError::InvalidSdJwt)?; - let revealed_jwt: RevealedSdJwt = jwt - .decode_reveal_any() - .map_err(|_| SdJwtVcError::JwtDecoding)?; - let claims: &JWTClaims = revealed_jwt.claims(); - serde_json::to_string(claims).map_err(|_| SdJwtVcError::Serialization) -} - -#[derive(Debug, uniffi::Error, thiserror::Error)] -pub enum SdJwtVcError { - #[error("failed to decode SD-JWT as a JWT")] - JwtDecoding, - #[error("invalid SD-JWT")] - InvalidSdJwt, - #[error("serialization error")] - Serialization, -} - -#[cfg(test)] -mod tests { - use super::*; - - use ssi::{ - claims::sd_jwt::{ConcealJwtClaims, SdAlg}, - json_pointer, JWK, - }; - - #[test] - fn test_decode_static() { - // Example SD-JWT input (you should replace this with a real SD-JWT string for a proper test) - let sd_jwt_input = include_str!("../../tests/examples/sd_vc.jwt"); - - // Call the function with the SD-JWT input - let output = - decode_reveal_sd_jwt(sd_jwt_input.to_string()).expect("failed to decode SD-JWT"); - - // Check the output JSON string structure - assert!(output.contains("\"sub\":\"user_42\"")); - assert!(output.contains("\"birthdate\":\"1940-01-01\"")); - } - - async fn generate_sd_jwt() -> SdJwtBuf { - // Define the key (this is a private key; for testing purposes you can use this inline or generate one) - let jwk: JWK = JWK::generate_ed25519().expect("unable to generate sd-jwt"); - - // Create the JWT claims - let registeredclaims = serde_json::json!({ - "iss": "https://issuer.example.com", - "sub": "1234567890", - "vc": { - "type": ["VerifiableCredential", "UniversityDegreeCredential"], - "credentialSubject": { - "id": "did:example:abcdef1234567890", - "name": "John Doe", - "degree": { - "type": "BachelorDegree", - "name": "Bachelor of Science and Arts" - } - } - } - }); - - let claims: JWTClaims = serde_json::from_value(registeredclaims).unwrap(); - let my_pointer = json_pointer!("/vc"); - - claims - .conceal_and_sign(SdAlg::Sha256, &[my_pointer], &jwk) - .await - .unwrap() - } - - #[tokio::test] - async fn test_decode_gen() -> Result<(), SdJwtVcError> { - // Example SD-JWT input (you should replace this with a real SD-JWT string for a proper test) - let sd_jwt_input = generate_sd_jwt().await; - - // Call the function with the SD-JWT input - let output = - decode_reveal_sd_jwt(sd_jwt_input.to_string()).expect("failed to decode SD-JWT"); - - // Check the output JSON string structure - assert!(output.contains("\"sub\":\"1234567890\"")); - assert!(output.contains("\"name\":\"John Doe\"")); - - Ok(()) - } -} diff --git a/src/credential/vcdm2_sd_jwt.rs b/src/credential/vcdm2_sd_jwt.rs new file mode 100644 index 00000000..06de5a73 --- /dev/null +++ b/src/credential/vcdm2_sd_jwt.rs @@ -0,0 +1,341 @@ +use super::{Credential, CredentialFormat, ParsedCredential, ParsedCredentialInner}; +use crate::{oid4vp::permission_request::RequestedField, CredentialType, KeyAlias}; + +use std::sync::Arc; + +use openid4vp::core::presentation_definition::PresentationDefinition; +use ssi::{ + claims::{ + sd_jwt::SdJwtBuf, + vc::v2::{Credential as _, JsonCredential}, + vc_jose_cose::SdJwtVc, + }, + prelude::AnyJsonCredential, +}; +use uniffi::deps::log; +use uuid::Uuid; + +#[derive(Debug, uniffi::Object)] +pub struct VCDM2SdJwt { + pub(crate) id: Uuid, + pub(crate) key_alias: Option, + pub(crate) credential: JsonCredential, + pub(crate) inner: SdJwtBuf, +} + +// Internal utility methods for decoding a SdJwt. +impl VCDM2SdJwt { + /// Return the revealed claims as a JSON value. + pub fn revealed_claims_as_json(&self) -> Result { + serde_json::to_value(&self.credential) + .map_err(|e| SdJwtError::Serialization(format!("{e:?}"))) + } + + /// The types of the credential from the VCDM, excluding the base `VerifiableCredential` type. + pub fn types(&self) -> Vec { + self.credential.additional_types().to_vec() + } + + /// Returns the SD-JWT credential as an AnyCredential type. + pub fn credential(&self) -> Result { + // NOTE: Due to the type constraints on AnyJsonCredential, we're + // reserializing the type into a V2 credential. + serde_json::to_value(&self.credential) + .map_err(|e| SdJwtError::Serialization(format!("{e:?}"))) + .and_then(|v| { + serde_json::from_value(v).map_err(|e| SdJwtError::Serialization(format!("{e:?}"))) + }) + } + + /// Check if the credential satisfies a presentation definition. + pub fn check_presentation_definition(&self, definition: &PresentationDefinition) -> bool { + // If the credential does not match the definition requested format, + // then return false. + if !definition.format().is_empty() + && !definition.contains_format(CredentialFormat::VCDM2SdJwt.to_string().as_str()) + { + println!( + "Credential does not match the requested format: {:?}.", + definition.format() + ); + + return false; + } + + let Ok(json) = serde_json::to_value(&self.credential) else { + // NOTE: if we cannot convert the credential to a JSON value, then we cannot + // check the presentation definition, so we return false. + // + // TODO: add logging to indicate that the credential could not be converted to JSON. + return false; + }; + + // Check the JSON-encoded credential against the definition. + definition.check_credential_validation(&json) + } + + /// Return the requested fields for the SD-JWT credential. + pub fn requested_fields( + &self, + definition: &PresentationDefinition, + ) -> Vec> { + let Ok(json) = serde_json::to_value(&self.credential) else { + // NOTE: if we cannot convert the credential to a JSON value, then we cannot + // check the presentation definition, so we return false. + log::debug!("credential could not be converted to JSON: {self:?}"); + return Vec::new(); + }; + + definition + .requested_fields(&json) + .into_iter() + .map(Into::into) + .map(Arc::new) + .collect() + } +} + +#[uniffi::export] +impl VCDM2SdJwt { + /// Create a new SdJwt instance from a compact SD-JWS string. + #[uniffi::constructor] + pub fn new_from_compact_sd_jwt(input: String) -> Result, SdJwtError> { + let inner: SdJwtBuf = + SdJwtBuf::new(input).map_err(|e| SdJwtError::InvalidSdJwt(format!("{e:?}")))?; + + let mut sd_jwt = VCDM2SdJwt::try_from(inner)?; + sd_jwt.key_alias = None; + + Ok(Arc::new(sd_jwt)) + } + + /// Create a new SdJwt instance from a compact SD-JWS string with a provided key alias. + #[uniffi::constructor] + pub fn new_from_compact_sd_jwt_with_key( + input: String, + key_alias: KeyAlias, + ) -> Result, SdJwtError> { + let inner: SdJwtBuf = + SdJwtBuf::new(input).map_err(|e| SdJwtError::InvalidSdJwt(format!("{e:?}")))?; + + let mut sd_jwt = VCDM2SdJwt::try_from(inner)?; + sd_jwt.key_alias = Some(key_alias); + + Ok(Arc::new(sd_jwt)) + } + + /// Return the ID for the SdJwt instance. + pub fn id(&self) -> Uuid { + self.id + } + + /// Return the key alias for the credential + pub fn key_alias(&self) -> Option { + self.key_alias.clone() + } + + /// The type of this credential. Note that if there is more than one type (i.e. `types()` + /// returns more than one value), then the types will be concatenated with a "+". + pub fn r#type(&self) -> CredentialType { + CredentialType(self.types().join("+")) + } + + /// Return the revealed claims as a UTF-8 encoded JSON string. + pub fn revealed_claims_as_json_string(&self) -> Result { + serde_json::to_string(&self.credential) + .map_err(|e| SdJwtError::Serialization(format!("{e:?}"))) + } +} + +impl From for ParsedCredential { + fn from(value: VCDM2SdJwt) -> Self { + ParsedCredential { + inner: ParsedCredentialInner::VCDM2SdJwt(Arc::new(value)), + } + } +} + +impl TryFrom for Credential { + type Error = SdJwtError; + + fn try_from(value: VCDM2SdJwt) -> Result { + ParsedCredential::from(value) + .into_generic_form() + .map_err(|e| SdJwtError::CredentialEncoding(format!("{e:?}"))) + } +} + +impl TryFrom> for Credential { + type Error = SdJwtError; + + fn try_from(value: Arc) -> Result { + ParsedCredential::new_sd_jwt(value) + .into_generic_form() + .map_err(|e| SdJwtError::CredentialEncoding(format!("{e:?}"))) + } +} + +impl TryFrom<&Credential> for VCDM2SdJwt { + type Error = SdJwtError; + + fn try_from(value: &Credential) -> Result { + let inner = SdJwtBuf::new(value.payload.clone()) + .map_err(|_| SdJwtError::InvalidSdJwt(Default::default()))?; + + let mut sd_jwt = VCDM2SdJwt::try_from(inner)?; + // Set the ID and key alias from the credential. + sd_jwt.id = value.id; + sd_jwt.key_alias = value.key_alias.clone(); + + Ok(sd_jwt) + } +} + +impl TryFrom for Arc { + type Error = SdJwtError; + + fn try_from(value: Credential) -> Result, SdJwtError> { + Ok(Arc::new(VCDM2SdJwt::try_from(&value)?)) + } +} + +impl TryFrom for VCDM2SdJwt { + type Error = SdJwtError; + + fn try_from(value: SdJwtBuf) -> Result { + let SdJwtVc(vc) = SdJwtVc::decode_reveal_any(&value) + .map_err(|e| SdJwtError::SdJwtDecoding(format!("{e:?}")))? + .into_claims() + .private; + + Ok(VCDM2SdJwt { + id: Uuid::new_v4(), + key_alias: None, + inner: value, + credential: vc, + }) + } +} + +#[uniffi::export] +pub fn decode_reveal_sd_jwt(input: String) -> Result { + let jwt: SdJwtBuf = + SdJwtBuf::new(input).map_err(|e| SdJwtError::InvalidSdJwt(format!("{e:?}")))?; + let SdJwtVc(vc) = SdJwtVc::decode_reveal_any(&jwt) + .map_err(|e| SdJwtError::SdJwtDecoding(format!("{e:?}")))? + .into_claims() + .private; + serde_json::to_string(&vc).map_err(|e| SdJwtError::Serialization(format!("{e:?}"))) +} + +#[derive(Debug, uniffi::Error, thiserror::Error)] +pub enum SdJwtError { + #[error("failed to initialize SD-JWT: {0}")] + SdJwtVcInitError(String), + #[error("failed to decode SD-JWT as a JWT: {0}")] + SdJwtDecoding(String), + #[error("invalid SD-JWT: {0}")] + InvalidSdJwt(String), + #[error("serialization error: {0}")] + Serialization(String), + #[error("failed to encode SD-JWT: {0}")] + CredentialEncoding(String), + #[error("'vc' is missing from the SD-JWT decoded claims")] + CredentialClaimMissing, +} + +#[cfg(test)] +pub(crate) mod tests { + use super::*; + + use ssi::{claims::sd_jwt::SdAlg, json_pointer, JWK}; + + #[test] + fn test_decode_static() { + // Example SD-JWT input (you should replace this with a real SD-JWT string for a proper test) + let sd_jwt_input = include_str!("../../tests/examples/sd_vc.jwt"); + + // Call the function with the SD-JWT input + let output = + decode_reveal_sd_jwt(sd_jwt_input.to_string()).expect("failed to decode SD-JWT"); + + // Check the output JSON string structure + assert!(output.contains("\"identityHash\":\"john.smith@example.com\"")); + assert!(output.contains("\"awardedDate\":\"2024-09-23T18:12:12+0000\"")); + } + + pub async fn generate_sd_jwt() -> SdJwtBuf { + // Define the key (this is a private key; for testing purposes you can use this inline or generate one) + let jwk: JWK = JWK::generate_ed25519().expect("unable to generate sd-jwt"); + + // Create the JWT claims + let registeredclaims = serde_json::json!({"@context": [ + "https://www.w3.org/ns/credentials/v2", + "https://purl.imsglobal.org/spec/ob/v3p0/context-3.0.3.json" + ], + "awardedDate": "2024-09-23T18:12:12+0000", + "credentialSubject": { + "identity": [ + { + "hashed": false, + "identityHash": "John Smith", + "identityType": "name", + "salt": "not-used", + "type": "IdentityObject" + }, + { + "hashed": false, + "identityHash": "john.smith@example.com", + "identityType": "emailAddress", + "salt": "not-used", + "type": "IdentityObject" + } + ], + "achievement": { + "name": "Team Membership", + "type": "Achievement" + } + }, + "issuer": { + "id": "did:jwk:eyJhbGciOiJFUzI1NiIsImNydiI6IlAtMjU2Iiwia3R5IjoiRUMiLCJ4IjoibWJUM2dqOWFvOGNuS280M0prcVRPUmNJQVI4MFgwTUFXQWNGYzZvR1JMYyIsInkiOiJiOFVOY0hDMmFHQ3J1STZ0QlRWSVY0dW5ZWEVyS0M4ZDRnRTFGZ0s0Q05JIn0#0", + "name": "Workforce Development Council", + "type": "Profile" + }, + "name": "TeamMembership", + "type": ["VerifiableCredential", "OpenBadgeCredential"] + }); + + let claims: SdJwtVc = serde_json::from_value(registeredclaims).unwrap(); + let my_pointer = json_pointer!("/credentialSubject/identity/0"); + + claims + .conceal_and_sign(SdAlg::Sha256, &[my_pointer], &jwk) + .await + .unwrap() + } + + #[tokio::test] + async fn test_sd_jwt() -> Result<(), SdJwtError> { + let input = generate_sd_jwt().await; + + assert!(VCDM2SdJwt::new_from_compact_sd_jwt(input.to_string()).is_ok()); + + Ok(()) + } + + #[tokio::test] + async fn test_decode_gen() -> Result<(), SdJwtError> { + // Example SD-JWT input (you should replace this with a real SD-JWT string for a proper test) + let sd_jwt_input = generate_sd_jwt().await; + + // Call the function with the SD-JWT input + let output = + decode_reveal_sd_jwt(sd_jwt_input.to_string()).expect("failed to decode SD-JWT"); + + // Check the output JSON string structure + assert!(output.contains("\"identityHash\":\"john.smith@example.com\"")); + assert!(output.contains("\"identityHash\":\"John Smith\"")); + + Ok(()) + } +} diff --git a/src/lib.rs b/src/lib.rs index 97ad83f2..6f69540c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,6 +6,7 @@ pub mod did; pub mod local_store; pub mod mdl; pub mod oid4vci; +pub mod oid4vp; pub mod proof_of_possession; pub mod storage_manager; pub mod vdc_collection; diff --git a/src/oid4vp/error.rs b/src/oid4vp/error.rs new file mode 100644 index 00000000..18776c1d --- /dev/null +++ b/src/oid4vp/error.rs @@ -0,0 +1,64 @@ +// use super::request_signer::RequestSignerError; + +/// The [OID4VPError] enum represents the errors that can occur +/// when using the oid4vp foreign library. +#[derive(thiserror::Error, Debug, uniffi::Error)] +pub enum OID4VPError { + #[error("An unexpected foreign callback error occurred: {0}")] + UnexpectedUniFFICallbackError(String), + #[error("Failed to validate the OID4VP Request: {0}")] + RequestValidation(String), + #[error("Failed to resolve the presentation definition: {0}")] + PresentationDefinitionResolution(String), + #[error("Failed to create verifiable presentation token: {0}")] + Token(String), + #[error("Unsupported Response Mode for OID4VP Request: {0}")] + UnsupportedResponseMode(String), + #[error("Failed to submit OID4VP response: {0}")] + ResponseSubmission(String), + #[error("Credential callback error: {0}")] + CredentialCallback(String), + #[error("Failed to create presentation submission: {0}")] + PresentationSubmissionCreation(String), + #[error("Failed to parse DID url: {0}")] + InvalidDIDUrl(String), + #[error("Failed to generate DID key URL: {0}")] + DIDKeyGenerateUrl(String), + #[error("Failed to encode credential: {0}")] + CredentialEncodingError(String), + #[error("Failed to decode credential: {0}")] + CredentialDecodingError(String), + #[error("Failed to parse JSON syntax: {0}")] + JsonSyntaxParse(String), + #[error(transparent)] + VdcCollection(#[from] crate::vdc_collection::VdcCollectionError), + #[error("HTTP Client Initialization Error: {0}")] + HttpClientInitialization(String), + #[error("Signing algorithm not found: {0}")] + SigningAlgorithmNotFound(String), + #[error("Unsupported Client ID Scheme: {0}")] + InvalidClientIdScheme(String), + #[error("Invalid Descriptor Not Found")] + InputDescriptorNotFound, + #[error("Failed to parse credential into verifiable presentation token: {0}")] + VpTokenParse(String), + #[error("Failed to create verifiable presentation token: {0}")] + VpTokenCreate(String), + #[error("JWK Parse Error: {0}")] + JwkParse(String), + #[error("VDC collection is not initialized")] + VdcCollectionNotInitialized, + #[error("Failed to find a current authorization request for permission response")] + AuthorizationRequestNotFound, + #[error("Request signer not found")] + RequestSignerNotFound, + #[error("Failed to initialize metadata: {0}")] + MetadataInitialization(String), +} + +// Handle unexpected errors when calling a foreign callback +impl From for OID4VPError { + fn from(value: uniffi::UnexpectedUniFFICallbackError) -> Self { + OID4VPError::UnexpectedUniFFICallbackError(value.reason) + } +} diff --git a/src/oid4vp/holder.rs b/src/oid4vp/holder.rs new file mode 100644 index 00000000..b9cfee6f --- /dev/null +++ b/src/oid4vp/holder.rs @@ -0,0 +1,391 @@ +use super::error::OID4VPError; +use super::permission_request::*; +use crate::common::*; +use crate::credential::*; +use crate::vdc_collection::VdcCollection; + +use std::sync::Arc; + +use openid4vp::core::authorization_request::parameters::ClientIdScheme; +use openid4vp::core::credential_format::{ClaimFormatDesignation, ClaimFormatPayload}; +use openid4vp::core::presentation_definition::PresentationDefinition; +use openid4vp::core::response::parameters::VpTokenItem; +use openid4vp::{ + core::{ + authorization_request::{ + parameters::ResponseMode, + verification::{did::verify_with_resolver, RequestVerifier}, + AuthorizationRequestObject, + }, + metadata::WalletMetadata, + presentation_submission::{DescriptorMap, PresentationSubmission}, + response::{parameters::VpToken, AuthorizationResponse, UnencodedAuthorizationResponse}, + }, + wallet::Wallet as OID4VPWallet, +}; +use ssi::dids::DIDWeb; +use ssi::dids::VerificationMethodDIDResolver; +use ssi::prelude::AnyJwkMethod; +use uniffi::deps::{anyhow, log}; + +/// A Holder is an entity that possesses one or more Verifiable Credentials. +/// The Holder is typically the subject of the credentials, but not always. +/// The Holder has the ability to generate Verifiable Presentations from +/// these credentials and share them with Verifiers. +#[derive(Debug, uniffi::Object)] +pub struct Holder { + /// An atomic reference to the VDC collection. + pub(crate) vdc_collection: Option>, + + /// Metadata about the holder. + pub(crate) metadata: WalletMetadata, + + /// HTTP Request Client + pub(crate) client: openid4vp::core::util::ReqwestClient, + + /// A list of trusted DIDs. + pub(crate) trusted_dids: Vec, + + /// Provide optional credentials to the holder instance. + pub(crate) provided_credentials: Option>>, +} + +#[uniffi::export(async_runtime = "tokio")] +impl Holder { + /// Uses VDC collection to retrieve the credentials for a given presentation definition. + #[uniffi::constructor] + pub async fn new( + vdc_collection: Arc, + trusted_dids: Vec, + ) -> Result, OID4VPError> { + let client = openid4vp::core::util::ReqwestClient::new() + .map_err(|e| OID4VPError::HttpClientInitialization(format!("{e:?}")))?; + + Ok(Arc::new(Self { + client, + vdc_collection: Some(vdc_collection), + metadata: Self::metadata()?, + trusted_dids, + provided_credentials: None, + })) + } + + /// Construct a new holder with provided credentials + /// instead of a VDC collection. + /// + /// This constructor will use the provided credentials for the presentation, + /// instead of searching for credentials in the VDC collection. + #[uniffi::constructor] + pub async fn new_with_credentials( + provided_credentials: Vec>, + trusted_dids: Vec, + ) -> Result, OID4VPError> { + let client = openid4vp::core::util::ReqwestClient::new() + .map_err(|e| OID4VPError::HttpClientInitialization(format!("{e:?}")))?; + + Ok(Arc::new(Self { + client, + vdc_collection: None, + metadata: Self::metadata()?, + trusted_dids, + provided_credentials: Some(provided_credentials), + })) + } + + /// Given an authorization request URL, return a permission request, + /// which provides a list of requested credentials and requested fields + /// that align with the presentation definition of the request. + /// + /// This will fetch the presentation definition from the verifier. + pub async fn authorization_request( + &self, + url: Url, + ) -> Result, OID4VPError> { + let request = self + .validate_request(url) + .await + .map_err(|e| OID4VPError::RequestValidation(format!("{e:?}")))?; + + match request.response_mode() { + ResponseMode::DirectPost | ResponseMode::DirectPostJwt => { + self.permission_request(request).await + } + ResponseMode::Unsupported(mode) => { + Err(OID4VPError::UnsupportedResponseMode(mode.to_owned())) + } + } + } + + pub async fn submit_permission_response( + &self, + response: Arc, + ) -> Result, OID4VPError> { + // Create a descriptor map for the presentation submission based on the credentials + // returned from the selection response. + let Some(input_descriptor_id) = response + .presentation_definition + .input_descriptors() + .first() + .map(|d| d.id().to_owned()) + else { + // NOTE: We may wish to add a more generic `BadRequest` error type + // we should always expect to have at least one input descriptor. + return Err(OID4VPError::InputDescriptorNotFound); + }; + + let descriptor_map = + self.create_descriptor_map(&response.selected_credential, input_descriptor_id); + + let presentation_submission_id = uuid::Uuid::new_v4(); + let presentation_definition_id = response.presentation_definition.id().clone(); + + // // Create a presentation submission. + let presentation_submission = PresentationSubmission::new( + presentation_submission_id, + presentation_definition_id, + vec![descriptor_map], + ); + + let vp_token = self + .create_verifiable_presentation(&response.selected_credential) + .await?; + + let response = self + .submit_response( + response.authorization_request.clone(), + AuthorizationResponse::Unencoded(UnencodedAuthorizationResponse( + Default::default(), + vp_token, + presentation_submission, + )), + ) + .await + .map_err(|e| OID4VPError::ResponseSubmission(format!("{e:?}")))?; + + Ok(response) + } +} + +// Internal methods for the Holder. +impl Holder { + /// Return the static metadata for the holder. + /// + /// This method is used to initialize the metadata for the holder. + pub(crate) fn metadata() -> Result { + let mut metadata = WalletMetadata::openid4vp_scheme_static(); + + // Insert support for the VCDM2 SD JWT format. + metadata.vp_formats_supported_mut().0.insert( + ClaimFormatDesignation::Other("vcdm2_sd_jwt".into()), + ClaimFormatPayload::AlgValuesSupported(vec!["ES256".into()]), + ); + + metadata + // Insert support for the DID client ID scheme. + .add_client_id_schemes_supported(ClientIdScheme::Did) + .map_err(|e| OID4VPError::MetadataInitialization(format!("{e:?}")))?; + + Ok(metadata) + } + + /// This will return all the credentials that match the presentation definition. + async fn search_credentials_vs_presentation_definition( + &self, + definition: &PresentationDefinition, + ) -> Result>, OID4VPError> { + let credentials = match &self.provided_credentials { + // Use a pre-selected list of credentials if provided. + Some(credentials) => credentials.to_owned(), + None => match &self.vdc_collection { + None => vec![], + Some(vdc_collection) => vdc_collection + .all_entries()? + .into_iter() + .filter_map(|id| { + vdc_collection + .get(id) + .ok() + .flatten() + .and_then(|cred| cred.try_into_parsed().ok()) + }) + .collect::>>(), + }, + } + .into_iter() + .filter_map( + |cred| match cred.check_presentation_definition(definition) { + true => Some(cred), + false => None, + }, + ) + .collect::>>(); + + Ok(credentials) + } + + // Construct a DescriptorMap for the presentation submission based on the + // credentials returned from the VDC collection. + fn create_descriptor_map( + &self, + credential: &Arc, + input_descriptor_id: String, + ) -> DescriptorMap { + DescriptorMap::new( + input_descriptor_id, + credential.format().to_string().as_str(), + "$.verifiableCredential".into(), + ) + } + + // Internal method for returning the `PermissionRequest` for an oid4vp request. + async fn permission_request( + &self, + request: AuthorizationRequestObject, + ) -> Result, OID4VPError> { + // Resolve the presentation definition. + let presentation_definition = request + .resolve_presentation_definition(self.http_client()) + .await + .map_err(|e| OID4VPError::PresentationDefinitionResolution(format!("{e:?}")))? + .into_parsed(); + + let credentials = self + .search_credentials_vs_presentation_definition(&presentation_definition) + .await?; + + Ok(PermissionRequest::new( + presentation_definition.clone(), + credentials.clone(), + request, + )) + } + + async fn create_verifiable_presentation( + &self, + credential: &Arc, + ) -> Result { + match &credential.inner { + ParsedCredentialInner::VCDM2SdJwt(sd_jwt) => { + // TODO: need to provide the "filtered" (disclosed) fields of the + // credential to be encoded into the VpToken. + // + // Currently, this is encoding the entire revealed SD-JWT, + // without the selection of individual disclosed fields. + let compact: &str = sd_jwt.inner.as_ref(); + Ok(VpTokenItem::from(compact.to_string()).into()) + } + _ => Err(OID4VPError::VpTokenParse(format!( + "Credential parsing for VP Token is not implemented for {:?}.", + credential, + ))), + } + } +} + +#[async_trait::async_trait] +impl RequestVerifier for Holder { + /// Performs verification on Authorization Request Objects when `client_id_scheme` is `did`. + async fn did( + &self, + decoded_request: &AuthorizationRequestObject, + request_jwt: String, + ) -> anyhow::Result<()> { + log::debug!("Verifying DID request."); + + let resolver: VerificationMethodDIDResolver = + VerificationMethodDIDResolver::new(DIDWeb); + + verify_with_resolver( + &self.metadata, + decoded_request, + request_jwt, + Some(self.trusted_dids.as_slice()), + &resolver, + ) + .await?; + + Ok(()) + } +} + +impl OID4VPWallet for Holder { + type HttpClient = openid4vp::core::util::ReqwestClient; + + fn http_client(&self) -> &Self::HttpClient { + &self.client + } + + fn metadata(&self) -> &WalletMetadata { + &self.metadata + } +} + +#[cfg(test)] +mod tests { + use super::*; + use vcdm2_sd_jwt::VCDM2SdJwt; + + // NOTE: This test requires the `companion` service to be running and + // available at localhost:3000. + // + // See: https://github.com/spruceid/companion/pull/1 + #[ignore] + #[tokio::test] + async fn test_oid4vp_url() -> Result<(), Box> { + let example_sd_jwt = include_str!("../../tests/examples/sd_vc.jwt"); + let sd_jwt = VCDM2SdJwt::new_from_compact_sd_jwt(example_sd_jwt.into())?; + let credential = ParsedCredential::new_sd_jwt(sd_jwt); + + let initiate_api = "http://localhost:3000/api/oid4vp/initiate"; + + // Make a request to the OID4VP initiate API. + // provide a url-encoded `format` parameter to specify the format of the presentation. + let response: (String, String) = reqwest::Client::new() + .post(initiate_api) + .form(&[("format", "sd_jwt")]) + .send() + .await? + .json() + .await?; + + let _id = response.0; + let url = Url::parse(&response.1).expect("failed to parse url"); + + // Make a request to the OID4VP URL. + let holder = Holder::new_with_credentials( + vec![credential], + vec![ + "did:web:localhost%3A3000:oid4vp:client".into(), + "did:web:f48e-99-209-178-38.ngrok-free.app:oid4vp:client".into(), + ], + ) + .await?; + + let permission_request = holder.authorization_request(url).await?; + + let mut parsed_credentials = permission_request.credentials(); + + assert_eq!(parsed_credentials.len(), 1); + + let selected_credential = parsed_credentials + .pop() + .expect("failed to retrieve a parsed credential matching the presentation definition"); + + let requested_fields = permission_request.requested_fields(&selected_credential); + + println!("Requested Fields: {requested_fields:?}"); + + assert!(requested_fields.len() > 0); + + let response = permission_request.create_permission_response(selected_credential); + + holder.submit_permission_response(response).await?; + + Ok(()) + } + + #[tokio::test] + async fn test_vehicle_title() -> Result<(), Box> { + Ok(()) + } +} diff --git a/src/oid4vp/mod.rs b/src/oid4vp/mod.rs new file mode 100644 index 00000000..a2ac028c --- /dev/null +++ b/src/oid4vp/mod.rs @@ -0,0 +1,3 @@ +pub mod error; +pub mod holder; +pub mod permission_request; diff --git a/src/oid4vp/permission_request.rs b/src/oid4vp/permission_request.rs new file mode 100644 index 00000000..a8455616 --- /dev/null +++ b/src/oid4vp/permission_request.rs @@ -0,0 +1,217 @@ +use openid4vp::core::authorization_request::AuthorizationRequestObject; +use openid4vp::core::presentation_definition::PresentationDefinition; + +use crate::common::*; +use crate::credential::{Credential, ParsedCredential}; + +use std::collections::HashMap; +use std::fmt::Debug; +use std::sync::{Arc, RwLock}; + +/// Type alias for mapping input descriptor ids to matching credentials +/// stored in the VDC collection. This mapping is used to provide a +/// shared state between native code and the rust code, to select +/// the appropriate credentials for a given input descriptor. +pub type InputDescriptorCredentialMap = HashMap>; + +/// A clonable and thread-safe reference to the input descriptor credential map. +pub type InputDescriptorCredentialMapRef = Arc>; + +/// A clonable and thread-safe reference to the selected credential map. +pub type SelectedCredentialMapRef = Arc>>>; + +#[derive(uniffi::Error, thiserror::Error, Debug)] +pub enum PermissionRequestError { + /// Permission denied for requested presentation. + #[error("Permission denied for requested presentation.")] + PermissionDenied, + + /// RwLock error + #[error("RwLock error.")] + RwLockError, + + /// Credential not found for input descriptor id. + #[error("Credential not found for input descriptor id: {0}")] + CredentialNotFound(String), + + /// Input descriptor not found for input descriptor id. + #[error("Input descriptor not found for input descriptor id: {0}")] + InputDescriptorNotFound(String), + + /// Invalid selected credential for requested field. Selected + /// credential does not match optional credentials. + #[error("Selected credential type, {0}, does not match requested credential types: {1}")] + InvalidSelectedCredential(String, String), + + /// Credential Presentation Error + /// + /// failed to present the credential. + #[error("Credential Presentation Error: {0}")] + CredentialPresentation(String), +} + +#[derive(Debug, uniffi::Object)] +pub struct RequestedField { + /// A unique ID for the requested field + pub(crate) id: Uuid, + pub(crate) name: String, + pub(crate) required: bool, + pub(crate) retained: bool, + pub(crate) purpose: Option, + pub(crate) constraint_field_id: Option, + // the `raw_field` represents the actual field + // being selected by the input descriptor JSON path + // selector. + pub(crate) raw_fields: Option, +} + +impl From for RequestedField { + fn from(value: openid4vp::core::input_descriptor::RequestedField) -> Self { + Self { + id: value.id, + name: value.name, + required: value.required, + retained: value.retained, + purpose: value.purpose, + constraint_field_id: value.constraint_field_id, + raw_fields: value.raw_fields, + } + } +} + +impl RequestedField { + /// Construct a new requested field given the required parameters. This method is exposed as + /// public, however, it is likely that the `from_definition` method will be used to construct + /// requested fields from a presentation definition. + /// + /// See [RequestedField::from_definition] to return a vector of requested fields + /// according to a presentation definition. + pub fn new( + name: String, + required: bool, + retained: bool, + purpose: Option, + constraint_field_id: Option, + raw_fields: Option, + ) -> Arc { + Arc::new(Self { + id: Uuid::new_v4(), + name, + required, + retained, + purpose, + constraint_field_id, + raw_fields, + }) + } + + /// Return the unique ID for the request field. + pub fn id(&self) -> Uuid { + self.id + } + + /// Return the constraint field id the requested field belongs to + pub fn constraint_field_id(&self) -> Option { + self.constraint_field_id.clone() + } + + /// Return the stringified JSON raw fields. + pub fn raw_fields(&self) -> Option { + self.raw_fields + .as_ref() + .and_then(|value| serde_json::to_string(value).ok()) + } +} + +/// Public methods for the RequestedField struct. +#[uniffi::export] +impl RequestedField { + /// Return the field name + pub fn name(&self) -> String { + self.name.clone() + } + + /// Return the field required status + pub fn required(&self) -> bool { + self.required + } + + /// Return the field retained status + pub fn retained(&self) -> bool { + self.retained + } + + /// Return the purpose of the requested field. + pub fn purpose(&self) -> Option { + self.purpose.clone() + } +} + +#[derive(Debug, Clone, uniffi::Object)] +pub struct PermissionRequest { + definition: PresentationDefinition, + credentials: Vec>, + request: AuthorizationRequestObject, +} + +impl PermissionRequest { + pub fn new( + definition: PresentationDefinition, + credentials: Vec>, + request: AuthorizationRequestObject, + ) -> Arc { + Arc::new(Self { + definition, + credentials, + request, + }) + } +} + +#[uniffi::export] +impl PermissionRequest { + /// Return the filtered list of credentials that matched + /// the presentation definition. + pub fn credentials(&self) -> Vec> { + self.credentials.clone() + } + + /// Return the requested fields for a given credential. + /// + /// NOTE: This will return only the requested fields for a given credential. + pub fn requested_fields(&self, credential: &Arc) -> Vec> { + credential.requested_fields(&self.definition) + } + + /// Construct a new permission response for the given credential. + pub fn create_permission_response( + &self, + selected_credential: Arc, + ) -> Arc { + Arc::new(PermissionResponse { + selected_credential, + presentation_definition: self.definition.clone(), + authorization_request: self.request.clone(), + }) + } + + /// Return the purpose of the presentation request. + pub fn purpose(&self) -> Option { + self.definition.purpose().map(ToOwned::to_owned) + } +} + +/// This struct is used to represent the response to a permission request. +/// +/// Use the [PermissionResponse::new] method to create a new instance of the PermissionResponse. +/// +/// The Requested Fields are created by calling the [PermissionRequest::requested_fields] method, and then +/// explicitly setting the permission to true or false, based on the holder's decision. +#[derive(Debug, Clone, uniffi::Object)] +pub struct PermissionResponse { + pub selected_credential: Arc, + pub presentation_definition: PresentationDefinition, + pub authorization_request: AuthorizationRequestObject, + // TODO: provide an optional internal mapping of `JsonPointer`s + // for selective disclosure that are selected as part of the requested fields. +} diff --git a/src/oid4vp/wallet.rs b/src/oid4vp/wallet.rs deleted file mode 100644 index ab5c5c39..00000000 --- a/src/oid4vp/wallet.rs +++ /dev/null @@ -1,390 +0,0 @@ -use crate::{ - common::*, - credentials_callback::{ - CredentialCallbackError, CredentialCallbackInterface, InputDescriptorCredentialMapRef, - PermissionRequest, RequestedField, SelectCredentialRequest, - }, - vdc_collection::Credential, - wallet::{Wallet, WalletError}, -}; - -use std::{collections::HashMap, str::FromStr, sync::Arc}; - -use anyhow::Result; -use oid4vp::{ - core::{ - authorization_request::{ - parameters::ResponseMode, - verification::{did::verify_with_resolver, RequestVerifier}, - AuthorizationRequestObject, - }, - metadata::WalletMetadata, - presentation_submission::{DescriptorMap, PresentationSubmission}, - response::{parameters::VpToken, AuthorizationResponse, UnencodedAuthorizationResponse}, - }, - holder::verifiable_presentation_builder::{ - VerifiablePresentationBuilder, VerifiablePresentationBuilderOptions, - }, - verifier::request_signer::RequestSigner, - wallet::Wallet as OID4VPWallet, -}; -use ssi::claims::jws::JWSSigner; -use ssi::dids::{ssi_json_ld::syntax::Value, DIDKey, DIDURLBuf}; -use ssi::jwk::JWK; -use uniffi::deps::log; - -// 5 minute default expiration. -const DEFAULT_EXPIRATION_IN_SECONDS: u64 = 60 * 5; - -#[uniffi::export] -impl Wallet { - /// Handle an OID4VP authorization request provided as a URL. - /// - /// This method will validate and process the request, returning a - /// redirect URL with the encoded verifiable presentation token, - /// if the presentation exchange was successful. - /// - /// If the request is invalid or cannot be processed, an error will be returned. - /// - /// # Arguments - /// - /// * `url` - The URL containing the OID4VP authorization request. - /// - /// # Returns - /// - /// An optional URL containing the OID4VP response. - /// - /// # Errors - /// - /// * If the request is invalid; - /// * If the response mode is not supported; - /// * If the response submission fails. - /// - pub async fn handle_oid4vp_request( - &self, - url: Url, - // NOTE: The callback handles UI interactions. - callback: &Arc, - ) -> Result, WalletError> { - let request = self - .validate_request(url) - .await - .map_err(|e| WalletError::OID4VPRequestValidation(e.to_string()))?; - - let response = match request.response_mode() { - ResponseMode::DirectPost => { - self.handle_unencoded_authorization_request(&request, callback) - .await? - } - // TODO: Implement support for other response modes? - mode => return Err(WalletError::OID4VPUnsupportedResponseMode(mode.to_string())), - }; - - self.submit_response(request, response) - .await - .map_err(|e| WalletError::OID4VPResponseSubmission(e.to_string())) - } -} - -// Internal wallet methods implementing the OID4VP specification. -impl Wallet { - /// Retrieves the credentials from the wallet - /// storage based on the presentation definition. - /// - /// Returns a HashMap where the Key is the input descriptor ID, - /// and the value is a vector of credentials that match the input descriptor. - fn retrieve_credentials( - &self, - requested_fields: &[Arc], - ) -> Result>>, WalletError> { - let mut map = HashMap::with_capacity(requested_fields.len()); - - for field in requested_fields.iter() { - if let Some(input_descriptor_id) = field.input_descriptor_id() { - match field.credential_type() { - None => { - // TODO: Handle the case where the credential type cannot be parsed. - // Currently this is not a hard error, and only a warning will be logged - // that the credential type could not be parsed from the input descriptor - // when the requested field was created. - log::warn!( - "Credential type could not be parsed from the input descriptor." - ); - } - Some(credential_type) => { - let keys = self - .vdc_collection - .entries_by_type(&credential_type, &self.storage_manager)?; - - let credentials = keys - .into_iter() - .filter_map(|key| { - self.vdc_collection - .get(key, &self.storage_manager) - .map_err(WalletError::from) - .transpose() - }) - .collect::>, WalletError>>()?; - - // Insert or update the map with the credentials. There may already be credentials - // mapped for the input descriptor ID, therefore need to append the new credentials. - map.entry(input_descriptor_id.clone()) - .and_modify(|v: &mut Vec>| { - v.extend(credentials.clone()) - }) - .or_insert(credentials); - } - } - } - } - - Ok(map) - } - - // Construct a DescriptorMap for the presentation submission based on the - // credentials returned from the VDC collection. - fn create_descriptor_maps( - &self, - selected_credentials: InputDescriptorCredentialMapRef, - ) -> Result)>, WalletError> { - let mut index = 0; - - Ok(selected_credentials - .read() - .map_err(|_| WalletError::from(CredentialCallbackError::RwLockError))? - .iter() - .flat_map(|(input_descriptor_id, credentials)| { - credentials - .iter() - .map(move |credential| { - let credential_descriptor_tuple = ( - DescriptorMap::new( - input_descriptor_id, - ClaimFormatDesignation::JwtVpJson, - "$".into(), - ) - // Credentials will be nested within a `vp` JSON object. - .set_path_nested(DescriptorMap::new( - credential.id().to_string(), - credential.format(), - format!("$.verifiableCredential[{index}]"), - )), - credential.to_owned(), - ); - - // Increment the index for the next descriptor. - index += 1; - - credential_descriptor_tuple - }) - .collect::)>>() - }) - .collect::>()) - } - - // Internal method for creating a verifiable presentation object. - pub(crate) async fn handle_unencoded_authorization_request( - &self, - request: &AuthorizationRequestObject, - callback: &Arc, - ) -> Result { - // Resolve the presentation definition. - let presentation_definition = request - .resolve_presentation_definition(self.http_client()) - .await - .map_err(|e| WalletError::OID4VPPresentationDefinitionResolution(e.to_string()))?; - - let presentation_submission_id = uuid::Uuid::new_v4(); - let presentation_definition_id = presentation_definition.parsed().id().clone(); - - // NOTE: This is a callback method to alert the client the information requested. - // The user can deny the request or permit the presentation. - let permission_response = callback - .permit_presentation(PermissionRequest::new(presentation_definition.parsed()))?; - - // Check if the verifiable credential(s) exists in the storage. - let credential_map = self.retrieve_credentials(permission_response.requested_fields())?; - - // TODO: Show the user the credentials, and then request a selection from the credentials. - let selected_credentials = - callback.select_credentials(SelectCredentialRequest::new(credential_map.clone())); - - // filter the credential_map to only include the selected credentials. - - // Create a descriptor map for the presentation submission based on the credentials - // returned from the selection response. - let credential_descriptor_map = - self.create_descriptor_maps(selected_credentials.inner.clone())?; - - // Create a presentation submission. - let presentation_submission = PresentationSubmission::new( - presentation_submission_id, - presentation_definition_id, - // Use the descriptor map to create the submission. - credential_descriptor_map - .iter() - .map(|(descriptor_map, _)| descriptor_map.clone()) - .collect(), - ) - .try_into() - .map_err(|e: anyhow::Error| WalletError::PresentationSubmissionCreation(e.to_string()))?; - - let vp_token = self - .create_unencoded_verifiable_presentation( - request, - credential_descriptor_map - .into_iter() - .map(|(_, credential)| credential) - .collect(), - ) - .await?; - - // Create a verifiable presentation object. - Ok(AuthorizationResponse::Unencoded( - UnencodedAuthorizationResponse(Default::default(), vp_token, presentation_submission), - )) - } - - // Internation method for creating a verifiable presentation JWT. - async fn create_unencoded_verifiable_presentation( - &self, - request: &AuthorizationRequestObject, - credential_descriptor_map: Vec>, - ) -> Result { - // NOTE: This assumes the client id scheme is DID URL. - let client_id = DIDURLBuf::from_str(&request.client_id().0) - .map_err(|e| WalletError::InvalidDIDUrl(e.to_string()))?; - - let verifiable_credential = credential_descriptor_map - .into_iter() - .map(|credential| credential.to_json().map_err(WalletError::from)) - .collect::, WalletError>>()?; - - let did_key = DIDKey::generate_url(&self.jwk()?) - .map_err(|e| WalletError::DIDKeyGenerateUrl(e.to_string()))?; - - let verifiable_presentation = - VerifiablePresentationBuilder::from_options(VerifiablePresentationBuilderOptions { - issuer: did_key.clone(), - subject: did_key, - audience: client_id, - nonce: request.nonce().clone(), - // Pull from the VDC collection - credentials: verifiable_credential.into(), - expiration_secs: DEFAULT_EXPIRATION_IN_SECONDS, - }); - - let token = verifiable_presentation - .as_base64_encoded_vp_token() - .map_err(|e| WalletError::OID4VPToken(e.to_string()))?; - - Ok(token) - } -} - -#[async_trait::async_trait] -impl RequestVerifier for Wallet { - /// Performs verification on Authorization Request Objects when `client_id_scheme` is `did`. - async fn did( - &self, - decoded_request: &AuthorizationRequestObject, - request_jwt: String, - ) -> Result<()> { - // let trusted_dids = - // .get_trusted_dids(&self.storage_manager) - // .ok(); - - verify_with_resolver( - &self.metadata, - decoded_request, - request_jwt, - // trusted_dids.as_ref().map(|did| did.as_slice()), - Some(self.trust_manager.as_slice()), - &self.jwk()?, - ) - .await?; - - Ok(()) - } -} - -// TODO: The wallet should provide a `factory` for wallet instances -// that implement the protocol-sepecific traits, e.g. `Wallet` in OID4VP. - -impl OID4VPWallet for Wallet { - type HttpClient = oid4vp::core::util::ReqwestClient; - - fn http_client(&self) -> &Self::HttpClient { - &self.client - } - - fn metadata(&self) -> &WalletMetadata { - &self.metadata - } -} - -#[async_trait::async_trait] -impl RequestSigner for Wallet { - type Error = WalletError; - - fn alg(&self) -> Result { - Ok(self - .jwk()? - .algorithm - .ok_or(WalletError::SigningAlgorithmNotFound( - "JWK algorithm not found.".into(), - ))? - .to_string()) - } - - fn jwk(&self) -> Result { - unimplemented!() - - // let jwk = self.get_jwk()?; - - // serde_json::from_str(&jwk).map_err(|e| WalletError::JWKParseError(e.to_string())) - } - - async fn sign(&self, _payload: &[u8]) -> Vec { - tracing::warn!("WARNING: use `try_sign` method instead."); - - Vec::with_capacity(0) - } - - async fn try_sign(&self, _payload: &[u8]) -> Result, Self::Error> { - unimplemented!() - // let index = self.get_active_key_index()?; - // let key_id = Key::with_prefix(KEY_MANAGER_PREFIX, &format!("{index}")); - - // self.key_manager - // .sign_payload(key_id, payload.to_vec()) - // .map_err(Into::into) - } -} - -impl JWSSigner for Wallet { - async fn fetch_info( - &self, - ) -> Result { - let jwk = self - .jwk() - .map_err(|e| ssi::claims::SignatureError::Other(e.to_string()))?; - - let algorithm = jwk.algorithm.ok_or(ssi::claims::SignatureError::Other( - "JWK algorithm not found.".into(), - ))?; - - let key_id = jwk.key_id.clone(); - - Ok(ssi::claims::jws::JWSSignerInfo { algorithm, key_id }) - } - - async fn sign_bytes( - &self, - signing_bytes: &[u8], - ) -> Result, ssi::claims::SignatureError> { - self.try_sign(signing_bytes) - .await - .map_err(|e| ssi::claims::SignatureError::Other(format!("Failed to sign bytes: {}", e))) - } -} diff --git a/src/vdc_collection.rs b/src/vdc_collection.rs index 3b732bfb..c33d9037 100644 --- a/src/vdc_collection.rs +++ b/src/vdc_collection.rs @@ -14,6 +14,7 @@ const KEY_PREFIX: &str = "Credential."; /// Verifiable Digital Credential Collection /// /// This is the main interface to credentials. +#[derive(Debug)] pub struct VdcCollection { storage: Arc, } @@ -71,8 +72,8 @@ impl VdcCollection { }; match serde_cbor::de::from_slice(&raw.0) { - Ok(x) => Ok(x), - Err(_) => Err(VdcCollectionError::DeserializeFailed), + Ok(Some(x)) => Ok(Some(x)), + _ => Err(VdcCollectionError::DeserializeFailed), } } diff --git a/tests/bindings/test.swift b/tests/bindings/test.swift index 78ed18c2..9c0f7b66 100644 --- a/tests/bindings/test.swift +++ b/tests/bindings/test.swift @@ -26,16 +26,21 @@ do { let vdc = VdcCollection(engine: storage) let mdocPayload = try String(contentsOf: URL(fileURLWithPath: "../../../tests/res/mdoc.b64")) let mdocBytes = Data(base64Encoded: mdocPayload) - let uuid = UUID.init() + let uuid = UUID() try await vdc.add( - credential: Credential( - id: uuid.uuidString, format: CredentialFormat.msoMdoc, - type: CredentialType("org.iso.18013.5.1.mDL"), payload: mdocBytes!, keyAlias: "alias")) + credential: Credential.init( + id: uuid.uuidString, + format: CredentialFormat.msoMdoc, + type: CredentialType("org.iso.18013.5.1.mDL"), + payload: mdocBytes!, + keyAlias: "alias" + )) let mdlSession = try await initializeMdlPresentation( - mdocId: uuid.uuidString, uuid: UUID.init().uuidString, storageManager: storage) + mdocId: uuid.uuidString, uuid: UUID().uuidString, storageManager: storage + ) assert( try! mdlSession.terminateSession() - == Data([0xa1, 0x66, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x14])) + == Data([0xA1, 0x66, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x14])) } catch { - assert(false, "\(error)") + assertionFailure("\(error)") } diff --git a/tests/examples/sd_vc.jwt b/tests/examples/sd_vc.jwt index 40515770..afea26f4 100644 --- a/tests/examples/sd_vc.jwt +++ b/tests/examples/sd_vc.jwt @@ -1 +1 @@ -eyJhbGciOiAiRVMyNTYiLCAidHlwIjogImV4YW1wbGUrc2Qtand0In0.eyJfc2QiOiBbIkNyUWU3UzVrcUJBSHQtbk1ZWGdjNmJkdDJTSDVhVFkxc1VfTS1QZ2tqUEkiLCAiSnpZakg0c3ZsaUgwUjNQeUVNZmVadTZKdDY5dTVxZWhabzdGN0VQWWxTRSIsICJQb3JGYnBLdVZ1Nnh5bUphZ3ZrRnNGWEFiUm9jMkpHbEFVQTJCQTRvN2NJIiwgIlRHZjRvTGJnd2Q1SlFhSHlLVlFaVTlVZEdFMHc1cnREc3JaemZVYW9tTG8iLCAiWFFfM2tQS3QxWHlYN0tBTmtxVlI2eVoyVmE1TnJQSXZQWWJ5TXZSS0JNTSIsICJYekZyendzY002R242Q0pEYzZ2Vks4QmtNbmZHOHZPU0tmcFBJWmRBZmRFIiwgImdiT3NJNEVkcTJ4Mkt3LXc1d1BFemFrb2I5aFYxY1JEMEFUTjNvUUw5Sk0iLCAianN1OXlWdWx3UVFsaEZsTV8zSmx6TWFTRnpnbGhRRzBEcGZheVF3TFVLNCJdLCAiaXNzIjogImh0dHBzOi8vaXNzdWVyLmV4YW1wbGUuY29tIiwgImlhdCI6IDE2ODMwMDAwMDAsICJleHAiOiAxODgzMDAwMDAwLCAic3ViIjogInVzZXJfNDIiLCAibmF0aW9uYWxpdGllcyI6IFt7Ii4uLiI6ICJwRm5kamtaX1ZDem15VGE2VWpsWm8zZGgta284YUlLUWM5RGxHemhhVllvIn0sIHsiLi4uIjogIjdDZjZKa1B1ZHJ5M2xjYndIZ2VaOGtoQXYxVTFPU2xlclAwVmtCSnJXWjAifV0sICJfc2RfYWxnIjogInNoYS0yNTYiLCAiY25mIjogeyJqd2siOiB7Imt0eSI6ICJFQyIsICJjcnYiOiAiUC0yNTYiLCAieCI6ICJUQ0FFUjE5WnZ1M09IRjRqNFc0dmZTVm9ISVAxSUxpbERsczd2Q2VHZW1jIiwgInkiOiAiWnhqaVdXYlpNUUdIVldLVlE0aGJTSWlyc1ZmdWVjQ0U2dDRqVDlGMkhaUSJ9fX0.RIOaMsGF0sU1W7vmSHgN6P_70Ziz0c0p1GKgJt2-T23YRDLVulwWMScbxLoIno6Aae3i7KuCjCP3hIlI2sVpKw~WyIyR0xDNDJzS1F2ZUNmR2ZyeU5STjl3IiwgImdpdmVuX25hbWUiLCAiSm9obiJd~WyJlbHVWNU9nM2dTTklJOEVZbnN4QV9BIiwgImZhbWlseV9uYW1lIiwgIkRvZSJd~WyI2SWo3dE0tYTVpVlBHYm9TNXRtdlZBIiwgImVtYWlsIiwgImpvaG5kb2VAZXhhbXBsZS5jb20iXQ~WyJlSThaV205UW5LUHBOUGVOZW5IZGhRIiwgInBob25lX251bWJlciIsICIrMS0yMDItNTU1LTAxMDEiXQ~WyJRZ19PNjR6cUF4ZTQxMmExMDhpcm9BIiwgInBob25lX251bWJlcl92ZXJpZmllZCIsIHRydWVd~WyJBSngtMDk1VlBycFR0TjRRTU9xUk9BIiwgImFkZHJlc3MiLCB7InN0cmVldF9hZGRyZXNzIjogIjEyMyBNYWluIFN0IiwgImxvY2FsaXR5IjogIkFueXRvd24iLCAicmVnaW9uIjogIkFueXN0YXRlIiwgImNvdW50cnkiOiAiVVMifV0~WyJQYzMzSk0yTGNoY1VfbEhnZ3ZfdWZRIiwgImJpcnRoZGF0ZSIsICIxOTQwLTAxLTAxIl0~WyJHMDJOU3JRZmpGWFE3SW8wOXN5YWpBIiwgInVwZGF0ZWRfYXQiLCAxNTcwMDAwMDAwXQ~WyJsa2x4RjVqTVlsR1RQVW92TU5JdkNBIiwgIlVTIl0~WyJuUHVvUW5rUkZxM0JJZUFtN0FuWEZBIiwgIkRFIl0~ \ No newline at end of file +eyJhbGciOiJFZERTQSJ9.eyJfc2RfYWxnIjoic2hhLTI1NiIsIkBjb250ZXh0IjpbImh0dHBzOi8vd3d3LnczLm9yZy9ucy9jcmVkZW50aWFscy92MiIsImh0dHBzOi8vcHVybC5pbXNnbG9iYWwub3JnL3NwZWMvb2IvdjNwMC9jb250ZXh0LTMuMC4zLmpzb24iXSwidHlwZSI6WyJWZXJpZmlhYmxlQ3JlZGVudGlhbCIsIk9wZW5CYWRnZUNyZWRlbnRpYWwiXSwiY3JlZGVudGlhbFN1YmplY3QiOnsiaWRlbnRpdHkiOlt7Ii4uLiI6ImxoeTloT3p6eWZZVFc0WWlockc4VVZRcHhFcWN1Nk9icFdvOVBhNExFRzAifSx7Imhhc2hlZCI6ZmFsc2UsImlkZW50aXR5SGFzaCI6ImpvaG4uc21pdGhAZXhhbXBsZS5jb20iLCJpZGVudGl0eVR5cGUiOiJlbWFpbEFkZHJlc3MiLCJzYWx0Ijoibm90LXVzZWQiLCJ0eXBlIjoiSWRlbnRpdHlPYmplY3QifV0sImFjaGlldmVtZW50Ijp7Im5hbWUiOiJUZWFtIE1lbWJlcnNoaXAiLCJ0eXBlIjoiQWNoaWV2ZW1lbnQifX0sImlzc3VlciI6eyJpZCI6ImRpZDpqd2s6ZXlKaGJHY2lPaUpGVXpJMU5pSXNJbU55ZGlJNklsQXRNalUySWl3aWEzUjVJam9pUlVNaUxDSjRJam9pYldKVU0yZHFPV0Z2T0dOdVMyODBNMHByY1ZSUFVtTkpRVkk0TUZnd1RVRlhRV05HWXpadlIxSk1ZeUlzSW5raU9pSmlPRlZPWTBoRE1tRkhRM0oxU1RaMFFsUldTVlkwZFc1WldFVnlTME00WkRSblJURkdaMHMwUTA1SkluMCMwIiwibmFtZSI6Ildvcmtmb3JjZSBEZXZlbG9wbWVudCBDb3VuY2lsIiwidHlwZSI6IlByb2ZpbGUifSwiYXdhcmRlZERhdGUiOiIyMDI0LTA5LTIzVDE4OjEyOjEyKzAwMDAiLCJuYW1lIjoiVGVhbU1lbWJlcnNoaXAifQ.0zGx7-Fkio8bS4EZW_odUu3F6FG7nvRVEbGKIte2OZPXGM12XEhTJptty-1ZUgPAtP_jiyU_KTP3hPrGKKNnBg~WyJoS25GV0g0R3J5dEdVMkFLUFNJSDdRIix7Imhhc2hlZCI6ZmFsc2UsImlkZW50aXR5SGFzaCI6IkpvaG4gU21pdGgiLCJpZGVudGl0eVR5cGUiOiJuYW1lIiwic2FsdCI6Im5vdC11c2VkIiwidHlwZSI6IklkZW50aXR5T2JqZWN0In1d~ \ No newline at end of file diff --git a/tests/examples/vehicle_title.json b/tests/examples/vehicle_title.json new file mode 100644 index 00000000..6df92783 --- /dev/null +++ b/tests/examples/vehicle_title.json @@ -0,0 +1,112 @@ +{ + "@context": [ + "https://www.w3.org/ns/credentials/v2", + "https://w3id.org/vvt/v1", + "https://example.gov/utopiadmv/v1" + ], + "type": ["VerifiableCredential", "VehicleTitleCredential"], + "issuer": "did:web:dmv.utopia.example", + "credentialStatus": { + "id": "https://example.gov/utopiadmv/credentials/status/3#94567", + "type": "BitstringStatusListEntry", + "statusPurpose": "revocation", + "statusListIndex": "94567", + "statusListCredential": "https://example.gov/utopiadmv/credentials/status/3" + }, + "credentialSubject": { + "type": "VehicleTitle", + "vehicle": { + "type": "Vehicle", + "vehicleIdentificationNumber": "4Y1SL65848Z411439", + "manufacturer": { + "type": "Organization", + "name": "Honda" + }, + "bodyType": "4H", + "numberOfAxles": 2, + "weight": { + "type": "QuantitativeValue", + "value": "3000", + "unitCode": "LBR" + }, + "vehicleModelDate": "1990", + "fuelType": "G", + "purchaseDate": "2020-02-01" + }, + "firstSold": { + "type": "FirstSaleInformation", + "class": "AA", + "year": "2020", + "month": "BB" + }, + "odometerReading": { + "type": "Observation", + "observationDate": "2020-02-01", + "variableMeasured": "Odometer Mileage", + "value": "100000", + "unitCode": "1M", + "description": "ODOMETER IS NOT THE ACTUAL MILEAGE" + }, + "owner": [ + { + "type": "Person", + "name": "USEDCAR COLLECTOR", + "address": { + "type": "PostalAddress", + "streetAddress": "123 LEMON AVE", + "addressLocality": "UTOPOLIS", + "addressRegion": "UTOPIA", + "postalCode": "12345" + }, + "hasCertification": { + "type": "Certification", + "about": "Driver's License Number", + "certificationIdentification": "542426814" + }, + "description": "Primary Owner" + }, + { + "type": "Person", + "name": "USEDCAR ENTHUSIAST", + "hasCertification": { + "type": "Certification", + "about": "Driver's License Number", + "certificationIdentification": "789245168" + } + } + ], + "ownerSaleRequirements": "USEDCAR COLLECTOR AND USEDCAR ENTHUSIAST", + "feesPaid": { + "type": "MonetaryAmount", + "currency": "USD", + "value": "100" + }, + "validFrom": "2024-02-01", + "titleIssueDate": "2024-02-01", + "registrationExpirationDate": "2025-02-01", + "licensePlateIdentifier": "ABC1234", + "equipmentTrustNumber": "12345678", + "lienholder": { + "type": "Organization", + "name": "USEDCAR LIENHOLDER", + "address": { + "type": "PostalAddress", + "streetAddress": "456 LEMON AVE", + "addressLocality": "UTOPOLIS", + "addressRegion": "UTOPIA", + "postalCode": "12345" + } + }, + "lienholderReleaseDate": "2020-01-01", + "controlNumber": "12345678", + "description": "OFF HIGHWAY", + "designations": "Original Taxi" + }, + "proof": { + "type": "DataIntegrity", + "cryptosuite": "ecdsa-rdfc-2019", + "verificationMethod": "did:web:dmv.utopia.example#key-1", + "proofPurpose": "assertionMethod", + "proofValue": "z4peo48uwK2EF4Fta8P...HzQMDYJ34r9gL" + } +}