diff --git a/Cargo.lock b/Cargo.lock index 75072cd..5de5fd0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -301,11 +301,10 @@ dependencies = [ "humantime", "humantime-serde", "itoa", - "jsonwebtoken", "maybe-owned", "rand", "regex", - "rsa", + "rsa 0.9.3", "ryu", "sea-orm", "sea-orm-migration", @@ -333,10 +332,10 @@ dependencies = [ "base64 0.21.2", "chrono", "displaydoc", - "jsonwebtoken", + "jwt-simple", "lazy_static", "regex", - "rsa", + "rsa 0.9.3", "serde", "serde_with", "tracing", @@ -370,7 +369,7 @@ dependencies = [ "hex", "http", "hyper", - "ring 0.16.20", + "ring", "time", "tokio", "tower", @@ -783,6 +782,12 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + [[package]] name = "base64" version = "0.13.1" @@ -822,6 +827,12 @@ dependencies = [ "num-traits", ] +[[package]] +name = "binstring" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e0d60973d9320722cb1206f412740e162a33b8547ea8d6be75d7cff237c7a85" + [[package]] name = "bitflags" version = "1.3.2" @@ -967,11 +978,10 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.83" +version = "1.0.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +checksum = "0f8e7c90afad890484a21653d08b6e209ae34770fb5ee298f9c699fcc1e5c856" dependencies = [ - "jobserver", "libc", ] @@ -1083,6 +1093,18 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b" +[[package]] +name = "coarsetime" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a90d114103adbc625300f346d4d09dfb4ab1c4a8df6868435dd903392ecf4354" +dependencies = [ + "libc", + "once_cell", + "wasi", + "wasm-bindgen", +] + [[package]] name = "codespan-reporting" version = "0.11.1" @@ -1159,9 +1181,9 @@ dependencies = [ [[package]] name = "const-oid" -version = "0.9.5" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28c122c3980598d243d63d9a704629a2d748d101f278052ff068be5a4423ab6f" +checksum = "520fbf3c07483f94e3e3ca9d0cfd913d7718ef2483d2cfd91c0d9e91474ab913" [[package]] name = "const_format" @@ -1255,6 +1277,18 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crypto-bigint" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf4c2f4e1afd912bc40bfd6fed5d9dc1f288e0ba01bfcc835cc5bc3eb13efe15" +dependencies = [ + "generic-array", + "rand_core", + "subtle", + "zeroize", +] + [[package]] name = "crypto-common" version = "0.1.6" @@ -1352,12 +1386,23 @@ dependencies = [ [[package]] name = "der" -version = "0.7.8" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fffa369a668c8af7dbf8b5e56c9f744fbd399949ed171606040001947de40b1c" +checksum = "f1a467a65c5e759bce6e65eaf91cc29f466cdc57cb65777bd646872a8a1fd4de" dependencies = [ "const-oid", - "pem-rfc7468", + "pem-rfc7468 0.6.0", + "zeroize", +] + +[[package]] +name = "der" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56acb310e15652100da43d130af8d97b509e95af61aab1c5a7939ef24337ee17" +dependencies = [ + "const-oid", + "pem-rfc7468 0.7.0", "zeroize", ] @@ -1433,6 +1478,20 @@ version = "0.15.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" +[[package]] +name = "ecdsa" +version = "0.16.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0997c976637b606099b9985693efa3581e84e41f5c11ba5255f88711058ad428" +dependencies = [ + "der 0.7.6", + "digest", + "elliptic-curve", + "rfc6979", + "signature 2.1.0", + "spki 0.7.2", +] + [[package]] name = "ed25519-compact" version = "2.0.4" @@ -1449,6 +1508,27 @@ version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" +[[package]] +name = "elliptic-curve" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "968405c8fdc9b3bf4df0a6638858cc0b52462836ab6b1c87377785dd09cf1c0b" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest", + "ff", + "generic-array", + "group", + "hkdf", + "pem-rfc7468 0.7.0", + "pkcs8 0.10.2", + "rand_core", + "sec1", + "subtle", + "zeroize", +] + [[package]] name = "encode_unicode" version = "0.3.6" @@ -1518,6 +1598,16 @@ dependencies = [ "instant", ] +[[package]] +name = "ff" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" +dependencies = [ + "rand_core", + "subtle", +] + [[package]] name = "filetime" version = "0.2.21" @@ -1681,19 +1771,31 @@ checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", + "zeroize", ] [[package]] name = "getrandom" -version = "0.2.10" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" +checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f" dependencies = [ "cfg-if", "libc", "wasi", ] +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core", + "subtle", +] + [[package]] name = "h2" version = "0.3.19" @@ -1810,6 +1912,30 @@ dependencies = [ "digest", ] +[[package]] +name = "hmac-sha1-compact" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05e2440a0078e20c3b68ca01234cea4219f23e64b0c0bdb1200c5550d54239bb" + +[[package]] +name = "hmac-sha256" +version = "1.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc736091aacb31ddaa4cd5f6988b3c21e99913ac846b41f32538c5fae5d71bfe" +dependencies = [ + "digest", +] + +[[package]] +name = "hmac-sha512" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520c9c3f6040661669bc5c91e551b605a520c8e0a63a766a91a65adef734d151" +dependencies = [ + "digest", +] + [[package]] name = "home" version = "0.5.5" @@ -2074,15 +2200,6 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440" -[[package]] -name = "jobserver" -version = "0.1.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "936cfd212a0155903bcbc060e316fb6cc7cbf2e1907329391ebadc1fe0ce77c2" -dependencies = [ - "libc", -] - [[package]] name = "js-sys" version = "0.3.63" @@ -2093,17 +2210,43 @@ dependencies = [ ] [[package]] -name = "jsonwebtoken" -version = "9.1.0" +name = "jwt-simple" +version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "155c4d7e39ad04c172c5e3a99c434ea3b4a7ba7960b38ecd562b270b097cce09" +checksum = "0537086995d782ba2fb6c120a88f0d66c5ee5f1208a3559826d4cf2264b170da" dependencies = [ - "base64 0.21.2", - "pem", - "ring 0.17.5", + "anyhow", + "binstring", + "coarsetime", + "ct-codecs", + "ed25519-compact", + "hmac-sha1-compact", + "hmac-sha256", + "hmac-sha512", + "k256", + "p256", + "p384", + "rand", + "rsa 0.7.2", "serde", "serde_json", - "simple_asn1", + "spki 0.6.0", + "thiserror", + "zeroize", +] + +[[package]] +name = "k256" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cadb76004ed8e97623117f3df85b17aaa6626ab0b0831e6573f104df16cd1bcc" +dependencies = [ + "cfg-if", + "ecdsa", + "elliptic-curve", + "once_cell", + "sha2", + "signature 2.1.0", ] [[package]] @@ -2143,9 +2286,9 @@ checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" [[package]] name = "libm" -version = "0.2.8" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" +checksum = "f7012b1bbb0719e1097c47611d3898568c546d597c2e74d66f6087edd5233ff4" [[package]] name = "libsqlite3-sys" @@ -2323,9 +2466,9 @@ dependencies = [ [[package]] name = "num-bigint-dig" -version = "0.8.4" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151" +checksum = "2399c9463abc5f909349d8aa9ba080e0b88b3ce2885389b60b993f39b1a56905" dependencies = [ "byteorder", "lazy_static", @@ -2438,6 +2581,30 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" +[[package]] +name = "p256" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + +[[package]] +name = "p384" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70786f51bcc69f6a4c0360e063a4cac5419ef7c5cd5b3c99ad70f3be5ba79209" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + [[package]] name = "parking_lot" version = "0.11.2" @@ -2470,13 +2637,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f746c4065a8fa3fe23974dd82f15431cc8d40779821001404d10d2e79ca7d79" [[package]] -name = "pem" -version = "3.0.2" +name = "pem-rfc7468" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3163d2912b7c3b52d651a055f2c7eec9ba5cd22d26ef75b8dd3a59980b185923" +checksum = "24d159833a9105500e0398934e205e0773f0b27529557134ecfc51c27646adac" dependencies = [ - "base64 0.21.2", - "serde", + "base64ct", ] [[package]] @@ -2526,15 +2692,37 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pkcs1" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eff33bdbdfc54cc98a2eca766ebdec3e1b8fb7387523d5c9c9a2891da856f719" +dependencies = [ + "der 0.6.1", + "pkcs8 0.9.0", + "spki 0.6.0", + "zeroize", +] + [[package]] name = "pkcs1" version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" dependencies = [ - "der", - "pkcs8", - "spki", + "der 0.7.6", + "pkcs8 0.10.2", + "spki 0.7.2", +] + +[[package]] +name = "pkcs8" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9eca2c590a5f85da82668fa685c09ce2888b9430e83299debf1f34b65fd4a4ba" +dependencies = [ + "der 0.6.1", + "spki 0.6.0", ] [[package]] @@ -2543,8 +2731,8 @@ version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" dependencies = [ - "der", - "spki", + "der 0.7.6", + "spki 0.7.2", ] [[package]] @@ -2574,6 +2762,15 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +[[package]] +name = "primeorder" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf8d3875361e28f7753baefef104386e7aa47642c93023356d97fdef4003bfb5" +dependencies = [ + "elliptic-curve", +] + [[package]] name = "proc-macro-crate" version = "0.1.5" @@ -2825,6 +3022,16 @@ dependencies = [ "winreg", ] +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + [[package]] name = "ring" version = "0.16.20" @@ -2835,25 +3042,11 @@ dependencies = [ "libc", "once_cell", "spin 0.5.2", - "untrusted 0.7.1", + "untrusted", "web-sys", "winapi", ] -[[package]] -name = "ring" -version = "0.17.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb0205304757e5d899b9c2e448b867ffd03ae7f988002e47cd24954391394d0b" -dependencies = [ - "cc", - "getrandom", - "libc", - "spin 0.9.8", - "untrusted 0.9.0", - "windows-sys 0.48.0", -] - [[package]] name = "rkyv" version = "0.7.42" @@ -2882,6 +3075,27 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "rsa" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "094052d5470cbcef561cb848a7209968c9f12dfa6d668f4bca048ac5de51099c" +dependencies = [ + "byteorder", + "digest", + "num-bigint-dig", + "num-integer", + "num-iter", + "num-traits", + "pkcs1 0.4.1", + "pkcs8 0.9.0", + "rand_core", + "signature 1.6.4", + "smallvec", + "subtle", + "zeroize", +] + [[package]] name = "rsa" version = "0.9.3" @@ -2893,11 +3107,11 @@ dependencies = [ "num-bigint-dig", "num-integer", "num-traits", - "pkcs1", - "pkcs8", + "pkcs1 0.7.5", + "pkcs8 0.10.2", "rand_core", - "signature", - "spki", + "signature 2.1.0", + "spki 0.7.2", "subtle", "zeroize", ] @@ -2950,7 +3164,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fff78fc74d175294f4e83b28343315ffcfb114b156f0185e9741cb5570f50e2f" dependencies = [ "log", - "ring 0.16.20", + "ring", "sct", "webpki", ] @@ -2962,7 +3176,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c911ba11bc8433e811ce56fde130ccf32f5127cab0e0194e9c68c5a5b671791e" dependencies = [ "log", - "ring 0.16.20", + "ring", "rustls-webpki", "sct", ] @@ -2994,8 +3208,8 @@ version = "0.100.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6207cd5ed3d8dca7816f8f3725513a34609c0c765bf652b8c3cb4cfd87db46b" dependencies = [ - "ring 0.16.20", - "untrusted 0.7.1", + "ring", + "untrusted", ] [[package]] @@ -3046,8 +3260,8 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" dependencies = [ - "ring 0.16.20", - "untrusted 0.7.1", + "ring", + "untrusted", ] [[package]] @@ -3219,6 +3433,20 @@ version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" +[[package]] +name = "sec1" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0aec48e813d6b90b15f0b8948af3c63483992dee44c03e9930b3eebdabe046e" +dependencies = [ + "base16ct", + "der 0.7.6", + "generic-array", + "pkcs8 0.10.2", + "subtle", + "zeroize", +] + [[package]] name = "security-framework" version = "2.9.1" @@ -3406,6 +3634,16 @@ dependencies = [ "libc", ] +[[package]] +name = "signature" +version = "1.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" +dependencies = [ + "digest", + "rand_core", +] + [[package]] name = "signature" version = "2.1.0" @@ -3422,18 +3660,6 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f27f6278552951f1f2b8cf9da965d10969b2efdea95a6ec47987ab46edfe263a" -[[package]] -name = "simple_asn1" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adc4e5204eb1910f40f9cfa375f6f05b68c3abac4b6fd879c8ff5e7ae8a0a085" -dependencies = [ - "num-bigint", - "num-traits", - "thiserror", - "time", -] - [[package]] name = "slab" version = "0.4.8" @@ -3474,6 +3700,16 @@ dependencies = [ "lock_api", ] +[[package]] +name = "spki" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67cf02bbac7a337dc36e4f5a693db6c21e7863f45070f7064577eb4367a3212b" +dependencies = [ + "base64ct", + "der 0.6.1", +] + [[package]] name = "spki" version = "0.7.2" @@ -3481,7 +3717,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d1e996ef02c474957d681f1b05213dfb0abab947b446a62d37770b23500184a" dependencies = [ "base64ct", - "der", + "der 0.7.6", ] [[package]] @@ -4128,12 +4364,6 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" -[[package]] -name = "untrusted" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" - [[package]] name = "url" version = "2.3.1" @@ -4312,8 +4542,8 @@ version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd" dependencies = [ - "ring 0.16.20", - "untrusted 0.7.1", + "ring", + "untrusted", ] [[package]] diff --git a/server/Cargo.toml b/server/Cargo.toml index bd4d5d2..50f005e 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -60,7 +60,6 @@ tracing-subscriber = { version = "0.3.17", features = [ "json" ] } uuid = { version = "1.3.3", features = ["v4"] } console-subscriber = "0.1.9" xdg = "2.5.0" -jsonwebtoken = "9.1.0" rsa = "0.9.3" [dependencies.async-compression] diff --git a/server/src/access/http.rs b/server/src/access/http.rs index 1b02d95..3c73c39 100644 --- a/server/src/access/http.rs +++ b/server/src/access/http.rs @@ -1,17 +1,15 @@ //! HTTP middlewares for access control. +use attic::cache::CacheName; +use attic_token::util::parse_authorization_header; use axum::{http::Request, middleware::Next, response::Response}; -use jsonwebtoken::Algorithm; use sea_orm::DatabaseConnection; use tokio::sync::OnceCell; use crate::access::{CachePermission, Token}; -use crate::config::JWTSigningConfig; use crate::database::{entity::cache::CacheModel, AtticDatabase}; use crate::error::ServerResult; use crate::{RequestState, State}; -use attic::cache::CacheName; -use attic_token::util::parse_authorization_header; /// Auth state. #[derive(Debug)] @@ -103,25 +101,19 @@ pub async fn apply_auth(req: Request, next: Next) -> Response { .and_then(parse_authorization_header) .and_then(|jwt| { let state = req.extensions().get::().unwrap(); - let (algorithm, decoding_key) = match &state.config.jwt.signing_config { - JWTSigningConfig::HS256SignAndVerify { decoding_key, .. } => { - (Algorithm::HS256, decoding_key) - } - JWTSigningConfig::RS256SignAndVerify { decoding_key, .. } => { - (Algorithm::RS256, decoding_key) - } - }; + let signature_type = state.config.jwt.signing_config.clone().into(); let res_token = Token::from_jwt( &jwt, - algorithm, - decoding_key, + &signature_type, &state.config.jwt.token_bound_issuer, &state.config.jwt.token_bound_audiences, ); + if let Err(e) = &res_token { tracing::debug!("Ignoring bad JWT token: {}", e); } + res_token.ok() }); diff --git a/server/src/adm/command/make_token.rs b/server/src/adm/command/make_token.rs index 24a2330..c027d41 100644 --- a/server/src/adm/command/make_token.rs +++ b/server/src/adm/command/make_token.rs @@ -2,12 +2,11 @@ use anyhow::{anyhow, Result}; use chrono::{Duration as ChronoDuration, Utc}; use clap::Parser; use humantime::Duration; -use jsonwebtoken::Algorithm; use crate::Opts; use attic::cache::CacheNamePattern; use attic_server::access::Token; -use attic_server::config::{Config, JWTSigningConfig}; +use attic_server::config::Config; /// Generate a new token. /// @@ -116,18 +115,10 @@ pub async fn run(config: Config, opts: Opts) -> Result<()> { if sub.dump_claims { println!("{}", serde_json::to_string(token.opaque_claims())?); } else { - let (algorithm, encoding_key) = match &config.jwt.signing_config { - JWTSigningConfig::HS256SignAndVerify { encoding_key, .. } => { - (Algorithm::HS256, encoding_key) - } - JWTSigningConfig::RS256SignAndVerify { encoding_key, .. } => { - (Algorithm::RS256, encoding_key) - } - }; + let signature_type = config.jwt.signing_config.into(); let encoded_token = token.encode( - algorithm, - &encoding_key, + &signature_type, &config.jwt.token_bound_issuer, &config.jwt.token_bound_audiences, )?; diff --git a/server/src/config.rs b/server/src/config.rs index 9792911..e7d3898 100644 --- a/server/src/config.rs +++ b/server/src/config.rs @@ -1,5 +1,6 @@ //! Server configuration. +use std::collections::HashSet; use std::env; use std::net::SocketAddr; use std::path::{Path, PathBuf}; @@ -7,13 +8,14 @@ use std::time::Duration; use anyhow::Result; use async_compression::Level as CompressionLevel; +use attic_token::SignatureType; use base64::{engine::general_purpose::STANDARD as BASE64_STANDARD, Engine}; use derivative::Derivative; use serde::{de, Deserialize}; use xdg::BaseDirectories; use crate::access::{ - decode_token_hs256_secret, decode_token_rs256_secret, DecodingKey, EncodingKey, + decode_token_hs256_secret_base64, decode_token_rs256_secret_base64, HS256Key, RS256KeyPair, }; use crate::narinfo::Compression as NixCompression; use crate::storage::{LocalStorageConfig, S3StorageConfig}; @@ -137,7 +139,7 @@ pub struct JWTConfig { /// values. #[serde(rename = "token-bound-audiences")] #[serde(default = "Default::default")] - pub token_bound_audiences: Option>, + pub token_bound_audiences: Option>, #[serde(rename = "signing")] #[serde(default = "load_jwt_signing_config_from_env")] @@ -152,10 +154,7 @@ pub enum JWTSigningConfig { /// Set this to the base64-encoded HMAC secret to use for signing and verifying JWTs. #[serde(rename = "token-hs256-secret-base64")] #[serde(deserialize_with = "deserialize_token_hs256_secret_base64")] - HS256SignAndVerify { - encoding_key: EncodingKey, - decoding_key: DecodingKey, - }, + HS256SignAndVerify(HS256Key), /// JSON Web Token RSA secret. /// @@ -163,10 +162,16 @@ pub enum JWTSigningConfig { /// JWTs. #[serde(rename = "token-rs256-secret-base64")] #[serde(deserialize_with = "deserialize_token_rs256_secret_base64")] - RS256SignAndVerify { - encoding_key: EncodingKey, - decoding_key: DecodingKey, - }, + RS256SignAndVerify(RS256KeyPair), +} + +impl From for SignatureType { + fn from(value: JWTSigningConfig) -> Self { + match value { + JWTSigningConfig::HS256SignAndVerify(key) => Self::HS256(key), + JWTSigningConfig::RS256SignAndVerify(key) => Self::RS256(key), + } + } } /// Database connection configuration. @@ -324,27 +329,17 @@ fn load_jwt_signing_config_from_env() -> JWTSigningConfig { fn load_token_hs256_secret_from_env() -> Option { let s = env::var(ENV_TOKEN_HS256_SECRET_BASE64).ok()?; - decode_token_hs256_secret(&s) + decode_token_hs256_secret_base64(&s) .ok() - .map( - |(encoding_key, decoding_key)| JWTSigningConfig::HS256SignAndVerify { - encoding_key, - decoding_key, - }, - ) + .map(JWTSigningConfig::HS256SignAndVerify) } fn load_token_rs256_secret_from_env() -> Option { let s = env::var(ENV_TOKEN_RS256_SECRET_BASE64).ok()?; - decode_token_rs256_secret(&s) + decode_token_rs256_secret_base64(&s) .ok() - .map( - |(encoding_key, decoding_key)| JWTSigningConfig::RS256SignAndVerify { - encoding_key, - decoding_key, - }, - ) + .map(JWTSigningConfig::RS256SignAndVerify) } fn load_database_url_from_env() -> String { @@ -398,30 +393,26 @@ impl Default for GarbageCollectionConfig { } } -fn deserialize_token_hs256_secret_base64<'de, D>( - deserializer: D, -) -> Result<(EncodingKey, DecodingKey), D::Error> +fn deserialize_token_hs256_secret_base64<'de, D>(deserializer: D) -> Result where D: de::Deserializer<'de>, { use de::Error; let s = String::deserialize(deserializer)?; - let key = decode_token_hs256_secret(&s).map_err(Error::custom)?; + let key = decode_token_hs256_secret_base64(&s).map_err(Error::custom)?; Ok(key) } -fn deserialize_token_rs256_secret_base64<'de, D>( - deserializer: D, -) -> Result<(EncodingKey, DecodingKey), D::Error> +fn deserialize_token_rs256_secret_base64<'de, D>(deserializer: D) -> Result where D: de::Deserializer<'de>, { use de::Error; let s = String::deserialize(deserializer)?; - let key = decode_token_rs256_secret(&s).map_err(Error::custom)?; + let key = decode_token_rs256_secret_base64(&s).map_err(Error::custom)?; Ok(key) } diff --git a/server/src/oobe.rs b/server/src/oobe.rs index add8cf9..d3d912d 100644 --- a/server/src/oobe.rs +++ b/server/src/oobe.rs @@ -17,7 +17,7 @@ use chrono::{Months, Utc}; use rsa::pkcs1::EncodeRsaPrivateKey; use tokio::fs::{self, OpenOptions}; -use crate::access::{decode_token_rs256_secret, Token}; +use crate::access::{decode_token_rs256_secret_base64, SignatureType, Token}; use crate::config; use attic::cache::CacheNamePattern; @@ -73,8 +73,8 @@ pub async fn run_oobe() -> Result<()> { perm.configure_cache_retention = true; perm.destroy_cache = true; - let key = decode_token_rs256_secret(&rs256_secret_base64).unwrap(); - token.encode(jsonwebtoken::Algorithm::RS256, &key.0, &None, &None)? + let key = decode_token_rs256_secret_base64(&rs256_secret_base64).unwrap(); + token.encode(&SignatureType::RS256(key), &None, &None)? }; eprintln!(); diff --git a/token/Cargo.toml b/token/Cargo.toml index 38fb5e1..1e13309 100644 --- a/token/Cargo.toml +++ b/token/Cargo.toml @@ -11,7 +11,7 @@ attic = { path = "../attic", default-features = false } base64 = "0.21.2" chrono = "0.4.31" displaydoc = "0.2.4" -jsonwebtoken = { version = "9.1.0", features = ["use_pem"] } +jwt-simple = "0.11.5" lazy_static = "1.4.0" regex = "1.8.3" serde = "1.0.163" diff --git a/token/src/lib.rs b/token/src/lib.rs index 7040aea..6424d6e 100644 --- a/token/src/lib.rs +++ b/token/src/lib.rs @@ -83,15 +83,18 @@ pub mod util; #[cfg(test)] mod tests; -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; use std::error::Error as StdError; use base64::{engine::general_purpose::STANDARD as BASE64_STANDARD, Engine}; use chrono::{DateTime, Utc}; use displaydoc::Display; -use jsonwebtoken::{Algorithm, Validation}; -pub use jsonwebtoken::{DecodingKey, EncodingKey}; -use rsa::pkcs1::{DecodeRsaPrivateKey, EncodeRsaPublicKey}; +use jwt_simple::prelude::{Duration, RSAKeyPairLike, RSAPublicKeyLike, VerificationOptions}; +pub use jwt_simple::{ + algorithms::{HS256Key, MACLike, RS256KeyPair}, + claims::{Claims, JWTClaims}, + prelude::UnixTimeStamp, +}; use serde::{Deserialize, Serialize}; use serde_with::{serde_as, BoolFromInt}; @@ -124,60 +127,6 @@ macro_rules! require_permission_function { }; } -/// A set of JWT claims. -/// -/// The `CustomClaims` parameter can be set to `NoCustomClaims` if only standard -/// claims are used, or to a user-defined type that must be `serde`-serializable -/// if custom claims are required. -/// -/// NOTE: This has been lifted from jwt_simple, but UnixTimeStamp has been -/// changed to i64, and Audiences is now a string. -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct JWTClaims { - /// Time the claims were created at - #[serde(rename = "iat", default, skip_serializing_if = "Option::is_none")] - pub issued_at: Option, - - /// Time the claims expire at - #[serde(rename = "exp", default, skip_serializing_if = "Option::is_none")] - pub expires_at: Option, - - /// Time the claims will be invalid until - #[serde(rename = "nbf", default, skip_serializing_if = "Option::is_none")] - pub invalid_before: Option, - - /// Issuer - This can be set to anything application-specific - #[serde(rename = "iss", default, skip_serializing_if = "Option::is_none")] - pub issuer: Option, - - /// Subject - This can be set to anything application-specific - #[serde(rename = "sub", default, skip_serializing_if = "Option::is_none")] - pub subject: Option, - - /// Audiences - #[serde(rename = "aud", default, skip_serializing_if = "Option::is_none")] - pub audiences: Option>, - - /// JWT identifier - /// - /// That property was originally designed to avoid replay attacks, but - /// keeping all previously sent JWT token IDs is unrealistic. - /// - /// Replay attacks are better addressed by keeping only the timestamp of the - /// last valid token for a user, and rejecting anything older in future - /// tokens. - #[serde(rename = "jti", default, skip_serializing_if = "Option::is_none")] - pub jwt_id: Option, - - /// Nonce - #[serde(rename = "nonce", default, skip_serializing_if = "Option::is_none")] - pub nonce: Option, - - /// Custom (application-defined) claims - #[serde(flatten)] - pub custom: CustomClaims, -} - /// A validated JSON Web Token. #[derive(Debug)] pub struct Token(JWTClaims); @@ -271,7 +220,7 @@ pub enum Error { PermissionDenied, /// JWT error: {0} - TokenError(jsonwebtoken::errors::Error), + TokenError(jwt_simple::Error), /// Base64 decode error: {0} Base64Error(base64::DecodeError), @@ -283,35 +232,51 @@ pub enum Error { Utf8Error(std::str::Utf8Error), } +/// The supported JWT signature types. +pub enum SignatureType { + HS256(HS256Key), + RS256(RS256KeyPair), +} + impl Token { /// Verifies and decodes a token. pub fn from_jwt( token: &str, - key_algorithm: Algorithm, - key: &jsonwebtoken::DecodingKey, + signature_type: &SignatureType, maybe_bound_issuer: &Option, - maybe_bound_audiences: &Option>, + maybe_bound_audiences: &Option>, ) -> Result { - let mut required_spec_claims = vec!["exp", "nbf", "sub"]; + let opts = VerificationOptions { + reject_before: None, + accept_future: false, + required_subject: None, + required_key_id: None, + required_public_key: None, + required_nonce: None, + allowed_issuers: maybe_bound_issuer + .as_ref() + .map(|s| [s.to_owned()].into()) + .to_owned(), + allowed_audiences: maybe_bound_audiences.to_owned(), + time_tolerance: None, + max_validity: None, + max_token_length: None, + max_header_length: None, + }; - let mut validation = Validation::new(key_algorithm); - validation.validate_nbf = true; - - if let Some(bound_issuer) = maybe_bound_issuer { - validation.set_issuer(&[bound_issuer]); - required_spec_claims.push("iss"); + match signature_type { + SignatureType::HS256(key) => key + .verify_token(token, Some(opts)) + .map_err(Error::TokenError) + .map(Token), + SignatureType::RS256(key) => { + let public_key = key.public_key(); + public_key + .verify_token(token, Some(opts)) + .map_err(Error::TokenError) + .map(Token) + } } - if let Some(bound_audiences) = maybe_bound_audiences { - validation.set_audience(&bound_audiences); - required_spec_claims.push("aud"); - } - - validation.set_required_spec_claims(&required_spec_claims); - - jsonwebtoken::decode::>(token, key, &validation) - .map_err(Error::TokenError) - .map(|tokendata| tokendata.claims) - .map(Token) } /// Creates a new token with an expiration timestamp. @@ -324,8 +289,13 @@ impl Token { Self(JWTClaims { issued_at: None, - expires_at: Some(exp.timestamp()), - invalid_before: Some(now_epoch.num_seconds()), + expires_at: Some(UnixTimeStamp::from_secs( + exp.timestamp().try_into().unwrap(), + )), + invalid_before: Some(Duration::new( + now_epoch.num_seconds().try_into().unwrap(), + 0, + )), issuer: None, subject: Some(sub), audiences: None, @@ -338,18 +308,23 @@ impl Token { /// Encodes the token. pub fn encode( &self, - key_algorithm: Algorithm, - key: &jsonwebtoken::EncodingKey, + signature_type: &SignatureType, maybe_bound_issuer: &Option, - maybe_bound_audiences: &Option>, + maybe_bound_audiences: &Option>, ) -> Result { - let header = jsonwebtoken::Header::new(key_algorithm); + let mut token = self.0.clone(); - let mut claims = self.0.clone(); - claims.issuer = maybe_bound_issuer.to_owned(); - claims.audiences = maybe_bound_audiences.to_owned(); + if let Some(issuer) = maybe_bound_issuer { + token = token.with_issuer(issuer); + } + if let Some(audiences) = maybe_bound_audiences { + token = token.with_audiences(audiences.to_owned()); + } - jsonwebtoken::encode(&header, &claims, key).map_err(Error::TokenError) + match signature_type { + SignatureType::HS256(key) => key.authenticate(token).map_err(Error::TokenError), + SignatureType::RS256(key) => key.sign(token).map_err(Error::TokenError), + } } /// Returns the subject of the token. @@ -454,28 +429,16 @@ impl CachePermission { impl StdError for Error {} -pub fn decode_token_hs256_secret(s: &str) -> Result<(EncodingKey, DecodingKey)> { - let secret = BASE64_STANDARD.decode(s).map_err(Error::Base64Error)?; - - let encoding_key = EncodingKey::from_secret(&secret); - let decoding_key = DecodingKey::from_secret(&secret); - - Ok((encoding_key, decoding_key)) -} - -pub fn decode_token_rs256_secret(s: &str) -> Result<(EncodingKey, DecodingKey)> { +pub fn decode_token_hs256_secret_base64(s: &str) -> Result { let decoded = BASE64_STANDARD.decode(s).map_err(Error::Base64Error)?; let secret = std::str::from_utf8(&decoded).map_err(Error::Utf8Error)?; - - let private_key = rsa::RsaPrivateKey::from_pkcs1_pem(secret).map_err(Error::RsaKeyError)?; - let public_key = private_key.to_public_key(); - let public_pkcs1_pem = public_key - .to_pkcs1_pem(rsa::pkcs1::LineEnding::LF) - .map_err(Error::RsaKeyError)?; - - let encoding_key = EncodingKey::from_rsa_pem(&secret.as_bytes()).map_err(Error::TokenError)?; - let decoding_key = - DecodingKey::from_rsa_pem(public_pkcs1_pem.as_bytes()).map_err(Error::TokenError)?; - - Ok((encoding_key, decoding_key)) + Ok(HS256Key::from_bytes(&secret.as_bytes())) +} + +pub fn decode_token_rs256_secret_base64(s: &str) -> Result { + let decoded = BASE64_STANDARD.decode(s).map_err(Error::Base64Error)?; + let secret = std::str::from_utf8(&decoded).map_err(Error::Utf8Error)?; + let keypair = RS256KeyPair::from_pem(secret).map_err(Error::TokenError)?; + + Ok(keypair) } diff --git a/token/src/tests.rs b/token/src/tests.rs index 41b219e..df90037 100644 --- a/token/src/tests.rs +++ b/token/src/tests.rs @@ -30,19 +30,12 @@ fn test_basic() { // BASE64_SECRET=$(openssl rsa -in rs256 -outform PEM -traditional | base64 -w0) let base64_secret = "LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcEFJQkFBS0NBUUVBNUZranRMRzV5eS9pMFlnYkQxeUJBK21GckNmLzZiQ2F0TDFFQ3ppNG1tZWhSZTcwCkFEL0dSSHhTVUErc0pZeCtZNjlyL0RqQWs2OFJlQ1c4b2FQWXhtc21RNG5VM2ZwZ2E3WWFqZ3ZoWmVsa3JtaC8KZ1ZURWtFTG1IZlJtQkwvOWlsT20yRHNtYTVhUFo0SFl6ellpdjJvcFF5UGRndXcyWXFtbzE3Nk5MdllCMmpJTwovR3FkdE55K3NPV296NktVSVlJa0hWWU5HMENVcFNzdXBqUTJ6VTVZMFc2UXlNQWFWd1BONElJT3lXWUNwZXRECjFJbWxYekhROXM4NXFSWnlLa21iZFhtTVBVWmUvekRxc2FFd3lscFlpT0RjbDdRYU5QTzEzZnk3UGtQMmVwdUkKTk5tZ1E0WEF0MkF4ZXNKck5ibUs4aG1iM3doRXZkNjRFMGdEV1FJREFRQUJBb0lCQUJEemNRd2IyVi8wK1JCMgoyeE5qMll2eHpPTi93S2FYWHBTbUxDUHRIUDhSVEU2RnM0VkZOckdrelBOMmhsL3ZNdjZ4YWdHNk1NbUZ5SFV6CnovSHIyTTY1NjRnOTloaFlXc29FSmFwL3hVYXNjYlhrdWZwZTBZeW4rcThra21JdDRtTmZYRlpXNWI0ODJmNWsKRERVdG5weTVBOEVoSzNOcGw0dnhia0E5dS90TlVlT1NHTkhPYVZjcHdERVhDNXJ4bmFxTm5wMkMwa1A4ODRINgpSb2lZVkF4bytHaVpNVzhIOFRmSXVsenh3c04yQnVNcUNmOGVhNG1EM0pRVHZ2REhhUHM4eVJTUlB3UmlHYUkzCnVybFRmdjg4U20va09oL0N2SkpoRnhCVkVNVjIydWRNUmU3L3NpTWtlbVlvUnhaTWJjRGVQK2h1RktJWTRSMEoKNnRJUHQ3VUNnWUVBOTlhL2IzeFBsQWh0ck02dUlUUXNQd0FYQUg3Q1NXL1FSdVJUTWVhYXVIMk9sRitjZmpMNApJS1Nsdy9QaUtaUEk1TFRWM2ZVZk5WNTVsOFZHTytsT2ViTFhnaXBYM3BqSDBma3AyY3Q2Smk3aGw0aUlXK0h0ClpJNE9KYkYwTTBETHdySkd3T25QL2trRHNxSW9IbC9MdTBRM2FxSm1RVCsvcG54R083R21kbDhDZ1lFQTY5NFcKZHF2NnF4VjF5V0Z4QWZOOE1hZStpTC9xY1VhTm85ZzMva2YvOXZ3VXdtcERvR0xnaVVLMWZKb3BUYlBjcWgwRwptbUZEQ3V2M1Q0OS9yU2k5dU4zYm82cmlXRUl4VFg1YUtFSjlpSEFMWDJGWDdGSDJRdUZGWEwzQ2c0ckdvL1pDCmdjUkxuS3dma3JUVnRxeEdaNjN4YmsvcFpHWjZtTW01VkNDck1VY0NnWUVBc3JUT1pQMG1CSC92VldQU2UyNjcKV05JZncrT2pCSUR6bGFxZHNxV3Rlc3BPUFA2VVFRdFBqM29wYlJvMlFmU21Md09XRXUzbEN2Nk1mcnRvNFZwaAprNjg1WmtwU0FkZjRmWmRFYmg4aWZOWGhKUHIyR0FyWXVtRVVJbW5LZUFxSTRtTGFVZEJHZ2Z6MEJhS1hldzlvClFDZjRMWlBjVjhBMzJUeFRDRWdZMTlFQ2dZQU04U2F5WkVWZzFkQ2N1Q2dIUDJEMUtJc2YzY2Z6WnplbVlkclEKclFxeWRxcDg4Rys5Z1M5bzJLdzBwaERXSHFSaEFTNjNrZGFuNXNLdkx1U0dqOUc1THhNNks4bzNwWW9uQW1QWQpDYTN4cXBRMUs1WXpkVnZaMTVxQ3VEYlFHUEZGVmVIWVZQa0JJOENud0J4cDVaSUhabGYxQVpXQTJNNnBTNGhMCndXOGpTUUtCZ1FDQmNJbjU4Y0lmZkhmMjM4SUJvZnR1UVVzREZGcnkzaUVpaWpTYmJ1WnB1Vm8zL2pWbUsyaEYKS2xUL2xoRDdWdGJ1V3phMG9WQmZDaWZqMnZ2S2pmZ0l6NnF3Um1UbC9DSjlWdUNHTUI1VG55cGl3OEtodXorSAo0L2twdDdNcW9WQ0dRSjd1WVQyQzY1K0JqNklnUnBQT09za3VKNW1RZ0FlbTQ3eDBrVnRSemc9PQotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo="; - let dec_key = decode_token_rs256_secret(base64_secret).unwrap().1; + let dec_key = decode_token_rs256_secret_base64(base64_secret).unwrap(); // TOKEN=$(jq -c < json | jwt encode --alg RS256 --secret @./rs256 -) let token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJleHAiOjQxMDIzMjQ5ODYsImh0dHBzOi8vand0LmF0dGljLnJzL3YxIjp7ImNhY2hlcyI6eyJjYWNoZS1ybyI6eyJyIjoxfSwiY2FjaGUtcnciOnsiciI6MSwidyI6MX0sInRlYW0tKiI6eyJjYyI6MSwiciI6MSwidyI6MX19fSwiaWF0IjoxNjk5NzM0NTU3LCJuYmYiOjAsInN1YiI6Im1lb3cifQ.k1TCqAg5_yaBQByKnYn5zSvMsYi8XrHe1h8T2hijZiP1SsYYnKphKKm0e61lmr3tSM-3dtRRCNGB7elhetpuz2jz8fWyBmpjO-yIX2uB787iRKVjaVCEKSPjcKO9lGp9LlxKdNH0SLRmdwkJGQUHbzN6QurfiV4C54cPxC_43EamkOqFUFmmwohi_r76RZtMb8uyt-9t7Canpm7GfJg4uVg3MLgbvCKxJ4BSu4UgXPz-MYupHS_pIEtlCY8FjlVrXlBLAleUvcBPY2qML9gxpqBrh9s1qfLpCeTZkG-vDjb_Y8X0gXa0OshFrvnoIyHwDc9jmj1X35T0YslyjbQXWQ"; - let decoded = Token::from_jwt( - token, - jsonwebtoken::Algorithm::RS256, - &dec_key, - &None, - &None, - ) - .unwrap(); + let decoded = Token::from_jwt(token, &SignatureType::RS256(dec_key), &None, &None).unwrap(); let perm_rw = decoded.get_permission_for_cache(&cache! { "cache-rw" });