aboutsummaryrefslogtreecommitdiffstats
path: root/adenosine-pds
diff options
context:
space:
mode:
Diffstat (limited to 'adenosine-pds')
-rw-r--r--adenosine-pds/src/crypto.rs61
-rw-r--r--adenosine-pds/src/db.rs29
-rw-r--r--adenosine-pds/src/did.rs8
-rw-r--r--adenosine-pds/src/lib.rs52
-rw-r--r--adenosine-pds/src/models.rs1
-rw-r--r--adenosine-pds/src/repo.rs11
-rw-r--r--adenosine-pds/src/ucan_p256.rs5
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();
}
}