diff options
| author | Bryan Newbold <bnewbold@robocracy.org> | 2022-11-03 18:19:22 -0700 | 
|---|---|---|
| committer | Bryan Newbold <bnewbold@robocracy.org> | 2022-11-03 18:19:22 -0700 | 
| commit | 95c05cc53c5b42b535cf70f8cee69c1a0be958b7 (patch) | |
| tree | afe00f175bf83228f319fbc214c0ba81f7cb6dfd /adenosine-pds/src | |
| parent | 2004d5ea148b8b21cd0cffeb82fd8f07f52d1ba7 (diff) | |
| download | adenosine-95c05cc53c5b42b535cf70f8cee69c1a0be958b7.tar.gz adenosine-95c05cc53c5b42b535cf70f8cee69c1a0be958b7.zip | |
pds: various bugfixes
Diffstat (limited to 'adenosine-pds/src')
| -rw-r--r-- | adenosine-pds/src/crypto.rs | 61 | ||||
| -rw-r--r-- | adenosine-pds/src/db.rs | 29 | ||||
| -rw-r--r-- | adenosine-pds/src/did.rs | 8 | ||||
| -rw-r--r-- | adenosine-pds/src/lib.rs | 52 | ||||
| -rw-r--r-- | adenosine-pds/src/models.rs | 1 | ||||
| -rw-r--r-- | adenosine-pds/src/repo.rs | 11 | ||||
| -rw-r--r-- | adenosine-pds/src/ucan_p256.rs | 5 | 
7 files changed, 121 insertions, 46 deletions
| 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<String> { +        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<Self> { +        Ok(Self::from_bytes( +            &data_encoding::HEXUPPER.decode(&hex.as_bytes())?, +        )?) +    } +} + +async fn build_ucan(key_material: P256KeyMaterial) -> Result<String> { +    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<AtpSession> { +    pub fn create_session( +        &mut self, +        username: &str, +        password: &str, +        keypair: &KeyPair, +    ) -> Result<AtpSession> {          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::<DefaultParams>::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<S: serde::Serialize>(resp: Result<S>) -> 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<String> {      )))?)  } -fn xrpc_get_atproto( +fn xrpc_get_handler(      srv: &Mutex<AtpService>,      method: &str,      request: &Request,  ) -> Result<serde_json::Value> {      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<AtpService>,      method: &str,      request: &Request,  ) -> Result<impl serde::Serialize> {      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<W: std::io::Write>(          &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();      }  } | 
