mirror of
https://codeberg.org/tyy/aspm
synced 2024-12-23 01:19:28 -07:00
Scrapped the entire thing lol
I was going to refactor slowly, but it is easier just to scrap the entire binary and make a new one
This commit is contained in:
parent
409fbe5f1d
commit
ea388904c2
11 changed files with 621 additions and 753 deletions
235
Cargo.lock
generated
235
Cargo.lock
generated
|
@ -17,55 +17,6 @@ dependencies = [
|
||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "anstream"
|
|
||||||
version = "0.3.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "0ca84f3628370c59db74ee214b3263d58f9aadd9b4fe7e711fd87dc452b7f163"
|
|
||||||
dependencies = [
|
|
||||||
"anstyle",
|
|
||||||
"anstyle-parse",
|
|
||||||
"anstyle-query",
|
|
||||||
"anstyle-wincon",
|
|
||||||
"colorchoice",
|
|
||||||
"is-terminal",
|
|
||||||
"utf8parse",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "anstyle"
|
|
||||||
version = "1.0.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "3a30da5c5f2d5e72842e00bcb57657162cdabef0931f40e2deb9b4140440cecd"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "anstyle-parse"
|
|
||||||
version = "0.2.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "938874ff5980b03a87c5524b3ae5b59cf99b1d6bc836848df7bc5ada9643c333"
|
|
||||||
dependencies = [
|
|
||||||
"utf8parse",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "anstyle-query"
|
|
||||||
version = "1.0.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b"
|
|
||||||
dependencies = [
|
|
||||||
"windows-sys 0.48.0",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "anstyle-wincon"
|
|
||||||
version = "1.0.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "180abfa45703aebe0093f79badacc01b8fd4ea2e35118747e5811127f926e188"
|
|
||||||
dependencies = [
|
|
||||||
"anstyle",
|
|
||||||
"windows-sys 0.48.0",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anyhow"
|
name = "anyhow"
|
||||||
version = "1.0.71"
|
version = "1.0.71"
|
||||||
|
@ -75,34 +26,30 @@ checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8"
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ariadne-signature-profile-proto"
|
name = "ariadne-signature-profile-proto"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
|
||||||
"asp",
|
[[package]]
|
||||||
"clap",
|
name = "arrayvec"
|
||||||
"data-encoding",
|
version = "0.7.4"
|
||||||
"indoc",
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
"josekit",
|
checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711"
|
||||||
"openssl",
|
|
||||||
"reqwest",
|
|
||||||
"serde",
|
|
||||||
"serde_json",
|
|
||||||
"sha2",
|
|
||||||
"tokio",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "asp"
|
name = "asp"
|
||||||
version = "0.0.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"data-encoding",
|
"data-encoding",
|
||||||
|
"hex_color",
|
||||||
"josekit",
|
"josekit",
|
||||||
"openssl",
|
"openssl",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"serde",
|
"serde",
|
||||||
|
"serde-email",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"sha2",
|
"sha2",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
"url",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -156,54 +103,6 @@ version = "1.0.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "clap"
|
|
||||||
version = "4.3.8"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "d9394150f5b4273a1763355bd1c2ec54cc5a2593f790587bcd6b2c947cfa9211"
|
|
||||||
dependencies = [
|
|
||||||
"clap_builder",
|
|
||||||
"clap_derive",
|
|
||||||
"once_cell",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "clap_builder"
|
|
||||||
version = "4.3.8"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "9a78fbdd3cc2914ddf37ba444114bc7765bbdcb55ec9cbe6fa054f0137400717"
|
|
||||||
dependencies = [
|
|
||||||
"anstream",
|
|
||||||
"anstyle",
|
|
||||||
"bitflags",
|
|
||||||
"clap_lex",
|
|
||||||
"strsim",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "clap_derive"
|
|
||||||
version = "4.3.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b8cd2b2a819ad6eec39e8f1d6b53001af1e5469f8c177579cdaeb313115b825f"
|
|
||||||
dependencies = [
|
|
||||||
"heck",
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "clap_lex"
|
|
||||||
version = "0.5.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "colorchoice"
|
|
||||||
version = "1.0.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "core-foundation"
|
name = "core-foundation"
|
||||||
version = "0.9.3"
|
version = "0.9.3"
|
||||||
|
@ -264,6 +163,15 @@ dependencies = [
|
||||||
"crypto-common",
|
"crypto-common",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "email_address"
|
||||||
|
version = "0.2.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e2153bd83ebc09db15bcbdc3e2194d901804952e3dc96967e1cd3b0c5c32d112"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "encoding_rs"
|
name = "encoding_rs"
|
||||||
version = "0.8.32"
|
version = "0.8.32"
|
||||||
|
@ -398,6 +306,17 @@ dependencies = [
|
||||||
"version_check",
|
"version_check",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "getrandom"
|
||||||
|
version = "0.2.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
"wasi",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "h2"
|
name = "h2"
|
||||||
version = "0.3.20"
|
version = "0.3.20"
|
||||||
|
@ -429,12 +348,6 @@ version = "0.14.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a"
|
checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "heck"
|
|
||||||
version = "0.4.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hermit-abi"
|
name = "hermit-abi"
|
||||||
version = "0.2.6"
|
version = "0.2.6"
|
||||||
|
@ -450,6 +363,17 @@ version = "0.3.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286"
|
checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hex_color"
|
||||||
|
version = "2.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ff917051cbc87800de93ddcf39b59c9f2a0a4d809411a341c0ac422771219808"
|
||||||
|
dependencies = [
|
||||||
|
"arrayvec",
|
||||||
|
"rand",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "http"
|
name = "http"
|
||||||
version = "0.2.9"
|
version = "0.2.9"
|
||||||
|
@ -551,12 +475,6 @@ dependencies = [
|
||||||
"hashbrown 0.14.0",
|
"hashbrown 0.14.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "indoc"
|
|
||||||
version = "2.0.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "9f2cb48b81b1dc9f39676bf99f5499babfec7cd8fe14307f7b3d747208fb5690"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "instant"
|
name = "instant"
|
||||||
version = "0.1.12"
|
version = "0.1.12"
|
||||||
|
@ -583,18 +501,6 @@ version = "2.7.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "12b6ee2129af8d4fb011108c73d99a1b83a85977f23b82460c0ae2e25bb4b57f"
|
checksum = "12b6ee2129af8d4fb011108c73d99a1b83a85977f23b82460c0ae2e25bb4b57f"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "is-terminal"
|
|
||||||
version = "0.4.7"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "adcf93614601c8129ddf72e2d5633df827ba6551541c6d8c59520a371475be1f"
|
|
||||||
dependencies = [
|
|
||||||
"hermit-abi 0.3.1",
|
|
||||||
"io-lifetimes",
|
|
||||||
"rustix",
|
|
||||||
"windows-sys 0.48.0",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itoa"
|
name = "itoa"
|
||||||
version = "1.0.6"
|
version = "1.0.6"
|
||||||
|
@ -786,6 +692,12 @@ version = "0.3.27"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964"
|
checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ppv-lite86"
|
||||||
|
version = "0.2.17"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.63"
|
version = "1.0.63"
|
||||||
|
@ -804,6 +716,36 @@ dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand"
|
||||||
|
version = "0.8.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"rand_chacha",
|
||||||
|
"rand_core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_chacha"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
|
||||||
|
dependencies = [
|
||||||
|
"ppv-lite86",
|
||||||
|
"rand_core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_core"
|
||||||
|
version = "0.6.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
||||||
|
dependencies = [
|
||||||
|
"getrandom",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "redox_syscall"
|
name = "redox_syscall"
|
||||||
version = "0.3.5"
|
version = "0.3.5"
|
||||||
|
@ -928,6 +870,16 @@ dependencies = [
|
||||||
"serde_derive",
|
"serde_derive",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde-email"
|
||||||
|
version = "2.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c4bbdc7ba85a57465d7e2947111caf279892eba3d4891bf770401d2a6ee5de4f"
|
||||||
|
dependencies = [
|
||||||
|
"email_address",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_derive"
|
name = "serde_derive"
|
||||||
version = "1.0.164"
|
version = "1.0.164"
|
||||||
|
@ -993,12 +945,6 @@ dependencies = [
|
||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "strsim"
|
|
||||||
version = "0.10.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "2.0.22"
|
version = "2.0.22"
|
||||||
|
@ -1195,14 +1141,9 @@ dependencies = [
|
||||||
"form_urlencoded",
|
"form_urlencoded",
|
||||||
"idna",
|
"idna",
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "utf8parse"
|
|
||||||
version = "0.2.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "vcpkg"
|
name = "vcpkg"
|
||||||
version = "0.2.15"
|
version = "0.2.15"
|
||||||
|
|
14
Cargo.toml
14
Cargo.toml
|
@ -7,19 +7,5 @@ edition = "2021"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
clap = { version = "4.3.8", features = ["derive"] }
|
|
||||||
data-encoding = "2.4.0"
|
|
||||||
indoc = "2.0.1"
|
|
||||||
josekit = "0.8.3"
|
|
||||||
openssl = "0.10.55"
|
|
||||||
reqwest = "0.11.18"
|
|
||||||
serde = { version = "1.0.164", features = ["derive"] }
|
|
||||||
serde_json = "1.0.99"
|
|
||||||
sha2 = "0.10.7"
|
|
||||||
tokio = { version = "1.28.2", features = ["macros", "rt-multi-thread"] }
|
|
||||||
|
|
||||||
asp = { path = "crates/asp" }
|
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
members = ["crates/*"]
|
members = ["crates/*"]
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "asp"
|
name = "asp"
|
||||||
version = "0.0.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
@ -8,11 +8,14 @@ edition = "2021"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0.71"
|
anyhow = "1.0.71"
|
||||||
data-encoding = "2.4.0"
|
data-encoding = "2.4.0"
|
||||||
|
hex_color = { version = "2.0.0", features = ["serde"] }
|
||||||
josekit = "0.8.3"
|
josekit = "0.8.3"
|
||||||
openssl = "0.10.55"
|
openssl = "0.10.55"
|
||||||
reqwest = "0.11.18"
|
reqwest = "0.11.18"
|
||||||
serde = { version = "1.0.164", features = ["derive"] }
|
serde = { version = "1.0.164", features = ["derive"] }
|
||||||
|
serde-email = "2.0.0"
|
||||||
serde_json = "1.0.99"
|
serde_json = "1.0.99"
|
||||||
sha2 = "0.10.7"
|
sha2 = "0.10.7"
|
||||||
thiserror = "1.0.40"
|
thiserror = "1.0.40"
|
||||||
tokio = { version = "1.28.2", features = ["macros", "rt-multi-thread"] }
|
tokio = { version = "1.28.2", features = ["macros", "rt-multi-thread"] }
|
||||||
|
url = { version = "2.4.0", features = ["serde"] }
|
||||||
|
|
248
crates/asp/src/keys/mod.rs
Normal file
248
crates/asp/src/keys/mod.rs
Normal file
|
@ -0,0 +1,248 @@
|
||||||
|
use josekit::{
|
||||||
|
jwk::{
|
||||||
|
alg::{ec::EcCurve, ed::EdCurve},
|
||||||
|
Jwk,
|
||||||
|
},
|
||||||
|
jws::{
|
||||||
|
alg::{ecdsa::EcdsaJwsAlgorithm::Es256, eddsa::EddsaJwsAlgorithm::Eddsa},
|
||||||
|
JwsHeader, JwsSigner, JwsVerifier,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
use crate::utils::jwk::JwtExt;
|
||||||
|
|
||||||
|
/// An enum representing the possible types of JWK for ASPs
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum AspKeyType {
|
||||||
|
EdDSA,
|
||||||
|
ES256,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A struct representing a key that can be used to create profiles or ASPE requests
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct AspKey {
|
||||||
|
pub key_type: AspKeyType,
|
||||||
|
pub fingerprint: String,
|
||||||
|
pub jwk: Jwk,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AspKey {
|
||||||
|
pub fn from_jwk(jwk: Jwk) -> Result<Self, AspKeyError> {
|
||||||
|
match jwk.key_type() {
|
||||||
|
"OKP" => match jwk.curve() {
|
||||||
|
Some("Ed25519") => Ok(Self {
|
||||||
|
key_type: AspKeyType::EdDSA,
|
||||||
|
fingerprint: jwk
|
||||||
|
.get_fingerprint()
|
||||||
|
.or(Err(AspKeyError::FingerprintError))?,
|
||||||
|
jwk,
|
||||||
|
}),
|
||||||
|
_ => Err(AspKeyError::InvalidJwkType),
|
||||||
|
},
|
||||||
|
"EC" => match jwk.curve() {
|
||||||
|
Some("P-256") => Ok(Self {
|
||||||
|
key_type: AspKeyType::ES256,
|
||||||
|
fingerprint: jwk
|
||||||
|
.get_fingerprint()
|
||||||
|
.or(Err(AspKeyError::FingerprintError))?,
|
||||||
|
jwk,
|
||||||
|
}),
|
||||||
|
_ => Err(AspKeyError::InvalidJwkType),
|
||||||
|
},
|
||||||
|
_ => Err(AspKeyError::InvalidJwkType),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_pkcs8(key: &str) -> Result<Self, AspKeyError> {
|
||||||
|
Self::from_jwk(Jwk::from_pkcs8(key.as_bytes()).or(Err(AspKeyError::Pkcs8ConversionError))?)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn into_pkcs8(&self) -> Result<String, AspKeyError> {
|
||||||
|
self.jwk
|
||||||
|
.to_pkcs8()
|
||||||
|
.or(Err(AspKeyError::Pkcs8ConversionError))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn generate(key_type: AspKeyType) -> Result<Self, AspKeyError> {
|
||||||
|
let result: anyhow::Result<Self> = try {
|
||||||
|
match key_type {
|
||||||
|
AspKeyType::EdDSA => {
|
||||||
|
let jwk = Jwk::generate_ed_key(EdCurve::Ed25519)?;
|
||||||
|
Self {
|
||||||
|
key_type,
|
||||||
|
fingerprint: jwk.get_fingerprint()?,
|
||||||
|
jwk,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AspKeyType::ES256 => {
|
||||||
|
let jwk = Jwk::generate_ec_key(EcCurve::P256)?;
|
||||||
|
Self {
|
||||||
|
key_type,
|
||||||
|
fingerprint: jwk.get_fingerprint()?,
|
||||||
|
jwk,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
result.or(Err(AspKeyError::GenerationError))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_signer(&self) -> anyhow::Result<Box<dyn JwsSigner>> {
|
||||||
|
Ok(match self.key_type {
|
||||||
|
AspKeyType::EdDSA => Box::new(Eddsa.signer_from_jwk(&self.jwk)?),
|
||||||
|
AspKeyType::ES256 => Box::new(Es256.signer_from_jwk(&self.jwk)?),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_verifier(&self) -> anyhow::Result<Box<dyn JwsVerifier>> {
|
||||||
|
Ok(match self.key_type {
|
||||||
|
AspKeyType::EdDSA => Box::new(Eddsa.verifier_from_jwk(&self.jwk)?),
|
||||||
|
AspKeyType::ES256 => Box::new(Es256.verifier_from_jwk(&self.jwk)?),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn export_encrypted(&self, secret: &[u8]) -> anyhow::Result<String> {
|
||||||
|
self.jwk.encrypt(secret)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_encrypted(secret: &[u8], jwe: &str) -> anyhow::Result<Self> {
|
||||||
|
let jwk = Jwk::from_encrypted(secret, jwe)?;
|
||||||
|
Ok(Self::from_jwk(jwk)?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<Jwk> for AspKey {
|
||||||
|
type Error = AspKeyError;
|
||||||
|
|
||||||
|
fn try_from(value: Jwk) -> Result<Self, Self::Error> {
|
||||||
|
Self::from_jwk(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait JwsHeaderExt {
|
||||||
|
fn set_asp_key(&mut self, key: &AspKey) -> anyhow::Result<()>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl JwsHeaderExt for JwsHeader {
|
||||||
|
fn set_asp_key(&mut self, key: &AspKey) -> anyhow::Result<()> {
|
||||||
|
self.set_algorithm(match key.key_type {
|
||||||
|
AspKeyType::ES256 => "ES256",
|
||||||
|
AspKeyType::EdDSA => "EdDSA",
|
||||||
|
});
|
||||||
|
self.set_key_id(&key.fingerprint);
|
||||||
|
self.set_jwk(key.jwk.to_public_key()?);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Error, Debug, PartialEq)]
|
||||||
|
pub enum AspKeyError {
|
||||||
|
#[error("provided jwk was not a valid type")]
|
||||||
|
InvalidJwkType,
|
||||||
|
#[error("unable to calculate fingerprint of key")]
|
||||||
|
FingerprintError,
|
||||||
|
#[error("an error occurred during key generation")]
|
||||||
|
GenerationError,
|
||||||
|
#[error("unable to convert PKCS#8 key to/from a jwt key")]
|
||||||
|
Pkcs8ConversionError,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use josekit::jwk::{
|
||||||
|
alg::{ec::EcCurve, ed::EdCurve},
|
||||||
|
Jwk,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::keys::{AspKey, AspKeyError, AspKeyType};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn generate_eddsa() {
|
||||||
|
let key = AspKey::generate(AspKeyType::EdDSA);
|
||||||
|
assert!(key.is_ok(), "key should generate successfully");
|
||||||
|
let key = key.unwrap();
|
||||||
|
assert_eq!(key.jwk.key_type(), "OKP", "key should have type of OKP");
|
||||||
|
assert_eq!(
|
||||||
|
key.jwk.curve(),
|
||||||
|
Some("Ed25519"),
|
||||||
|
"key should have curve of Ed25519"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn generate_es256() {
|
||||||
|
let key = AspKey::generate(AspKeyType::ES256);
|
||||||
|
assert!(key.is_ok(), "key should generate successfully");
|
||||||
|
let key = key.unwrap();
|
||||||
|
assert_eq!(key.jwk.key_type(), "EC", "key should have type of EC");
|
||||||
|
assert_eq!(
|
||||||
|
key.jwk.curve(),
|
||||||
|
Some("P-256"),
|
||||||
|
"key should have curve of P-256"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn convert_eddsa_jwk() {
|
||||||
|
let jwk = Jwk::generate_ed_key(EdCurve::Ed25519);
|
||||||
|
assert!(jwk.is_ok(), "jwk should generate successfully");
|
||||||
|
let key = AspKey::from_jwk(jwk.unwrap());
|
||||||
|
assert!(key.is_ok(), "key should generate successfully");
|
||||||
|
let key = key.unwrap();
|
||||||
|
assert_eq!(key.jwk.key_type(), "OKP", "key should have type of OKP");
|
||||||
|
assert_eq!(
|
||||||
|
key.jwk.curve(),
|
||||||
|
Some("Ed25519"),
|
||||||
|
"key should have curve of Ed25519"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn convert_es256_jwk() {
|
||||||
|
let jwk = Jwk::generate_ec_key(EcCurve::P256);
|
||||||
|
assert!(jwk.is_ok(), "jwk should generate successfully");
|
||||||
|
let key = AspKey::from_jwk(jwk.unwrap());
|
||||||
|
assert!(key.is_ok(), "key should generate successfully");
|
||||||
|
let key = key.unwrap();
|
||||||
|
assert_eq!(key.jwk.key_type(), "EC", "key should have type of EC");
|
||||||
|
assert_eq!(
|
||||||
|
key.jwk.curve(),
|
||||||
|
Some("P-256"),
|
||||||
|
"key should have curve of P-256"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn convert_invalid_jwk() {
|
||||||
|
let jwk = Jwk::generate_ec_key(EcCurve::P521); // Invalid curve type!
|
||||||
|
assert!(jwk.is_ok(), "jwk should generate successfully");
|
||||||
|
let key = AspKey::from_jwk(jwk.unwrap());
|
||||||
|
assert_eq!(
|
||||||
|
key.err(),
|
||||||
|
Some(AspKeyError::InvalidJwkType),
|
||||||
|
"key should fail to convert"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn export_encrypted() {
|
||||||
|
let mut secret = [0u8; 32];
|
||||||
|
assert!(openssl::rand::rand_bytes(&mut secret).is_ok());
|
||||||
|
let key = AspKey::generate(AspKeyType::EdDSA);
|
||||||
|
assert!(key.is_ok());
|
||||||
|
let jwe = key.unwrap().export_encrypted(&secret);
|
||||||
|
assert!(jwe.is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn import_encrypted() {
|
||||||
|
let mut secret = [0u8; 32];
|
||||||
|
assert!(openssl::rand::rand_bytes(&mut secret).is_ok());
|
||||||
|
let key = AspKey::generate(AspKeyType::EdDSA).unwrap();
|
||||||
|
let encrypted = key.export_encrypted(&secret);
|
||||||
|
assert!(encrypted.is_ok());
|
||||||
|
let decrypted = AspKey::from_encrypted(&secret, &encrypted.unwrap());
|
||||||
|
assert!(decrypted.is_ok());
|
||||||
|
}
|
||||||
|
}
|
|
@ -1 +1,5 @@
|
||||||
|
#![feature(try_blocks)]
|
||||||
|
|
||||||
pub mod keys;
|
pub mod keys;
|
||||||
|
pub mod profiles;
|
||||||
|
pub mod utils;
|
||||||
|
|
135
crates/asp/src/profiles/mod.rs
Normal file
135
crates/asp/src/profiles/mod.rs
Normal file
|
@ -0,0 +1,135 @@
|
||||||
|
use hex_color::HexColor;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use serde_email::Email;
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
|
use crate::utils::jwt::JwtSerializable;
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug, PartialEq)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub enum AspType {
|
||||||
|
Profile,
|
||||||
|
Request,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug, PartialEq)]
|
||||||
|
pub struct AriadneSignatureProfile {
|
||||||
|
#[serde(rename = "http://ariadne.id/version")]
|
||||||
|
pub version: u8,
|
||||||
|
#[serde(rename = "http://ariadne.id/type")]
|
||||||
|
pub r#type: AspType,
|
||||||
|
#[serde(rename = "http://ariadne.id/name")]
|
||||||
|
pub name: String,
|
||||||
|
#[serde(rename = "http://ariadne.id/claims")]
|
||||||
|
pub claims: Vec<String>,
|
||||||
|
#[serde(
|
||||||
|
rename = "http://ariadne.id/description",
|
||||||
|
skip_serializing_if = "Option::is_none"
|
||||||
|
)]
|
||||||
|
pub description: Option<String>,
|
||||||
|
#[serde(
|
||||||
|
rename = "http://ariadne.id/avatar_url",
|
||||||
|
skip_serializing_if = "Option::is_none"
|
||||||
|
)]
|
||||||
|
pub avatar_url: Option<Url>,
|
||||||
|
#[serde(
|
||||||
|
rename = "http://ariadne.id/email",
|
||||||
|
skip_serializing_if = "Option::is_none"
|
||||||
|
)]
|
||||||
|
pub email: Option<Email>,
|
||||||
|
#[serde(
|
||||||
|
rename = "http://ariadne.id/color",
|
||||||
|
skip_serializing_if = "Option::is_none"
|
||||||
|
)]
|
||||||
|
pub color: Option<HexColor>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl JwtSerializable for AriadneSignatureProfile {}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use hex_color::HexColor;
|
||||||
|
use josekit::jwk::Jwk;
|
||||||
|
|
||||||
|
use crate::{keys::AspKey, utils::jwt::JwtSerialize};
|
||||||
|
|
||||||
|
use super::{AriadneSignatureProfile, AspType};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn serializing_profile_succeeds() {
|
||||||
|
// NOTE: This key is taken from the example keys in RFC 7517
|
||||||
|
let key = TryInto::<AspKey>::try_into(
|
||||||
|
Jwk::from_bytes(
|
||||||
|
r#"
|
||||||
|
{"kty":"EC",
|
||||||
|
"crv":"P-256",
|
||||||
|
"x":"MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4",
|
||||||
|
"y":"4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM",
|
||||||
|
"d":"870MB6gfuTJ4HtUnUvYMyJpr5eUZNP4Bk43bVdj3eAE"}
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let profile = AriadneSignatureProfile {
|
||||||
|
version: 0,
|
||||||
|
r#type: AspType::Profile,
|
||||||
|
name: "Example name".to_string(),
|
||||||
|
claims: vec![
|
||||||
|
"dns:example.com?type=TXT".to_string(),
|
||||||
|
"https://git.example.com/example/forgejo_proof".to_string(),
|
||||||
|
],
|
||||||
|
description: None,
|
||||||
|
avatar_url: None,
|
||||||
|
email: None,
|
||||||
|
color: Some(HexColor::from_str("#a434eb").unwrap()),
|
||||||
|
};
|
||||||
|
|
||||||
|
let jwt = profile.encode_and_sign(&key);
|
||||||
|
assert!(jwt.is_ok(), "Jwt should encode and sign successfully");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn deserializing_profile_succeeds() {
|
||||||
|
// NOTE: This key is taken from the example keys in RFC 7517
|
||||||
|
let key = TryInto::<AspKey>::try_into(
|
||||||
|
Jwk::from_bytes(
|
||||||
|
r#"
|
||||||
|
{"kty":"EC",
|
||||||
|
"crv":"P-256",
|
||||||
|
"x":"MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4",
|
||||||
|
"y":"4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM",
|
||||||
|
"d":"870MB6gfuTJ4HtUnUvYMyJpr5eUZNP4Bk43bVdj3eAE"}
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let jwt = r"eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiIsImtpZCI6IjQ1MkpGQUk2QjNLT0xLQkFVWDNNQzczREFVIiwiandrIjp7Imt0eSI6IkVDIiwiY3J2IjoiUC0yNTYiLCJ4IjoiTUtCQ1ROSWNLVVNEaWkxMXlTczM1MjZpRFo4QWlUbzdUdTZLUEFxdjdENCIsInkiOiI0RXRsNlNSVzJZaUxVck41dmZ2Vkh1aHA3eDhQeGx0bVdXbGJiTTRJRnlNIn19.eyJodHRwOi8vYXJpYWRuZS5pZC92ZXJzaW9uIjowLCJodHRwOi8vYXJpYWRuZS5pZC90eXBlIjoicHJvZmlsZSIsImh0dHA6Ly9hcmlhZG5lLmlkL25hbWUiOiJFeGFtcGxlIG5hbWUiLCJodHRwOi8vYXJpYWRuZS5pZC9jbGFpbXMiOlsiZG5zOmV4YW1wbGUuY29tP3R5cGU9VFhUIiwiaHR0cHM6Ly9naXQuZXhhbXBsZS5jb20vZXhhbXBsZS9mb3JnZWpvX3Byb29mIl0sImh0dHA6Ly9hcmlhZG5lLmlkL2NvbG9yIjoiI0E0MzRFQiJ9.u5AbAqRpyXetXwU_QqpZrieNzwZGCRZ0tFTL4FoIwPRiZZ9iIGBnqs7PWbsd0iHQpYT_Q7s1GmwggGssM9ttxQ";
|
||||||
|
|
||||||
|
let profile = AriadneSignatureProfile::decode_and_verify(jwt, &key);
|
||||||
|
assert!(profile.is_ok(), "Profile should parse and verify correctly");
|
||||||
|
let profile = profile.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
*profile,
|
||||||
|
AriadneSignatureProfile {
|
||||||
|
version: 0,
|
||||||
|
r#type: AspType::Profile,
|
||||||
|
name: "Example name".to_string(),
|
||||||
|
claims: vec![
|
||||||
|
"dns:example.com?type=TXT".to_string(),
|
||||||
|
"https://git.example.com/example/forgejo_proof".to_string()
|
||||||
|
],
|
||||||
|
description: None,
|
||||||
|
avatar_url: None,
|
||||||
|
email: None,
|
||||||
|
color: Some(HexColor::from_str("#a434eb").unwrap()),
|
||||||
|
},
|
||||||
|
"Profile should decode correctly"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,27 +1,33 @@
|
||||||
|
use anyhow::Context;
|
||||||
use data_encoding::{BASE32_NOPAD, BASE64URL_NOPAD, BASE64_NOPAD};
|
use data_encoding::{BASE32_NOPAD, BASE64URL_NOPAD, BASE64_NOPAD};
|
||||||
use josekit::{
|
use josekit::{
|
||||||
|
jwe::JweHeader,
|
||||||
|
jwe::{self, alg::aesgcmkw::AesgcmkwJweAlgorithm::A256gcmkw},
|
||||||
jwk::{alg::ec::EcKeyPair, Jwk},
|
jwk::{alg::ec::EcKeyPair, Jwk},
|
||||||
jws::ES256,
|
jws::ES256,
|
||||||
};
|
};
|
||||||
use openssl::pkey::PKey;
|
use openssl::pkey::PKey;
|
||||||
use sha2::{Digest, Sha512};
|
use sha2::{Digest, Sha512};
|
||||||
use anyhow::Context;
|
|
||||||
|
|
||||||
pub trait JwtExt {
|
pub trait JwtExt {
|
||||||
fn get_fingerprint(&self) -> anyhow::Result<String>;
|
fn get_fingerprint(&self) -> anyhow::Result<String>;
|
||||||
fn to_pkcs8(&self) -> anyhow::Result<String>;
|
fn to_pkcs8(&self) -> anyhow::Result<String>;
|
||||||
fn from_pkcs8(pkcs8: &[u8]) -> anyhow::Result<Jwk>;
|
fn from_pkcs8(pkcs8: &[u8]) -> anyhow::Result<Jwk>;
|
||||||
|
fn from_encrypted(secret: &[u8], jwe: &str) -> anyhow::Result<Jwk>;
|
||||||
|
fn encrypt(&self, secret: &[u8]) -> anyhow::Result<String>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl JwtExt for Jwk {
|
impl JwtExt for Jwk {
|
||||||
fn get_fingerprint(&self) -> anyhow::Result<String> {
|
fn get_fingerprint(&self) -> anyhow::Result<String> {
|
||||||
// Get the "x" value of the JWK
|
// Get the "x" value of the JWK
|
||||||
let fingerprint = self.parameter("x").context(r#"Jwk "x" parameter was not present"#)?;
|
let fingerprint = self
|
||||||
|
.parameter("x")
|
||||||
|
.context(r#"Jwk "x" parameter was not present"#)?;
|
||||||
// Base64url decode the "x" value and use that as the public key value
|
// Base64url decode the "x" value and use that as the public key value
|
||||||
let fingerprint = BASE64URL_NOPAD
|
let fingerprint = BASE64URL_NOPAD
|
||||||
.decode(
|
.decode(
|
||||||
// The as_str() can be unwrapped safely because it is impossible to create a Jwk struct where the "x" value is not a string
|
// The as_str() can be unwrapped safely because it is impossible to create a Jwk struct where the "x" value is not a string
|
||||||
fingerprint.as_str().unwrap().as_bytes()
|
fingerprint.as_str().unwrap().as_bytes(),
|
||||||
)
|
)
|
||||||
.unwrap(); // The decode() can be unwrapped safely because it is impossible to create a Jwk struct where the "x" value is not base64url decodable
|
.unwrap(); // The decode() can be unwrapped safely because it is impossible to create a Jwk struct where the "x" value is not base64url decodable
|
||||||
// Sha512 hash the public key
|
// Sha512 hash the public key
|
||||||
|
@ -46,9 +52,7 @@ impl JwtExt for Jwk {
|
||||||
let key_pair = EcKeyPair::from_jwk(self)?;
|
let key_pair = EcKeyPair::from_jwk(self)?;
|
||||||
let pem_private = key_pair.to_pem_private_key();
|
let pem_private = key_pair.to_pem_private_key();
|
||||||
let pkey = PKey::private_key_from_pem(&pem_private)?;
|
let pkey = PKey::private_key_from_pem(&pem_private)?;
|
||||||
let pkcs8 = pkey
|
let pkcs8 = pkey.as_ref().private_key_to_pkcs8()?;
|
||||||
.as_ref()
|
|
||||||
.private_key_to_pkcs8()?;
|
|
||||||
let encoded = BASE64_NOPAD.encode(&pkcs8);
|
let encoded = BASE64_NOPAD.encode(&pkcs8);
|
||||||
Ok(encoded)
|
Ok(encoded)
|
||||||
}
|
}
|
||||||
|
@ -58,4 +62,25 @@ impl JwtExt for Jwk {
|
||||||
let key_pair = ES256.key_pair_from_der(decoded_pkcs8)?;
|
let key_pair = ES256.key_pair_from_der(decoded_pkcs8)?;
|
||||||
Ok(key_pair.to_jwk_key_pair())
|
Ok(key_pair.to_jwk_key_pair())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn encrypt(&self, secret: &[u8]) -> anyhow::Result<String> {
|
||||||
|
let mut header = JweHeader::new();
|
||||||
|
header.set_content_type("jwt+json");
|
||||||
|
header.set_content_encryption("A128CBC-HS256");
|
||||||
|
|
||||||
|
let payload = self.to_string();
|
||||||
|
|
||||||
|
let encrypter = A256gcmkw.encrypter_from_bytes(secret)?;
|
||||||
|
let jwt = jwe::serialize_compact(payload.as_bytes(), &header, &encrypter)?;
|
||||||
|
|
||||||
|
Ok(jwt)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_encrypted(secret: &[u8], jwe: &str) -> anyhow::Result<Jwk> {
|
||||||
|
let decrypter = A256gcmkw.decrypter_from_bytes(secret)?;
|
||||||
|
let (deserialized, _) = jwe::deserialize_compact(jwe, &decrypter)?;
|
||||||
|
let jwk = Jwk::from_bytes(&deserialized)?;
|
||||||
|
|
||||||
|
Ok(jwk)
|
||||||
|
}
|
||||||
}
|
}
|
104
crates/asp/src/utils/jwt.rs
Normal file
104
crates/asp/src/utils/jwt.rs
Normal file
|
@ -0,0 +1,104 @@
|
||||||
|
use anyhow::Context;
|
||||||
|
use josekit::{
|
||||||
|
jws::JwsHeader,
|
||||||
|
jwt::{self, JwtPayload},
|
||||||
|
};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
use crate::keys::{AspKey, JwsHeaderExt};
|
||||||
|
|
||||||
|
pub trait JwtSerializable {}
|
||||||
|
|
||||||
|
pub trait JwtSerialize {
|
||||||
|
fn encode_and_sign(&self, key: &AspKey) -> Result<String, JwtSerializationError>;
|
||||||
|
fn decode_and_verify(jwt: &str, key: &AspKey) -> Result<Box<Self>, JwtDeserializationError>
|
||||||
|
where
|
||||||
|
Self: for<'de> Deserialize<'de> + JwtSerializable;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Error, Debug)]
|
||||||
|
pub enum JwtSerializationError {
|
||||||
|
#[error("provided jwk was unable to be used")]
|
||||||
|
JwkUsageError,
|
||||||
|
#[error("provided payload was unable to be serialized")]
|
||||||
|
PayloadSerializationError,
|
||||||
|
#[error("jwt was unable to be serialized for an unknown reason")]
|
||||||
|
SerializationError,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Error, Debug)]
|
||||||
|
pub enum JwtDeserializationError {
|
||||||
|
#[error("provided jwk was not the correct key for the provided jwt")]
|
||||||
|
WrongJwkError,
|
||||||
|
#[error("jwt header was unable to be decoded")]
|
||||||
|
HeaderDecodeError,
|
||||||
|
#[error("jwt was unable to be decoded and verified")]
|
||||||
|
JwtDecodeError,
|
||||||
|
#[error("provided jwk was unable to be used")]
|
||||||
|
JwkUsageError,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<O: JwtSerializable + Serialize + for<'de> Deserialize<'de>> JwtSerialize for O {
|
||||||
|
fn encode_and_sign(&self, key: &AspKey) -> Result<String, JwtSerializationError> {
|
||||||
|
// Construct the JWT header
|
||||||
|
let mut header = JwsHeader::new();
|
||||||
|
header.set_token_type("JWT");
|
||||||
|
header
|
||||||
|
.set_asp_key(&key)
|
||||||
|
.or(Err(JwtSerializationError::JwkUsageError))?;
|
||||||
|
|
||||||
|
// Construct the payload
|
||||||
|
let value =
|
||||||
|
serde_json::to_value(self).or(Err(JwtSerializationError::PayloadSerializationError))?;
|
||||||
|
let map = value
|
||||||
|
.as_object()
|
||||||
|
.context("serialized struct was not a Map")
|
||||||
|
.or(Err(JwtSerializationError::PayloadSerializationError))?;
|
||||||
|
let payload = JwtPayload::from_map(map.clone())
|
||||||
|
.or(Err(JwtSerializationError::PayloadSerializationError))?;
|
||||||
|
|
||||||
|
// Sign it into a JWT
|
||||||
|
Ok(jwt::encode_with_signer(
|
||||||
|
&payload,
|
||||||
|
&header,
|
||||||
|
&*key
|
||||||
|
.create_signer()
|
||||||
|
.or(Err(JwtSerializationError::JwkUsageError))?,
|
||||||
|
)
|
||||||
|
.or(Err(JwtSerializationError::SerializationError))?)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decode_and_verify(jwt: &str, key: &AspKey) -> Result<Box<Self>, JwtDeserializationError>
|
||||||
|
where
|
||||||
|
Self: for<'de> serde::Deserialize<'de> + JwtSerializable,
|
||||||
|
{
|
||||||
|
// Decode the header and check if the key id is correct
|
||||||
|
let header = jwt::decode_header(jwt).or(Err(JwtDeserializationError::HeaderDecodeError))?;
|
||||||
|
let key_id = header
|
||||||
|
.claim("kid")
|
||||||
|
.context("kid value on header was missing")
|
||||||
|
.or(Err(JwtDeserializationError::HeaderDecodeError))?
|
||||||
|
.as_str()
|
||||||
|
.context("kid value on header was not a string")
|
||||||
|
.or(Err(JwtDeserializationError::HeaderDecodeError))?;
|
||||||
|
|
||||||
|
if key.fingerprint != key_id {
|
||||||
|
return Err(JwtDeserializationError::WrongJwkError);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Decode the rest of the JWT
|
||||||
|
let (payload, _) = jwt::decode_with_verifier(
|
||||||
|
jwt,
|
||||||
|
&*key
|
||||||
|
.create_verifier()
|
||||||
|
.or(Err(JwtDeserializationError::JwkUsageError))?,
|
||||||
|
)
|
||||||
|
.or(Err(JwtDeserializationError::JwtDecodeError))?;
|
||||||
|
let claims: Self =
|
||||||
|
serde_json::from_value(serde_json::Value::Object(payload.claims_set().clone()))
|
||||||
|
.or(Err(JwtDeserializationError::JwtDecodeError))?;
|
||||||
|
|
||||||
|
Ok(Box::new(claims))
|
||||||
|
}
|
||||||
|
}
|
2
crates/asp/src/utils/mod.rs
Normal file
2
crates/asp/src/utils/mod.rs
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
pub mod jwk;
|
||||||
|
pub mod jwt;
|
516
src/main.rs
516
src/main.rs
|
@ -1,515 +1,3 @@
|
||||||
mod structs;
|
fn main() {
|
||||||
|
todo!();
|
||||||
use std::{
|
|
||||||
fs::File,
|
|
||||||
io::{Read, Write},
|
|
||||||
time::SystemTime,
|
|
||||||
};
|
|
||||||
|
|
||||||
use clap::{Parser, Subcommand};
|
|
||||||
use data_encoding::{BASE64_NOPAD};
|
|
||||||
use indoc::printdoc;
|
|
||||||
use josekit::{
|
|
||||||
jwk::{
|
|
||||||
alg::ec::{EcCurve, EcKeyPair},
|
|
||||||
Jwk,
|
|
||||||
},
|
|
||||||
jws::{JwsHeader, ES256},
|
|
||||||
jwt::{self, JwtPayload},
|
|
||||||
};
|
|
||||||
use openssl::pkey::PKey;
|
|
||||||
use reqwest::{header, redirect, StatusCode};
|
|
||||||
use asp::keys::JwtExt;
|
|
||||||
|
|
||||||
use crate::structs::{AspClaims, AspType, AspRequestAction, AspRequestClaims};
|
|
||||||
|
|
||||||
// Prototype implementation of ASPs
|
|
||||||
#[derive(Parser)]
|
|
||||||
#[command(author, version, about, long_about = None)]
|
|
||||||
#[command(propagate_version = true)]
|
|
||||||
struct Cli {
|
|
||||||
#[command(subcommand)]
|
|
||||||
command: Commands,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Subcommand)]
|
|
||||||
enum AspeCommand {
|
|
||||||
/// Uploads a profile to an aspe server
|
|
||||||
Upload {
|
|
||||||
/// The path of the key file (stored in JSON JWK format) to be used when signing the profile, or - to parse a JWK from stdin (but only this or the profile can be taken from stdin at once). This can be generated with the `create-key` subcommand.
|
|
||||||
#[arg(short, long)]
|
|
||||||
key: String,
|
|
||||||
/// The JWT profile to upload to the server (or - for stdin, but only this or the key can be taken from stdin at once)
|
|
||||||
profile: String,
|
|
||||||
},
|
|
||||||
/// Deletes a profile on an aspe server
|
|
||||||
Delete {
|
|
||||||
/// The path of the key file (stored in JSON JWK format) to be used when signing the profile, or - to parse a JWK from stdin (but only this or the profile can be taken from stdin at once). This can be generated with the `create-key` subcommand.
|
|
||||||
#[arg(short, long)]
|
|
||||||
key: String,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Subcommand)]
|
|
||||||
enum Commands {
|
|
||||||
/// Creates a new JWK (EC, p-256)
|
|
||||||
CreateKey {
|
|
||||||
/// The file to output to (or - for stdout)
|
|
||||||
#[arg(short, long)]
|
|
||||||
file: String,
|
|
||||||
},
|
|
||||||
/// Converts a pkcs8 private key to a JWK (Ec, p-256)
|
|
||||||
ConvertFromPkcs8 {
|
|
||||||
/// The pkcs8-encoded private key
|
|
||||||
data: String,
|
|
||||||
/// The file to output to (or - for stdout)
|
|
||||||
#[arg(short, long)]
|
|
||||||
file: String,
|
|
||||||
},
|
|
||||||
/// Converts a JWT (Ec, p-256) to a pkcs8 private key
|
|
||||||
ConvertToPkcs8 {
|
|
||||||
/// The file to input the JWT from (or - for stdin)
|
|
||||||
#[arg(short, long)]
|
|
||||||
key: String,
|
|
||||||
},
|
|
||||||
/// Creates a profile with the specified claims
|
|
||||||
CreateProfile {
|
|
||||||
/// The path of the key file (stored in JSON JWK format) to be used when signing the profile, or - to parse a JWK from stdin. This can be generated with the `create-key` subcommand.
|
|
||||||
#[arg(short, long)]
|
|
||||||
key: String,
|
|
||||||
/// The claims to use on the profile
|
|
||||||
#[arg(short, long)]
|
|
||||||
claim: Vec<String>,
|
|
||||||
/// The name to use for the profile
|
|
||||||
#[arg(short, long)]
|
|
||||||
name: String,
|
|
||||||
/// The description to use for the profile
|
|
||||||
#[arg(short, long)]
|
|
||||||
description: Option<String>,
|
|
||||||
/// The url for the avatar of this profile
|
|
||||||
#[arg(short, long)]
|
|
||||||
avatar_url: Option<String>,
|
|
||||||
/// The email to use for this profile
|
|
||||||
#[arg(short, long)]
|
|
||||||
email: Option<String>,
|
|
||||||
/// The color to use for this profile
|
|
||||||
#[arg(long)]
|
|
||||||
color: Option<String>,
|
|
||||||
},
|
|
||||||
/// Parses and displays nicely a profile JWT
|
|
||||||
ParseProfile {
|
|
||||||
/// The file (or "-") that contains the JWT
|
|
||||||
profile: String,
|
|
||||||
},
|
|
||||||
Aspe {
|
|
||||||
#[command(subcommand)]
|
|
||||||
command: AspeCommand,
|
|
||||||
/// The domain of the server to use for aspe operations
|
|
||||||
#[arg(short, long)]
|
|
||||||
server: String,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::main]
|
|
||||||
async fn main() {
|
|
||||||
let http = reqwest::Client::builder()
|
|
||||||
.redirect(redirect::Policy::none())
|
|
||||||
.user_agent(format!(
|
|
||||||
"{}/{}",
|
|
||||||
env!("CARGO_PKG_NAME"),
|
|
||||||
env!("CARGO_PKG_VERSION")
|
|
||||||
))
|
|
||||||
.build()
|
|
||||||
.expect("unable to create http client");
|
|
||||||
let args = Cli::parse();
|
|
||||||
|
|
||||||
match &args.command {
|
|
||||||
Commands::CreateKey { file } => {
|
|
||||||
let key = Jwk::generate_ec_key(EcCurve::P256).expect("Unable to generate EC256 JWK");
|
|
||||||
if file != "-" {
|
|
||||||
let mut file = File::create("key.jwk").unwrap();
|
|
||||||
file.write_all(key.to_string().as_bytes())
|
|
||||||
.expect("unable to write key to file");
|
|
||||||
} else {
|
|
||||||
std::io::stdout()
|
|
||||||
.write_all(key.to_string().as_bytes())
|
|
||||||
.expect("unable to write key to stdout");
|
|
||||||
};
|
|
||||||
}
|
|
||||||
Commands::ConvertFromPkcs8 { data, file } => {
|
|
||||||
let keypair = ES256
|
|
||||||
.key_pair_from_der(
|
|
||||||
BASE64_NOPAD
|
|
||||||
.decode(data.as_bytes())
|
|
||||||
.expect("unable to base64 decode input private key"),
|
|
||||||
)
|
|
||||||
.expect("unable to parse private key into an ES256 key pair");
|
|
||||||
let jwk = keypair.to_jwk_key_pair();
|
|
||||||
|
|
||||||
if file != "-" {
|
|
||||||
let mut file = File::create("key.jwk").unwrap();
|
|
||||||
file.write_all(jwk.to_string().as_bytes())
|
|
||||||
.expect("unable to write key to file");
|
|
||||||
} else {
|
|
||||||
std::io::stdout()
|
|
||||||
.write_all(jwk.to_string().as_bytes())
|
|
||||||
.expect("unable to write key to stdout");
|
|
||||||
};
|
|
||||||
}
|
|
||||||
Commands::ConvertToPkcs8 { key } => {
|
|
||||||
let mut jwk_bytes = Vec::new();
|
|
||||||
if key == "-" {
|
|
||||||
std::io::stdin()
|
|
||||||
.read_to_end(&mut jwk_bytes)
|
|
||||||
.expect("unable to read from stdin");
|
|
||||||
} else {
|
|
||||||
File::open(key)
|
|
||||||
.expect("unable to open keyfile")
|
|
||||||
.read_to_end(&mut jwk_bytes)
|
|
||||||
.expect("unable to read keyfile");
|
|
||||||
};
|
|
||||||
|
|
||||||
let jwk = Jwk::from_bytes(jwk_bytes).expect("unable to parse jwk");
|
|
||||||
|
|
||||||
// This was done because I couldn't find an easy way to get a PKCS#8 encoded private key from the josekit Jwk struct, so I did the following as a workaround, directly using the openssl library:
|
|
||||||
// 1. Get the josekit EcKeyPair from the Jwk
|
|
||||||
// 2. Convert that to a PEM private key
|
|
||||||
// 3. Get the openssl Pkey struct by loading the PRM private key
|
|
||||||
// 4. Convert that Pkey into the PKCS#8 encoded private key (and then base64 encode it)
|
|
||||||
let key_pair = EcKeyPair::from_jwk(&jwk).expect("unable to create EcKeyPair from jwk");
|
|
||||||
let pem_private = key_pair.to_pem_private_key();
|
|
||||||
let pkey = PKey::private_key_from_pem(&pem_private).unwrap();
|
|
||||||
println!(
|
|
||||||
"{}",
|
|
||||||
BASE64_NOPAD.encode(&pkey.as_ref().private_key_to_pkcs8().unwrap())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Commands::CreateProfile {
|
|
||||||
key,
|
|
||||||
claim,
|
|
||||||
name,
|
|
||||||
description,
|
|
||||||
avatar_url,
|
|
||||||
email,
|
|
||||||
color,
|
|
||||||
} => {
|
|
||||||
let mut jwk_bytes = Vec::new();
|
|
||||||
if key == "-" {
|
|
||||||
std::io::stdin()
|
|
||||||
.read_to_end(&mut jwk_bytes)
|
|
||||||
.expect("unable to read from stdin");
|
|
||||||
} else {
|
|
||||||
File::open(key)
|
|
||||||
.expect("unable to open keyfile")
|
|
||||||
.read_to_end(&mut jwk_bytes)
|
|
||||||
.expect("unable to read keyfile");
|
|
||||||
};
|
|
||||||
|
|
||||||
let jwk = Jwk::from_bytes(jwk_bytes).expect("unable to parse jwk");
|
|
||||||
|
|
||||||
// Derive the key fingerprint from the JWT
|
|
||||||
let fingerprint = jwk.get_fingerprint().expect("unable to calculate jwk fingerprint");
|
|
||||||
|
|
||||||
// Construct the JWT header
|
|
||||||
let mut header = JwsHeader::new();
|
|
||||||
header.set_token_type("JWT");
|
|
||||||
header.set_algorithm("ES256");
|
|
||||||
header.set_jwk(
|
|
||||||
jwk.to_public_key()
|
|
||||||
.expect("unable to convert jwk to public key"),
|
|
||||||
);
|
|
||||||
header.set_key_id(fingerprint);
|
|
||||||
|
|
||||||
// Construct the JWT payload
|
|
||||||
let payload = JwtPayload::from_map(
|
|
||||||
serde_json::to_value(AspClaims {
|
|
||||||
name: name.clone(),
|
|
||||||
version: 0,
|
|
||||||
r#type: AspType::Profile,
|
|
||||||
claims: claim.clone(),
|
|
||||||
description: description.clone(),
|
|
||||||
avatar_url: avatar_url.clone(),
|
|
||||||
email: email.clone(),
|
|
||||||
color: color.clone(),
|
|
||||||
})
|
|
||||||
.unwrap()
|
|
||||||
.as_object()
|
|
||||||
.unwrap()
|
|
||||||
.clone(),
|
|
||||||
)
|
|
||||||
.expect("unable to create payload from map of claims");
|
|
||||||
|
|
||||||
// Construct the JWT itself
|
|
||||||
let signer = ES256
|
|
||||||
.signer_from_jwk(&jwk)
|
|
||||||
.expect("unable to convert jwk to jwt signer");
|
|
||||||
let jwt = jwt::encode_with_signer(&payload, &header, &signer)
|
|
||||||
.expect("unable to create and sign jwt");
|
|
||||||
println!("{jwt}");
|
|
||||||
}
|
|
||||||
Commands::ParseProfile { profile } => {
|
|
||||||
// Get the profile JWT
|
|
||||||
let mut jwt_bytes = Vec::new();
|
|
||||||
if profile == "-" {
|
|
||||||
std::io::stdin()
|
|
||||||
.read_to_end(&mut jwt_bytes)
|
|
||||||
.expect("unable to read from stdin");
|
|
||||||
} else {
|
|
||||||
File::open(profile)
|
|
||||||
.expect("unable to open jwt file")
|
|
||||||
.read_to_end(&mut jwt_bytes)
|
|
||||||
.expect("unable to read jwt file");
|
|
||||||
};
|
|
||||||
// Strip newlines
|
|
||||||
let jwt_bytes = jwt_bytes.strip_suffix(&[10]).unwrap_or(&jwt_bytes[..]);
|
|
||||||
|
|
||||||
// Parse the header of the JWT in order to fetch the JWK from it
|
|
||||||
let header = jwt::decode_header(jwt_bytes).expect("unable to decode header");
|
|
||||||
let jwk = Jwk::from_map(
|
|
||||||
header
|
|
||||||
.claim("jwk")
|
|
||||||
.expect("jwt does not have key embedded")
|
|
||||||
.as_object()
|
|
||||||
.expect("embedded jwk is not object type")
|
|
||||||
.clone(),
|
|
||||||
)
|
|
||||||
.expect("unable to parse embedded jwk");
|
|
||||||
|
|
||||||
// Parse and verify the JWT
|
|
||||||
let verifier = ES256
|
|
||||||
.verifier_from_jwk(&jwk)
|
|
||||||
.expect("unable to create verifier from jwk");
|
|
||||||
let (payload, _) = jwt::decode_with_verifier(&jwt_bytes, &verifier)
|
|
||||||
.expect("unable to parse and verify jwt");
|
|
||||||
|
|
||||||
let claims: AspClaims =
|
|
||||||
serde_json::from_value(serde_json::Value::Object(payload.claims_set().clone()))
|
|
||||||
.expect("unable to deserialize jwt payload claims");
|
|
||||||
|
|
||||||
printdoc! {
|
|
||||||
"
|
|
||||||
Profile fingerprint: {fingerprint}
|
|
||||||
Profile version: {version}
|
|
||||||
|
|
||||||
Name: {name}
|
|
||||||
Email: {email}
|
|
||||||
Avatar url: {avatar_url}
|
|
||||||
Description:
|
|
||||||
| {description}
|
|
||||||
Color: {color}
|
|
||||||
Claims:
|
|
||||||
{claims_formatted}
|
|
||||||
",
|
|
||||||
fingerprint = jwk.get_fingerprint().expect("unable to calculate jwt fingerprint"),
|
|
||||||
version = claims.version,
|
|
||||||
name = claims.name,
|
|
||||||
description = claims.description.unwrap_or("N/A".to_string()),
|
|
||||||
email = claims.email.unwrap_or("N/A".to_string()),
|
|
||||||
avatar_url = claims.avatar_url.unwrap_or("N/A".to_string()),
|
|
||||||
color = claims.color.unwrap_or("N/A".to_string()),
|
|
||||||
claims_formatted = claims.claims.iter().map(|claim| format!("- {claim}")).collect::<Vec<String>>().join("\n")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Commands::Aspe { command, server } => {
|
|
||||||
match command {
|
|
||||||
AspeCommand::Upload {
|
|
||||||
key,
|
|
||||||
profile,
|
|
||||||
} => {
|
|
||||||
if key == "-" && profile == "-" {
|
|
||||||
panic!("Only one of `--key <key>` and `<profile>` can be `-` at a time")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Obtain the JWK w/ private key
|
|
||||||
let mut jwk_bytes = Vec::new();
|
|
||||||
if key == "-" {
|
|
||||||
std::io::stdin()
|
|
||||||
.read_to_end(&mut jwk_bytes)
|
|
||||||
.expect("unable to read from stdin");
|
|
||||||
} else {
|
|
||||||
File::open(key)
|
|
||||||
.expect("unable to open keyfile")
|
|
||||||
.read_to_end(&mut jwk_bytes)
|
|
||||||
.expect("unable to read keyfile");
|
|
||||||
};
|
|
||||||
|
|
||||||
let jwk = Jwk::from_bytes(jwk_bytes).expect("unable to parse jwk");
|
|
||||||
|
|
||||||
// Obtain the JWT profile
|
|
||||||
let mut jwt_bytes = Vec::new();
|
|
||||||
if profile == "-" {
|
|
||||||
std::io::stdin()
|
|
||||||
.read_to_end(&mut jwt_bytes)
|
|
||||||
.expect("unable to read from stdin");
|
|
||||||
} else {
|
|
||||||
File::open(profile)
|
|
||||||
.expect("unable to open jwt file")
|
|
||||||
.read_to_end(&mut jwt_bytes)
|
|
||||||
.expect("unable to read jwt file");
|
|
||||||
};
|
|
||||||
// Strip newlines
|
|
||||||
let jwt_bytes = jwt_bytes.strip_suffix(&[10]).unwrap_or(&jwt_bytes[..]);
|
|
||||||
|
|
||||||
// Construct a request JWT
|
|
||||||
|
|
||||||
// Construct the JWT header
|
|
||||||
let mut header = JwsHeader::new();
|
|
||||||
header.set_token_type("JWT");
|
|
||||||
header.set_algorithm("ES256");
|
|
||||||
header.set_jwk(
|
|
||||||
jwk.to_public_key()
|
|
||||||
.expect("unable to convert jwk to public key"),
|
|
||||||
);
|
|
||||||
header.set_key_id(jwk.get_fingerprint().expect("unable to calculate jwk fingerprint"));
|
|
||||||
|
|
||||||
// Construct the payload
|
|
||||||
let mut payload = JwtPayload::from_map(
|
|
||||||
serde_json::to_value(AspRequestClaims {
|
|
||||||
version: 0,
|
|
||||||
r#type: AspType::Request,
|
|
||||||
action: AspRequestAction::Create,
|
|
||||||
aspe_uri: None,
|
|
||||||
profile_jws: Some(
|
|
||||||
String::from_utf8(jwt_bytes.to_vec())
|
|
||||||
.expect("unable to parse jwt bytes into String"),
|
|
||||||
),
|
|
||||||
})
|
|
||||||
.unwrap()
|
|
||||||
.as_object()
|
|
||||||
.unwrap()
|
|
||||||
.clone(),
|
|
||||||
)
|
|
||||||
.expect("unable to create payload from map of claims");
|
|
||||||
payload.set_issued_at(&SystemTime::now());
|
|
||||||
|
|
||||||
// Construct the signer
|
|
||||||
let signer = ES256
|
|
||||||
.signer_from_jwk(&jwk)
|
|
||||||
.expect("unable to construct signer from jwk");
|
|
||||||
|
|
||||||
// Construct the actual jwt
|
|
||||||
let jwt = jwt::encode_with_signer(&payload, &header.into(), &signer)
|
|
||||||
.expect("unable to create and sign jwt");
|
|
||||||
|
|
||||||
// Send the request
|
|
||||||
let response = http
|
|
||||||
.post(format!("https://{server}/.well-known/aspe/post"))
|
|
||||||
.header(header::CONTENT_TYPE, "application/jose; charset=UTF-8")
|
|
||||||
.body(jwt)
|
|
||||||
.send()
|
|
||||||
.await
|
|
||||||
.expect("unable to send http request to server");
|
|
||||||
|
|
||||||
match response.status() {
|
|
||||||
StatusCode::CREATED => {
|
|
||||||
dbg!(response.text().await.unwrap());
|
|
||||||
}
|
|
||||||
StatusCode::BAD_REQUEST => {
|
|
||||||
dbg!(response.text().await.unwrap());
|
|
||||||
panic!("Request returned 400, did the request take over 60 seconds to send?")
|
|
||||||
}
|
|
||||||
StatusCode::TOO_MANY_REQUESTS => {
|
|
||||||
dbg!(response.text().await.unwrap());
|
|
||||||
panic!("Ratelimited");
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
dbg!(response.status());
|
|
||||||
dbg!(response.text().await.unwrap());
|
|
||||||
|
|
||||||
panic!("wtf")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
AspeCommand::Delete {
|
|
||||||
key,
|
|
||||||
} => {
|
|
||||||
// Obtain the JWK w/ private key
|
|
||||||
let mut jwk_bytes = Vec::new();
|
|
||||||
if key == "-" {
|
|
||||||
std::io::stdin()
|
|
||||||
.read_to_end(&mut jwk_bytes)
|
|
||||||
.expect("unable to read from stdin");
|
|
||||||
} else {
|
|
||||||
File::open(key)
|
|
||||||
.expect("unable to open keyfile")
|
|
||||||
.read_to_end(&mut jwk_bytes)
|
|
||||||
.expect("unable to read keyfile");
|
|
||||||
};
|
|
||||||
|
|
||||||
let jwk = Jwk::from_bytes(jwk_bytes).expect("unable to parse jwk");
|
|
||||||
|
|
||||||
// Construct a request JWT
|
|
||||||
let fingerprint = jwk.get_fingerprint().expect("unable to calculate jwt fingerprint");
|
|
||||||
|
|
||||||
// Construct the JWT header
|
|
||||||
let mut header = JwsHeader::new();
|
|
||||||
header.set_token_type("JWT");
|
|
||||||
header.set_algorithm("ES256");
|
|
||||||
header.set_jwk(
|
|
||||||
jwk.to_public_key()
|
|
||||||
.expect("unable to convert jwk to public key"),
|
|
||||||
);
|
|
||||||
header.set_key_id(&fingerprint);
|
|
||||||
|
|
||||||
// Construct the payload
|
|
||||||
let mut payload = JwtPayload::from_map(
|
|
||||||
serde_json::to_value(AspRequestClaims {
|
|
||||||
version: 0,
|
|
||||||
r#type: AspType::Request,
|
|
||||||
action: AspRequestAction::Delete,
|
|
||||||
aspe_uri: Some(format!("aspe:{server}:{fingerprint}")),
|
|
||||||
profile_jws: None,
|
|
||||||
})
|
|
||||||
.unwrap()
|
|
||||||
.as_object()
|
|
||||||
.unwrap()
|
|
||||||
.clone(),
|
|
||||||
)
|
|
||||||
.expect("unable to create payload from map of claims");
|
|
||||||
payload.set_issued_at(&SystemTime::now());
|
|
||||||
|
|
||||||
// Construct the signer
|
|
||||||
let signer = ES256
|
|
||||||
.signer_from_jwk(&jwk)
|
|
||||||
.expect("unable to construct signer from jwk");
|
|
||||||
|
|
||||||
// Construct the actual jwt
|
|
||||||
let jwt = jwt::encode_with_signer(&payload, &header.into(), &signer)
|
|
||||||
.expect("unable to create and sign jwt");
|
|
||||||
dbg!(&jwt);
|
|
||||||
// Send the request
|
|
||||||
let response = http
|
|
||||||
.post(format!("https://{server}/.well-known/aspe/post"))
|
|
||||||
.header(header::CONTENT_TYPE, "application/jose; charset=UTF-8")
|
|
||||||
.body(jwt)
|
|
||||||
.send()
|
|
||||||
.await
|
|
||||||
.expect("unable to send http request to server");
|
|
||||||
|
|
||||||
match response.status() {
|
|
||||||
StatusCode::OK => {
|
|
||||||
dbg!(response.status());
|
|
||||||
dbg!(response.text().await.unwrap());
|
|
||||||
}
|
|
||||||
StatusCode::BAD_REQUEST => {
|
|
||||||
dbg!(response.status());
|
|
||||||
dbg!(response.text().await.unwrap());
|
|
||||||
panic!("Request returned 400, did the request take over 60 seconds to send?")
|
|
||||||
}
|
|
||||||
StatusCode::TOO_MANY_REQUESTS => {
|
|
||||||
dbg!(response.status());
|
|
||||||
dbg!(response.text().await.unwrap());
|
|
||||||
panic!("Ratelimited");
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
dbg!(response.status());
|
|
||||||
dbg!(response.text().await.unwrap());
|
|
||||||
|
|
||||||
panic!("wtf")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -1,68 +0,0 @@
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub enum AspType {
|
|
||||||
Profile,
|
|
||||||
Request,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
|
||||||
pub struct AspClaims {
|
|
||||||
#[serde(rename = "http://ariadne.id/version")]
|
|
||||||
pub version: u8,
|
|
||||||
#[serde(rename = "http://ariadne.id/type")]
|
|
||||||
pub r#type: AspType,
|
|
||||||
#[serde(rename = "http://ariadne.id/name")]
|
|
||||||
pub name: String,
|
|
||||||
#[serde(rename = "http://ariadne.id/claims")]
|
|
||||||
pub claims: Vec<String>,
|
|
||||||
#[serde(
|
|
||||||
rename = "http://ariadne.id/description",
|
|
||||||
skip_serializing_if = "Option::is_none"
|
|
||||||
)]
|
|
||||||
pub description: Option<String>,
|
|
||||||
#[serde(
|
|
||||||
rename = "http://ariadne.id/avatar_url",
|
|
||||||
skip_serializing_if = "Option::is_none"
|
|
||||||
)]
|
|
||||||
pub avatar_url: Option<String>,
|
|
||||||
#[serde(
|
|
||||||
rename = "http://ariadne.id/email",
|
|
||||||
skip_serializing_if = "Option::is_none"
|
|
||||||
)]
|
|
||||||
pub email: Option<String>,
|
|
||||||
#[serde(
|
|
||||||
rename = "http://ariadne.id/color",
|
|
||||||
skip_serializing_if = "Option::is_none"
|
|
||||||
)]
|
|
||||||
pub color: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub enum AspRequestAction {
|
|
||||||
Create,
|
|
||||||
Update,
|
|
||||||
Delete,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
|
||||||
pub struct AspRequestClaims {
|
|
||||||
#[serde(rename = "http://ariadne.id/version")]
|
|
||||||
pub version: u8,
|
|
||||||
#[serde(rename = "http://ariadne.id/type")]
|
|
||||||
pub r#type: AspType,
|
|
||||||
#[serde(rename = "http://ariadne.id/action")]
|
|
||||||
pub action: AspRequestAction,
|
|
||||||
#[serde(
|
|
||||||
rename = "http://ariadne.id/profile_jws",
|
|
||||||
skip_serializing_if = "Option::is_none"
|
|
||||||
)]
|
|
||||||
pub profile_jws: Option<String>,
|
|
||||||
#[serde(
|
|
||||||
rename = "http://ariadne.id/aspe_uri",
|
|
||||||
skip_serializing_if = "Option::is_none"
|
|
||||||
)]
|
|
||||||
pub aspe_uri: Option<String>,
|
|
||||||
}
|
|
Loading…
Reference in a new issue