From 95c05cc53c5b42b535cf70f8cee69c1a0be958b7 Mon Sep 17 00:00:00 2001 From: Bryan Newbold Date: Thu, 3 Nov 2022 18:19:22 -0700 Subject: pds: various bugfixes --- adenosine-pds/src/crypto.rs | 61 ++++++++++++++++++++++++++++++++++++++---- adenosine-pds/src/db.rs | 29 ++++++++++++++------ adenosine-pds/src/did.rs | 8 +++--- adenosine-pds/src/lib.rs | 52 ++++++++++++++++++++++------------- adenosine-pds/src/models.rs | 1 + adenosine-pds/src/repo.rs | 11 +++----- adenosine-pds/src/ucan_p256.rs | 5 ++-- 7 files changed, 121 insertions(+), 46 deletions(-) (limited to 'adenosine-pds/src') diff --git a/adenosine-pds/src/crypto.rs b/adenosine-pds/src/crypto.rs index e94c34a..0720c07 100644 --- a/adenosine-pds/src/crypto.rs +++ b/adenosine-pds/src/crypto.rs @@ -1,9 +1,12 @@ +use crate::P256KeyMaterial; use anyhow::{anyhow, ensure, Result}; use k256; -use k256::ecdsa::signature::{Signer, Verifier}; use multibase; use p256; +use p256::ecdsa::signature::{Signer, Verifier}; use std::str::FromStr; +use ucan::builder::UcanBuilder; +use ucan::crypto::KeyMaterial; // Need to: // @@ -16,11 +19,13 @@ use std::str::FromStr; const MULTICODE_P256_BYTES: [u8; 2] = [0x80, 0x24]; const MULTICODE_K256_BYTES: [u8; 2] = [0xe7, 0x01]; +#[derive(Clone, PartialEq, Eq)] pub struct KeyPair { public: p256::ecdsa::VerifyingKey, secret: p256::ecdsa::SigningKey, } +#[derive(Clone, PartialEq, Eq)] pub enum PubKey { P256(p256::ecdsa::VerifyingKey), K256(k256::ecdsa::VerifyingKey), @@ -52,23 +57,55 @@ impl KeyPair { } pub fn sign_bytes(&self, data: &[u8]) -> String { - println!("BYTES: {:?}", data); let sig = self.secret.sign(data); data_encoding::BASE64URL_NOPAD.encode(&sig.to_vec()) } + + fn ucan_keymaterial(&self) -> P256KeyMaterial { + P256KeyMaterial(self.public, Some(self.secret.clone())) + } + + /// This is currently just an un-validated token; we don't actually verify these. + pub fn ucan(&self) -> Result { + let key_material = self.ucan_keymaterial(); + let rt = tokio::runtime::Builder::new_current_thread() + .enable_all() + .build()?; + rt.block_on(build_ucan(key_material)) + } + + pub fn to_hex(&self) -> String { + data_encoding::HEXUPPER.encode(&self.to_bytes()) + } + + pub fn from_hex(hex: &str) -> Result { + Ok(Self::from_bytes( + &data_encoding::HEXUPPER.decode(&hex.as_bytes())?, + )?) + } +} + +async fn build_ucan(key_material: P256KeyMaterial) -> Result { + let token_string = UcanBuilder::default() + .issued_by(&key_material) + .for_audience(key_material.get_did().await.unwrap().as_str()) + .with_nonce() + .with_lifetime(60 * 60 * 24 * 90) + .build()? + .sign() + .await? + .encode()?; + Ok(token_string) } impl PubKey { pub fn verify_bytes(&self, data: &[u8], sig: &str) -> Result<()> { - println!("BYTES: {:?}", data); let sig_bytes = data_encoding::BASE64URL_NOPAD.decode(sig.as_bytes())?; // TODO: better way other than this re-encoding? let sig_hex = data_encoding::HEXUPPER.encode(&sig_bytes); match self { PubKey::P256(key) => { - println!("pre-parse: {}", sig); let sig = p256::ecdsa::Signature::from_str(&sig_hex)?; - println!("parsed: {}", sig); Ok(key.verify(data, &sig)?) } PubKey::K256(key) => { @@ -137,6 +174,13 @@ impl PubKey { PubKey::K256(key) => key.to_bytes().to_vec(), } } + + pub fn ucan_keymaterial(&self) -> P256KeyMaterial { + match self { + PubKey::P256(key) => P256KeyMaterial(*key, None), + PubKey::K256(_key) => unimplemented!(), + } + } } impl std::fmt::Display for PubKey { @@ -215,3 +259,10 @@ fn test_signing() { let pubkey = PubKey::from_did_key(&did_key).unwrap(); pubkey.verify_bytes(msg, &sig_str).unwrap(); } + +#[test] +fn test_keypair_hex() { + let before = KeyPair::new_random(); + let after = KeyPair::from_hex(&before.to_hex()).unwrap(); + assert!(before == after); +} diff --git a/adenosine-pds/src/db.rs b/adenosine-pds/src/db.rs index 03f6c68..72f2a8d 100644 --- a/adenosine-pds/src/db.rs +++ b/adenosine-pds/src/db.rs @@ -1,5 +1,5 @@ -use crate::AtpSession; /// ATP database (as distinct from blockstore) +use crate::{AtpSession, KeyPair}; use anyhow::{anyhow, Result}; use lazy_static::lazy_static; use log::debug; @@ -110,19 +110,30 @@ impl AtpDatabase { username: &str, password: &str, email: &str, + recovery_pubkey: &str, ) -> Result<()> { debug!("bcrypt hashing password (can be slow)..."); let password_bcrypt = bcrypt::hash(password, bcrypt::DEFAULT_COST)?; - let did = "did:TODO"; let mut stmt = self.conn.prepare_cached( - "INSERT INTO account (username, password_bcrypt, email, did) VALUES (?1, ?2, ?3, ?4)", + "INSERT INTO account (username, password_bcrypt, email, did, recovery_pubkey) VALUES (?1, ?2, ?3, ?4, ?5)", )?; - stmt.execute(params!(username, password_bcrypt, email, did))?; + stmt.execute(params!( + username, + password_bcrypt, + email, + did, + recovery_pubkey + ))?; Ok(()) } /// Returns a JWT session token - pub fn create_session(&mut self, username: &str, password: &str) -> Result { + pub fn create_session( + &mut self, + username: &str, + password: &str, + keypair: &KeyPair, + ) -> Result { let mut stmt = self .conn .prepare_cached("SELECT did, password_bcrypt FROM account WHERE username = ?1")?; @@ -131,9 +142,11 @@ impl AtpDatabase { if !bcrypt::verify(password, &password_bcrypt)? { return Err(anyhow!("password did not match")); } - // TODO: generate JWT - // TODO: insert session with JWT - let jwt = "jwt:BOGUS"; + let jwt = keypair.ucan()?; + let mut stmt = self + .conn + .prepare_cached("INSERT INTO session (did, jwt) VALUES (?1, ?2)")?; + stmt.execute(params!(did, jwt))?; Ok(AtpSession { did, name: username.to_string(), diff --git a/adenosine-pds/src/did.rs b/adenosine-pds/src/did.rs index 109a717..389b090 100644 --- a/adenosine-pds/src/did.rs +++ b/adenosine-pds/src/did.rs @@ -146,7 +146,7 @@ impl CreateOp { let cpy = (*self).clone(); cpy.into_unsigned() }; - println!("unsigned: {:?}", unsigned); + //println!("unsigned: {:?}", unsigned); let block = Block::::encode(DagCborCodec, Code::Sha2_256, &unsigned) .expect("encode DAG-CBOR"); key.verify_bytes(block.data(), &self.sig) @@ -295,9 +295,7 @@ fn test_did_plc_examples() { .to_string(), }; op.verify_self().unwrap(); - - // XXX: DID PLC generation is also also not consistent - //assert_eq!(&op.did_plc(), "did:plc:7iza6de2dwap2sbkpav7c6c6"); + assert_eq!(&op.did_plc(), "did:plc:7iza6de2dwap2sbkpav7c6c6"); // interacting with PDS / PLC server let op = CreateOp { @@ -311,7 +309,7 @@ fn test_did_plc_examples() { "HNfQUg6SMnYKp1l3LtAIsoAblmi33mYiHE9JH1j7w3B-hd8xWpmCUBUoqKfQXmsAs0K1z8Izt19yYk6PqVFgyg" .to_string(), }; - op.verify_self(); + op.verify_self().unwrap(); assert_eq!(&op.did_plc(), "did:plc:bmrcg7zrxoiw2kiml3tkw2xv"); } diff --git a/adenosine-pds/src/lib.rs b/adenosine-pds/src/lib.rs index 5627a7e..4ea2f7b 100644 --- a/adenosine-pds/src/lib.rs +++ b/adenosine-pds/src/lib.rs @@ -1,6 +1,6 @@ use anyhow::{anyhow, Result}; use libipld::Ipld; -use log::{error, info}; +use log::{error, info, warn}; use rouille::{router, Request, Response}; use serde_json::{json, Value}; use std::fmt; @@ -14,13 +14,16 @@ mod did; mod models; pub mod mst; mod repo; +mod ucan_p256; pub use car::{load_car_to_blockstore, load_car_to_sqlite}; pub use crypto::{KeyPair, PubKey}; pub use db::AtpDatabase; pub use models::*; pub use repo::{RepoCommit, RepoStore}; +pub use ucan_p256::P256KeyMaterial; +#[allow(non_snake_case)] #[derive(Debug, serde::Deserialize, serde::Serialize, PartialEq, Eq)] struct AccountRequest { email: String, @@ -68,6 +71,7 @@ fn xrpc_wrap(resp: Result) -> Response { Some(XrpcError::Forbidden(_)) => 403, None => 500, }; + warn!("HTTP {}: {}", code, msg); Response::json(&json!({ "message": msg })).with_status_code(code) } } @@ -101,11 +105,11 @@ pub fn run_server(port: u16, blockstore_db_path: &PathBuf, atp_db_path: &PathBuf (GET) ["/"] => { Response::text("Not much to see here yet!") }, - (POST) ["/xrpc/com.atproto.{endpoint}", endpoint: String] => { - xrpc_wrap(xrpc_post_atproto(&srv, &endpoint, request)) + (POST) ["/xrpc/{endpoint}", endpoint: String] => { + xrpc_wrap(xrpc_post_handler(&srv, &endpoint, request)) }, - (GET) ["/xrpc/com.atproto.{endpoint}", endpoint: String] => { - xrpc_wrap(xrpc_get_atproto(&srv, &endpoint, request)) + (GET) ["/xrpc/{endpoint}", endpoint: String] => { + xrpc_wrap(xrpc_get_handler(&srv, &endpoint, request)) }, _ => rouille::Response::empty_404() ) @@ -138,16 +142,16 @@ fn xrpc_required_param(request: &Request, key: &str) -> Result { )))?) } -fn xrpc_get_atproto( +fn xrpc_get_handler( srv: &Mutex, method: &str, request: &Request, ) -> Result { match method { - "getAccountsConfig" => { + "com.atproto.getAccountsConfig" => { Ok(json!({"availableUserDomains": ["test"], "inviteCodeRequired": false})) } - "getRecord" => { + "com.atproto.getRecord" => { let did = xrpc_required_param(request, "did")?; let collection = xrpc_required_param(request, "collection")?; let rkey = xrpc_required_param(request, "rkey")?; @@ -163,7 +167,7 @@ fn xrpc_get_atproto( Err(e) => Err(e), } } - "syncGetRoot" => { + "com.atproto.syncGetRoot" => { let did = xrpc_required_param(request, "did")?; let mut srv = srv.lock().expect("service mutex"); srv.repo @@ -172,20 +176,21 @@ fn xrpc_get_atproto( .ok_or(XrpcError::NotFound(format!("no repository found for DID: {}", did)).into()) } _ => Err(anyhow!(XrpcError::NotFound(format!( - "XRPC endpoint handler not found: com.atproto.{}", + "XRPC endpoint handler not found: {}", method )))), } } -fn xrpc_post_atproto( +fn xrpc_post_handler( srv: &Mutex, method: &str, request: &Request, ) -> Result { match method { - "createAccount" => { + "com.atproto.createAccount" => { // TODO: generate did:plc, and insert an empty record/pointer to repo + info!("creating new account"); // validate account request let req: AccountRequest = rouille::input::json_input(request) @@ -205,30 +210,41 @@ fn xrpc_post_atproto( req.username.clone(), srv.pds_public_url.clone(), &srv.pds_keypair, - req.recoveryKey, + req.recoveryKey.clone(), ); create_op.verify_self()?; let did = create_op.did_plc(); let did_doc = create_op.did_doc(); // register in ATP DB and generate DID doc - srv.atp_db - .create_account(&did, &req.username, &req.password, &req.email)?; + let recovery_key = req + .recoveryKey + .unwrap_or(srv.pds_keypair.pubkey().to_did_key()); + srv.atp_db.create_account( + &did, + &req.username, + &req.password, + &req.email, + &recovery_key, + )?; srv.atp_db.put_did_doc(&did, &did_doc)?; // insert empty MST repository let root_cid = { let empty_map_cid: String = srv.repo.mst_from_map(&Default::default())?; let meta_cid = srv.repo.write_metadata(&did)?; - srv.repo.write_root(&did, &meta_cid, None, &empty_map_cid)? + srv.repo.write_root(&meta_cid, None, &empty_map_cid)? }; let _commit_cid = srv.repo.write_commit(&did, &root_cid, "XXX-dummy-sig")?; - let sess = srv.atp_db.create_session(&req.username, &req.password)?; + let keypair = srv.pds_keypair.clone(); + let sess = srv + .atp_db + .create_session(&req.username, &req.password, &keypair)?; Ok(sess) } _ => Err(anyhow!(XrpcError::NotFound(format!( - "XRPC endpoint handler not found: com.atproto.{}", + "XRPC endpoint handler not found: {}", method )))), } diff --git a/adenosine-pds/src/models.rs b/adenosine-pds/src/models.rs index cfbd877..8f855c5 100644 --- a/adenosine-pds/src/models.rs +++ b/adenosine-pds/src/models.rs @@ -1,5 +1,6 @@ use serde; +#[allow(non_snake_case)] #[derive(Debug, serde::Serialize, serde::Deserialize, Clone, PartialEq, Eq)] pub struct AtpSession { pub did: String, diff --git a/adenosine-pds/src/repo.rs b/adenosine-pds/src/repo.rs index 419d23f..5351379 100644 --- a/adenosine-pds/src/repo.rs +++ b/adenosine-pds/src/repo.rs @@ -166,7 +166,6 @@ impl RepoStore { pub fn write_root( &mut self, - did: &str, meta_cid: &str, prev: Option<&str>, mst_cid: &str, @@ -228,9 +227,9 @@ impl RepoStore { /// The "from" commit CID feature is not implemented. pub fn write_car( &mut self, - did: &str, + _did: &str, _from_commit_cid: Option<&str>, - out: &mut W, + _out: &mut W, ) -> Result<()> { unimplemented!() } @@ -271,9 +270,7 @@ fn test_repo_mst() { // create root and commit IPLD nodes let meta_cid = repo.write_metadata(did).unwrap(); - let simple_root_cid = repo - .write_root(did, &meta_cid, None, &simple_map_cid) - .unwrap(); + let simple_root_cid = repo.write_root(&meta_cid, None, &simple_map_cid).unwrap(); let simple_commit_cid = repo .write_commit(did, &simple_root_cid, "dummy-sig") .unwrap(); @@ -299,7 +296,7 @@ fn test_repo_mst() { map.insert("/records/3".to_string(), record_cid.clone()); let simple3_map_cid: String = repo.mst_from_map(&map).unwrap(); let simple3_root_cid = repo - .write_root(did, &meta_cid, Some(&simple_commit_cid), &simple3_map_cid) + .write_root(&meta_cid, Some(&simple_commit_cid), &simple3_map_cid) .unwrap(); let simple3_commit_cid = repo .write_commit(did, &simple3_root_cid, "dummy-sig3") diff --git a/adenosine-pds/src/ucan_p256.rs b/adenosine-pds/src/ucan_p256.rs index 9fe89ed..21e8a9a 100644 --- a/adenosine-pds/src/ucan_p256.rs +++ b/adenosine-pds/src/ucan_p256.rs @@ -66,8 +66,7 @@ mod tests { #[cfg_attr(not(target_arch = "wasm32"), tokio::test)] async fn it_can_sign_and_verify_a_ucan() { - let rng = rand::thread_rng(); - let private_key = P256PrivateKey::new(rng); + let private_key = P256PrivateKey::random(&mut p256::elliptic_curve::rand_core::OsRng); let public_key = P256PublicKey::from(&private_key); let key_material = P256KeyMaterial(public_key, Some(private_key)); @@ -85,7 +84,7 @@ mod tests { let mut did_parser = DidParser::new(&[(P256_MAGIC_BYTES, bytes_to_p256_key)]); - let ucan = Ucan::try_from(token_string).unwrap(); + let ucan = Ucan::try_from_token_string(&token_string).unwrap(); ucan.check_signature(&mut did_parser).await.unwrap(); } } -- cgit v1.2.3