Compare commits

..

2 commits

Author SHA1 Message Date
c06afde7cb merge
All checks were successful
Build and Test / account (push) Successful in 6m46s
2026-04-27 14:29:13 +02:00
4864fb83a8 add certificate verification 2026-04-27 14:16:13 +02:00
4 changed files with 387 additions and 67 deletions

213
Cargo.lock generated
View file

@ -10,21 +10,27 @@ dependencies = [
"argon2", "argon2",
"base64", "base64",
"bcrypt", "bcrypt",
"binrw",
"bytemuck", "bytemuck",
"cbc", "cbc",
"chrono", "chrono",
"crc32fast", "crc32fast",
"dotenvy", "dotenvy",
"dsa",
"ecdsa",
"gxhash", "gxhash",
"hex", "hex",
"hmac", "hmac",
"juniper", "juniper",
"juniper_rocket", "juniper_rocket",
"k256",
"lettre", "lettre",
"log", "log",
"md-5", "md-5",
"mii", "mii",
"once_cell", "once_cell",
"openssl",
"p256",
"prost", "prost",
"quick-xml", "quick-xml",
"rand 0.8.5", "rand 0.8.5",
@ -35,6 +41,7 @@ dependencies = [
"serde", "serde",
"serde_json", "serde_json",
"sha2", "sha2",
"sha256",
"sqlx", "sqlx",
"thiserror", "thiserror",
"time", "time",
@ -281,6 +288,12 @@ dependencies = [
"password-hash", "password-hash",
] ]
[[package]]
name = "array-init"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d62b7694a562cdf5a74227903507c56ab2cc8bdd1f781ed5cb4cf9c9f810bfc"
[[package]] [[package]]
name = "async-stream" name = "async-stream"
version = "0.3.6" version = "0.3.6"
@ -424,6 +437,12 @@ dependencies = [
"windows-targets 0.52.6", "windows-targets 0.52.6",
] ]
[[package]]
name = "base16ct"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf"
[[package]] [[package]]
name = "base64" name = "base64"
version = "0.22.1" version = "0.22.1"
@ -455,6 +474,30 @@ version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "383d29d513d8764dcdc42ea295d979eb99c3c9f00607b3692cf68a431f7dca72" checksum = "383d29d513d8764dcdc42ea295d979eb99c3c9f00607b3692cf68a431f7dca72"
[[package]]
name = "binrw"
version = "0.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d53195f985e88ab94d1cc87e80049dd2929fd39e4a772c5ae96a7e5c4aad3642"
dependencies = [
"array-init",
"binrw_derive",
"bytemuck",
]
[[package]]
name = "binrw_derive"
version = "0.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5910da05ee556b789032c8ff5a61fb99239580aa3fd0bfaa8f4d094b2aee00ad"
dependencies = [
"either",
"owo-colors",
"proc-macro2",
"quote",
"syn",
]
[[package]] [[package]]
name = "bitflags" name = "bitflags"
version = "2.8.0" version = "2.8.0"
@ -697,6 +740,18 @@ version = "0.8.21"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
[[package]]
name = "crypto-bigint"
version = "0.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76"
dependencies = [
"generic-array",
"rand_core 0.6.4",
"subtle",
"zeroize",
]
[[package]] [[package]]
name = "crypto-common" name = "crypto-common"
version = "0.1.6" version = "0.1.6"
@ -831,6 +886,36 @@ version = "0.15.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b"
[[package]]
name = "dsa"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48bc224a9084ad760195584ce5abb3c2c34a225fa312a128ad245a6b412b7689"
dependencies = [
"digest",
"num-bigint-dig",
"num-traits",
"pkcs8",
"rfc6979",
"sha2",
"signature",
"zeroize",
]
[[package]]
name = "ecdsa"
version = "0.16.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca"
dependencies = [
"der",
"digest",
"elliptic-curve",
"rfc6979",
"signature",
"spki",
]
[[package]] [[package]]
name = "either" name = "either"
version = "1.13.0" version = "1.13.0"
@ -840,6 +925,26 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "elliptic-curve"
version = "0.13.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47"
dependencies = [
"base16ct",
"crypto-bigint",
"digest",
"ff",
"generic-array",
"group",
"pem-rfc7468",
"pkcs8",
"rand_core 0.6.4",
"sec1",
"subtle",
"zeroize",
]
[[package]] [[package]]
name = "email-encoding" name = "email-encoding"
version = "0.4.1" version = "0.4.1"
@ -909,6 +1014,16 @@ version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
[[package]]
name = "ff"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393"
dependencies = [
"rand_core 0.6.4",
"subtle",
]
[[package]] [[package]]
name = "figment" name = "figment"
version = "0.10.19" version = "0.10.19"
@ -1109,6 +1224,7 @@ checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
dependencies = [ dependencies = [
"typenum", "typenum",
"version_check", "version_check",
"zeroize",
] ]
[[package]] [[package]]
@ -1146,6 +1262,17 @@ version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2"
[[package]]
name = "group"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63"
dependencies = [
"ff",
"rand_core 0.6.4",
"subtle",
]
[[package]] [[package]]
name = "gxhash" name = "gxhash"
version = "3.5.0" version = "3.5.0"
@ -1771,6 +1898,20 @@ dependencies = [
"tempfile", "tempfile",
] ]
[[package]]
name = "k256"
version = "0.13.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b"
dependencies = [
"cfg-if",
"ecdsa",
"elliptic-curve",
"once_cell",
"sha2",
"signature",
]
[[package]] [[package]]
name = "language-tags" name = "language-tags"
version = "0.3.2" version = "0.3.2"
@ -2102,9 +2243,9 @@ checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
[[package]] [[package]]
name = "openssl" name = "openssl"
version = "0.10.71" version = "0.10.78"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e14130c6a98cd258fdcb0fb6d744152343ff729cbfcb28c656a9d12b999fbcd" checksum = "f38c4372413cdaaf3cc79dd92d29d7d9f5ab09b51b10dded508fb90bb70b9222"
dependencies = [ dependencies = [
"bitflags", "bitflags",
"cfg-if", "cfg-if",
@ -2134,9 +2275,9 @@ checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e"
[[package]] [[package]]
name = "openssl-sys" name = "openssl-sys"
version = "0.9.106" version = "0.9.114"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8bb61ea9811cc39e3c2069f40b8b8e2e70d8569b361f879786cc7ed48b777cdd" checksum = "13ce1245cd07fcc4cfdb438f7507b0c7e4f3849a69fd84d52374c66d83741bb6"
dependencies = [ dependencies = [
"cc", "cc",
"libc", "libc",
@ -2161,6 +2302,24 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
[[package]]
name = "owo-colors"
version = "4.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d211803b9b6b570f68772237e415a029d5a50c65d382910b879fb19d3271f94d"
[[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]] [[package]]
name = "parking" name = "parking"
version = "2.2.1" version = "2.2.1"
@ -2333,6 +2492,15 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "primeorder"
version = "0.13.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6"
dependencies = [
"elliptic-curve",
]
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.93" version = "1.0.93"
@ -2624,6 +2792,16 @@ dependencies = [
"windows-registry", "windows-registry",
] ]
[[package]]
name = "rfc6979"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2"
dependencies = [
"hmac",
"subtle",
]
[[package]] [[package]]
name = "ring" name = "ring"
version = "0.17.11" version = "0.17.11"
@ -2857,6 +3035,20 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "sec1"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc"
dependencies = [
"base16ct",
"der",
"generic-array",
"pkcs8",
"subtle",
"zeroize",
]
[[package]] [[package]]
name = "security-framework" name = "security-framework"
version = "2.11.1" version = "2.11.1"
@ -3090,6 +3282,19 @@ dependencies = [
"digest", "digest",
] ]
[[package]]
name = "sha256"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f880fc8562bdeb709793f00eb42a2ad0e672c4f883bbe59122b926eca935c8f6"
dependencies = [
"async-trait",
"bytes",
"hex",
"sha2",
"tokio",
]
[[package]] [[package]]
name = "sharded-slab" name = "sharded-slab"
version = "0.1.7" version = "0.1.7"

View file

@ -48,6 +48,13 @@ prost = "0.13.4"
lettre = "0.11.15" lettre = "0.11.15"
rand = "0.8.5" rand = "0.8.5"
reqwest = "0.12.12" reqwest = "0.12.12"
binrw = "0.15.1"
ecdsa = { version = "0.16.9", features = ["pem", "std", "verifying"] }
sha256 = "1.6.0"
p256 = "0.13.2"
k256 = "0.13.4"
dsa = "0.6.3"
openssl = "0.10.78"
time = "0.3.47" time = "0.3.47"

@ -1 +1 @@
Subproject commit 405fe9b47b416e76b21d7087b2ed11606deccfcf Subproject commit 410111190ec9f540d60108b70d55a437a6caf68e

View file

@ -1,38 +1,39 @@
use std::io::Write; use std::io::{Cursor, Write};
use std::ops::{Deref, DerefMut}; use std::ops::{Deref, DerefMut};
// Don't import until required. // Don't import until required.
// use argon2::{Algorithm, Argon2, PasswordHash, PasswordHasher, PasswordVerifier}; // use argon2::{Algorithm, Argon2, PasswordHash, PasswordHasher, PasswordVerifier};
// use argon2::password_hash::rand_core::OsRng; // use argon2::password_hash::rand_core::OsRng;
// use argon2::password_hash::SaltString; // use argon2::password_hash::SaltString;
use base64::Engine; use crate::Pool;
use base64::prelude::BASE64_STANDARD;
use bytemuck::bytes_of;
use chrono::{NaiveDate, NaiveDateTime, Utc};
use rocket::http::Status;
use rocket::{async_trait, Request};
use rocket::request::{FromRequest, Outcome};
use sha2::{Digest, Sha256};
use crate::error::{Error, Errors}; use crate::error::{Error, Errors};
use crate::nnid::oauth::TokenData; use crate::nnid::oauth::TokenData;
use crate::Pool; use base64::Engine;
use base64::prelude::BASE64_STANDARD;
use binrw::{BinRead, binread};
use bytemuck::bytes_of;
use chrono::{NaiveDate, NaiveDateTime, Utc};
use openssl::bn::BigNum;
use openssl::ecdsa::EcdsaSig;
use rand::Rng; use rand::Rng;
use rocket::http::Status;
use rocket::request::{FromRequest, Outcome};
use rocket::{Request, async_trait};
use sha2::{Digest, Sha256};
macro_rules! request_try { macro_rules! request_try {
($expression:expr) => { ($expression:expr) => {
match $expression{ match $expression {
Ok(v) => v, Ok(v) => v,
Err(e) => return Outcome::Error((Status::BadRequest, e)) Err(e) => return Outcome::Error((Status::BadRequest, e)),
} }
}; };
} }
const INVALID_TOKEN_ERRORS: Errors<'static> = Errors{ const INVALID_TOKEN_ERRORS: Errors<'static> = Errors {
error: &[ error: &[Error {
Error{ message: "Invalid access token",
message: "Invalid access token", code: "0005",
code: "0005" }],
}
]
}; };
// optimization note: add token caching // optimization note: add token caching
@ -56,52 +57,48 @@ pub struct User {
pub creation_date: NaiveDateTime, pub creation_date: NaiveDateTime,
pub updated: NaiveDateTime, pub updated: NaiveDateTime,
pub nex_password: String, pub nex_password: String,
pub verification_code: Option<i32> pub verification_code: Option<i32>,
} }
fn generate_nintendo_hash(pid: i32, text_password: &str) -> String{ fn generate_nintendo_hash(pid: i32, text_password: &str) -> String {
let mut sha = Sha256::new(); let mut sha = Sha256::new();
sha.write_all(&bytes_of(&pid)).unwrap(); sha.write_all(&bytes_of(&pid)).unwrap();
sha.write_all(&[0x02, 0x65, 0x43 ,0x46]).unwrap(); sha.write_all(&[0x02, 0x65, 0x43, 0x46]).unwrap();
sha.write_all(text_password.as_bytes()).unwrap(); sha.write_all(text_password.as_bytes()).unwrap();
hex::encode(&sha.finalize()[..]) hex::encode(&sha.finalize()[..])
} }
impl User{ impl User {
pub async fn get_by_username(name: &str, pool: &Pool) -> Option<Self>{ pub async fn get_by_username(name: &str, pool: &Pool) -> Option<Self> {
sqlx::query_as!( sqlx::query_as!(Self, "SELECT * FROM users WHERE username = $1", name)
Self, .fetch_one(pool)
"SELECT * FROM users WHERE username = $1",
name
).fetch_one(pool)
.await .await
.ok() .ok()
} }
fn generate_nintendo_hash(&self, text_password: &str) -> String{ fn generate_nintendo_hash(&self, text_password: &str) -> String {
generate_nintendo_hash(self.pid, text_password) generate_nintendo_hash(self.pid, text_password)
} }
pub fn verify_cleartext_password(&self, cleartext_password: &str) -> Option<bool>{ pub fn verify_cleartext_password(&self, cleartext_password: &str) -> Option<bool> {
let nintendo_hash = self.generate_nintendo_hash(cleartext_password); let nintendo_hash = self.generate_nintendo_hash(cleartext_password);
self.verify_hashed_password(&nintendo_hash) self.verify_hashed_password(&nintendo_hash)
} }
pub fn verify_hashed_password(&self, hashed_password: &str) -> Option<bool>{ pub fn verify_hashed_password(&self, hashed_password: &str) -> Option<bool> {
bcrypt::verify(hashed_password, &self.password).ok() bcrypt::verify(hashed_password, &self.password).ok()
} }
} }
pub fn generate_password(pid: i32, cleartext_password: &str) -> Option<String>{ pub fn generate_password(pid: i32, cleartext_password: &str) -> Option<String> {
let password = generate_nintendo_hash(pid, cleartext_password); let password = generate_nintendo_hash(pid, cleartext_password);
bcrypt::hash(password, 10).ok() bcrypt::hash(password, 10).ok()
} }
pub async fn read_basic_auth_token(connection: &Pool, token: &str) -> Option<User> { pub async fn read_basic_auth_token(connection: &Pool, token: &str) -> Option<User> {
let data = match BASE64_STANDARD.decode(&token) { let data = match BASE64_STANDARD.decode(&token) {
Ok(d) => d, Ok(d) => d,
@ -131,7 +128,9 @@ pub async fn read_basic_auth_token(connection: &Pool, token: &str) -> Option<Use
User, User,
"SELECT * FROM users WHERE username = $1", "SELECT * FROM users WHERE username = $1",
login_username login_username
).fetch_one(connection).await; )
.fetch_one(connection)
.await;
let user = match user_result { let user = match user_result {
Ok(u) => u, Ok(u) => u,
@ -150,26 +149,27 @@ pub async fn read_basic_auth_token(connection: &Pool, token: &str) -> Option<Use
} }
} }
pub async fn read_bearer_auth_token(connection: &Pool, token: &str) -> Option<User> { pub async fn read_bearer_auth_token(connection: &Pool, token: &str) -> Option<User> {
let data = TokenData::decode(token)?; let data = TokenData::decode(token)?;
let token_info = let token_info = sqlx::query!(
sqlx::query!( "select * from tokens where pid = $1 and token_id = $2 and random =$3",
"select * from tokens where pid = $1 and token_id = $2 and random =$3", data.pid,
data.pid, data.token_id, data.random data.token_id,
). data.random
fetch_one(connection).await.ok()?; )
.fetch_one(connection)
.await
.ok()?;
if token_info.expires.and_utc() < Utc::now(){ if token_info.expires.and_utc() < Utc::now() {
return None return None;
} }
let user = sqlx::query_as!( let user = sqlx::query_as!(User, "SELECT * FROM users WHERE pid = $1", token_info.pid)
User, .fetch_one(connection)
"SELECT * FROM users WHERE pid = $1", .await
token_info.pid .ok()?;
).fetch_one(connection).await.ok()?;
Some(user) Some(user)
} }
@ -194,53 +194,71 @@ pub fn generate_nex_password() -> String {
output output
} }
pub struct Auth<const FORCE_BEARER_AUTH: bool>(pub User); pub struct Auth<const FORCE_BEARER_AUTH: bool = true, const USE_CERT: bool = FORCE_BEARER_AUTH>(
pub User,
);
impl<const FORCE_BEARER_AUTH: bool> AsRef<User> for Auth<FORCE_BEARER_AUTH>{ impl<const FORCE_BEARER_AUTH: bool, const USE_CERT: bool> AsRef<User>
for Auth<FORCE_BEARER_AUTH, USE_CERT>
{
fn as_ref(&self) -> &User { fn as_ref(&self) -> &User {
&self.0 &self.0
} }
} }
impl<const FORCE_BEARER_AUTH: bool> AsMut<User> for Auth<FORCE_BEARER_AUTH>{ impl<const FORCE_BEARER_AUTH: bool, const USE_CERT: bool> AsMut<User>
for Auth<FORCE_BEARER_AUTH, USE_CERT>
{
fn as_mut(&mut self) -> &mut User { fn as_mut(&mut self) -> &mut User {
&mut self.0 &mut self.0
} }
} }
impl<const FORCE_BEARER_AUTH: bool> Deref for Auth<FORCE_BEARER_AUTH>{ impl<const FORCE_BEARER_AUTH: bool, const USE_CERT: bool> Deref
for Auth<FORCE_BEARER_AUTH, USE_CERT>
{
type Target = User; type Target = User;
fn deref(&self) -> &Self::Target { fn deref(&self) -> &Self::Target {
&self.0 &self.0
} }
} }
impl<const FORCE_BEARER_AUTH: bool> DerefMut for Auth<FORCE_BEARER_AUTH>{ impl<const FORCE_BEARER_AUTH: bool, const USE_CERT: bool> DerefMut
for Auth<FORCE_BEARER_AUTH, USE_CERT>
{
fn deref_mut(&mut self) -> &mut Self::Target { fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0 &mut self.0
} }
} }
impl<const FORCE_BEARER_AUTH: bool> Into<User> for Auth<FORCE_BEARER_AUTH>{ impl<const FORCE_BEARER_AUTH: bool, const USE_CERT: bool> Into<User>
for Auth<FORCE_BEARER_AUTH, USE_CERT>
{
fn into(self) -> User { fn into(self) -> User {
self.0 self.0
} }
} }
#[async_trait] #[async_trait]
impl<'r, const FORCE_BEARER_AUTH: bool> FromRequest<'r> for Auth<FORCE_BEARER_AUTH>{ impl<'r, const FORCE_BEARER_AUTH: bool, const USE_CERT: bool> FromRequest<'r>
for Auth<FORCE_BEARER_AUTH, USE_CERT>
{
type Error = Errors<'static>; type Error = Errors<'static>;
async fn from_request(request: &'r Request<'_>) -> Outcome<Self, Self::Error> { async fn from_request(request: &'r Request<'_>) -> Outcome<Self, Self::Error> {
let pool: &Pool = request.rocket().state().unwrap(); let pool: &Pool = request.rocket().state().unwrap();
let auth = request_try!(request.headers().get("Authorization").next().ok_or(INVALID_TOKEN_ERRORS)); let auth = request_try!(
request
.headers()
.get("Authorization")
.next()
.ok_or(INVALID_TOKEN_ERRORS)
);
let (auth_type, token) = request_try!(auth.split_once(' ').ok_or(INVALID_TOKEN_ERRORS)); let (auth_type, token) = request_try!(auth.split_once(' ').ok_or(INVALID_TOKEN_ERRORS));
let user = match auth_type{ let user = match auth_type {
"Basic" if !FORCE_BEARER_AUTH => read_basic_auth_token(pool, token).await, "Basic" if !FORCE_BEARER_AUTH => read_basic_auth_token(pool, token).await,
"Bearer" => read_bearer_auth_token(pool, token).await, "Bearer" => read_bearer_auth_token(pool, token).await,
_ => return Outcome::Error((Status::BadRequest, INVALID_TOKEN_ERRORS)), _ => return Outcome::Error((Status::BadRequest, INVALID_TOKEN_ERRORS)),
@ -250,10 +268,24 @@ impl<'r, const FORCE_BEARER_AUTH: bool> FromRequest<'r> for Auth<FORCE_BEARER_AU
return Outcome::Error((Status::BadRequest, INVALID_TOKEN_ERRORS)); return Outcome::Error((Status::BadRequest, INVALID_TOKEN_ERRORS));
}; };
if user.account_level < 0{ if user.account_level < 0 {
return Outcome::Error((Status::BadRequest, INVALID_TOKEN_ERRORS)); return Outcome::Error((Status::BadRequest, INVALID_TOKEN_ERRORS));
} }
if USE_CERT {
let cert = request_try!(
request
.headers()
.get("X-Nintendo-Device-Cert")
.next()
.ok_or(INVALID_TOKEN_ERRORS)
);
let Some(cert) = Certificate::new(&cert) else {
return Outcome::Error((Status::BadGateway, INVALID_TOKEN_ERRORS));
};
}
// let user = User{ // let user = User{
// nex_password: format!("{:a>16}", user.nex_password), // nex_password: format!("{:a>16}", user.nex_password),
// ..user // ..user
@ -262,3 +294,79 @@ impl<'r, const FORCE_BEARER_AUTH: bool> FromRequest<'r> for Auth<FORCE_BEARER_AU
Outcome::Success(Self(user)) Outcome::Success(Self(user))
} }
} }
#[binread]
#[br(big)]
#[derive(Debug)]
pub struct Certificate {
issuer: [u8; 0x40],
key_type: u32,
cert_name: [u8; 0x40],
key_id: u32,
pubkey_data: [u8; 0x40],
}
#[binread]
#[br(big)]
#[br(magic(0x10005u32))]
struct OuterCertificate {
signature: [u8; 0x3C],
padding: [u8; 0x40],
data: [u8; 0x100],
}
const PUB_PEM: &[u8] = br#"-----BEGIN PUBLIC KEY-----
MFIwEAYHKoZIzj0CAQYFK4EEABsDPgAEAP1WBBgs8XUJIQDDCK5IOZEbb5+h1TqV
rwgzSUcrAAFxMWm1kf/TDL9z2nZkuo0N+VtNEQREZDXA7aQv
-----END PUBLIC KEY-----"#;
impl Certificate {
fn new(data: &str) -> Option<Self> {
let data = BASE64_STANDARD.decode(data).unwrap();
let cert = OuterCertificate::read(&mut Cursor::new(&data)).ok()?;
println!("key");
let key = openssl::ec::EcKey::public_key_from_pem(PUB_PEM).ok()?;
let sig_components = read_p1363(&cert.signature)?;
let sig = EcdsaSig::from_private_components(sig_components.0, sig_components.1).unwrap();
let mut hasher = openssl::sha::Sha256::new();
hasher.update(&cert.data);
let hash = hasher.finish();
if !sig.verify(&hash[..], &key).ok()? {
return None;
}
Certificate::read(&mut Cursor::new(cert.data)).ok()
}
pub fn hash(&self) -> [u8; 32] {
let mut hasher = openssl::sha::Sha256::new();
hasher.update(&self.issuer[..]);
hasher.update(bytes_of(&self.key_id));
hasher.update(&self.cert_name[..]);
hasher.update(bytes_of(&self.key_type));
hasher.update(&self.pubkey_data[..]);
hasher.finish()
}
}
fn read_p1363(data: &[u8]) -> Option<(BigNum, BigNum)> {
if data.len() % 2 != 0 {
return None;
}
let half_len = data.len() / 2;
Some((
BigNum::from_slice(&data[..half_len]).ok()?,
BigNum::from_slice(&data[half_len..]).ok()?,
))
}