From 21202b1376b5519e09f7c0737cb70ac1a679a9c7 Mon Sep 17 00:00:00 2001 From: Marlon Baeten Date: Fri, 18 Oct 2024 15:49:16 +0200 Subject: [PATCH] Add TDW resolve and server Signed-off-by: Marlon Baeten --- .gitignore | 1 + tdw-server/.gitignore | 3 + tdw-server/README.md | 10 ++++ tdw-server/bun.lockb | Bin 0 -> 11740 bytes tdw-server/docker-compose.yml | 10 ++++ tdw-server/package.json | 17 ++++++ tdw-server/prisma/schema.prisma | 17 ++++++ tdw-server/server.ts | 65 +++++++++++++++++++++ tsp/src/crypto/mod.rs | 8 +-- tsp/src/vid/did/mod.rs | 1 + tsp/src/vid/did/tdw.rs | 100 ++++++++++++++++++++++++++++++++ tsp/src/vid/resolve.rs | 10 ++-- 12 files changed, 231 insertions(+), 11 deletions(-) create mode 100644 tdw-server/.gitignore create mode 100644 tdw-server/README.md create mode 100755 tdw-server/bun.lockb create mode 100644 tdw-server/docker-compose.yml create mode 100644 tdw-server/package.json create mode 100644 tdw-server/prisma/schema.prisma create mode 100644 tdw-server/server.ts create mode 100644 tsp/src/vid/did/tdw.rs diff --git a/.gitignore b/.gitignore index 74a9fdc..a781a78 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ Cargo.lock .dockerignore docs/book/ data/ +node_modules/ \ No newline at end of file diff --git a/tdw-server/.gitignore b/tdw-server/.gitignore new file mode 100644 index 0000000..9b7257d --- /dev/null +++ b/tdw-server/.gitignore @@ -0,0 +1,3 @@ +node_modules/ +# Keep environment variables out of version control +.env diff --git a/tdw-server/README.md b/tdw-server/README.md new file mode 100644 index 0000000..931fb0b --- /dev/null +++ b/tdw-server/README.md @@ -0,0 +1,10 @@ +# Development + +```sh +curl -fsSL https://bun.sh/install | bash +bun install +docker compose up +bunx prisma generate +DATABASE_URL=postgresql://tsp-test@127.0.0.1:5432/tsp-test bunx prisma db push +DOMAIN=tdw.tsp-test.org DATABASE_URL=postgresql://tsp-test@127.0.0.1:5432/tsp-test bun serve +``` diff --git a/tdw-server/bun.lockb b/tdw-server/bun.lockb new file mode 100755 index 0000000000000000000000000000000000000000..1e20ff9f82c507309b486080a41d93c04ab8690a GIT binary patch literal 11740 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 'Hello world!') + .get('/create', async () => { + const authKey = await generateEd25519VerificationMethod('authentication'); + const agreementKey = await generateX25519VerificationMethod('keyAgreement'); + + const result = await createDID({ + domain: DOMAIN, + signer: createSigner(authKey), + updateKeys: [ + authKey.publicKeyMultibase, + agreementKey.publicKeyMultibase + ], + verificationMethods: [authKey, agreementKey], + created: new Date(), + }); + + await db.user.create({ + data: { + did: result.did, + doc: result.doc, + log: result.log, + meta: result.meta, + }, + }); + + return { + public: result, + private: { + authKey, + agreementKey + } + }; + }) + .get('/get/:id', async ({ params: { id } }) => { + console.log(`Resolving ${id}...`); + try { + const current = await db.user.findUnique({ where: { did: id } }); + + if (!current) { + throw new Error(`User with DID ${id} not found`); + } + + const logEntries: DIDLog = current.log; + const { doc, meta } = await resolveDID(logEntries); + + return { doc, meta }; + } catch (e) { + console.error(e); + throw new Error(`Failed to resolve DID`); + } + }) + .listen(8000); + +console.log( + `🔍 Publication server is running at on port ${app.server?.port}...` +); diff --git a/tsp/src/crypto/mod.rs b/tsp/src/crypto/mod.rs index e8e36d6..5fb2858 100644 --- a/tsp/src/crypto/mod.rs +++ b/tsp/src/crypto/mod.rs @@ -1,11 +1,9 @@ -use crate::definitions::MessageType; use crate::definitions::{ - Digest, NonConfidentialData, Payload, PrivateKeyData, PrivateSigningKeyData, PrivateVid, - PublicKeyData, PublicVerificationKeyData, TSPMessage, VerifiedVid, + Digest, MessageType, NonConfidentialData, Payload, PrivateKeyData, PrivateSigningKeyData, + PrivateVid, PublicKeyData, PublicVerificationKeyData, TSPMessage, VerifiedVid, }; -pub use digest::blake2b256; -pub use digest::sha256; +pub use digest::{blake2b256, sha256}; use rand::rngs::OsRng; mod digest; diff --git a/tsp/src/vid/did/mod.rs b/tsp/src/vid/did/mod.rs index ca43ac5..7a1651c 100644 --- a/tsp/src/vid/did/mod.rs +++ b/tsp/src/vid/did/mod.rs @@ -2,5 +2,6 @@ pub(crate) const SCHEME: &str = "did"; pub(crate) mod peer; +pub(crate) mod tdw; #[cfg(feature = "resolve")] pub(crate) mod web; diff --git a/tsp/src/vid/did/tdw.rs b/tsp/src/vid/did/tdw.rs new file mode 100644 index 0000000..1bc14bb --- /dev/null +++ b/tsp/src/vid/did/tdw.rs @@ -0,0 +1,100 @@ +use crate::definitions::{PUBLIC_KEY_SIZE, PUBLIC_VERIFICATION_KEY_SIZE}; +use serde::Deserialize; +use url::Url; + +use crate::vid::{error::VidError, Vid}; + +pub(crate) const SCHEME: &str = "twd"; + +const PROTOCOL: &str = "https://"; + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct TdwDocument { + pub doc: Doc, +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Doc { + pub key_agreement: Vec, + pub authentication: Vec, + pub verification_method: Vec, +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct VerificationMethod { + pub id: String, + pub public_key_multibase: String, +} + +pub async fn resolve(id: &str, parts: Vec<&str>) -> Result { + let url = resolve_url(&parts)?; + + let response = reqwest::get(url.as_ref()) + .await + .map_err(|e| VidError::Http(url.to_string(), e))?; + + let did_document: TdwDocument = match response.error_for_status() { + Ok(r) => r + .json() + .await + .map_err(|e| VidError::Json(url.to_string(), e))?, + Err(e) => Err(VidError::Http(url.to_string(), e))?, + }; + + // At this time there is no servce/transport in the TDW document + let transport = format!("{PROTOCOL}{}/user/{}", parts[3], parts[2]); + + let sig_key_id = &did_document.doc.authentication[0]; + let enc_key_id = &did_document.doc.key_agreement[0]; + + let sig_key = did_document + .doc + .verification_method + .iter() + .find(|vm| vm.id == *sig_key_id) + .map(|k| &k.public_key_multibase) + .ok_or_else(|| VidError::ResolveVid("No valid signing key found in DID document"))?; + + let enc_key = did_document + .doc + .verification_method + .iter() + .find(|vm| vm.id == *enc_key_id) + .map(|k| &k.public_key_multibase) + .ok_or_else(|| VidError::ResolveVid("No valid encryption key found in DID document"))?; + + let mut public_sigkey: [u8; PUBLIC_VERIFICATION_KEY_SIZE] = [0; PUBLIC_VERIFICATION_KEY_SIZE]; + + bs58::decode(&sig_key[6..]) + .with_alphabet(bs58::Alphabet::BITCOIN) + .onto(&mut public_sigkey) + .map_err(|_| VidError::ResolveVid("invalid encoded signing key in did:tdw"))?; + + let mut public_enckey: [u8; PUBLIC_KEY_SIZE] = [0; PUBLIC_KEY_SIZE]; + + bs58::decode(&enc_key[6..]) + .with_alphabet(bs58::Alphabet::BITCOIN) + .onto(&mut public_enckey) + .map_err(|_| VidError::ResolveVid("invalid encoded encryption key in did:tdw"))?; + + Ok(Vid { + id: id.to_string(), + transport: Url::parse(&transport).map_err(|_| VidError::InvalidVid(parts.join(":")))?, + public_sigkey: public_sigkey.into(), + public_enckey: public_enckey.into(), + }) +} + +pub fn resolve_url(parts: &[&str]) -> Result { + let full = parts.join(":"); + + match parts { + ["did", "tdw", _id, _domain] => format!("http://localhost:8000/get/{full}"), + _ => return Err(VidError::InvalidVid(parts.join(":"))), + } + .parse() + .map_err(|_| VidError::InvalidVid(parts.join(":"))) +} diff --git a/tsp/src/vid/resolve.rs b/tsp/src/vid/resolve.rs index f222741..74f2470 100644 --- a/tsp/src/vid/resolve.rs +++ b/tsp/src/vid/resolve.rs @@ -1,7 +1,4 @@ -use super::{ - did::{self, peer}, - error::VidError, -}; +use super::{did, error::VidError}; use crate::Vid; #[cfg(feature = "resolve")] @@ -11,7 +8,8 @@ pub async fn verify_vid(id: &str) -> Result { match parts.get(0..2) { Some([did::SCHEME, did::web::SCHEME]) => did::web::resolve(id, parts).await, - Some([did::SCHEME, did::peer::SCHEME]) => peer::verify_did_peer(&parts), + Some([did::SCHEME, did::peer::SCHEME]) => did::peer::verify_did_peer(&parts), + Some([did::SCHEME, did::tdw::SCHEME]) => did::tdw::resolve(id, parts).await, _ => Err(VidError::InvalidVid(id.to_string())), } } @@ -21,7 +19,7 @@ pub fn verify_vid_offline(id: &str) -> Result { let parts = id.split(':').collect::>(); match parts.get(0..2) { - Some([did::SCHEME, did::peer::SCHEME]) => peer::verify_did_peer(&parts), + Some([did::SCHEME, did::peer::SCHEME]) => did::peer::verify_did_peer(&parts), _ => Err(VidError::InvalidVid(id.to_string())), } }