Skip to content

Commit

Permalink
UCAN JWT canonicalization (#28)
Browse files Browse the repository at this point in the history
* UCAN JWT canonicalization

* Make store write a mut operation

* Hash with Blake2b instead of SHA256

* Add default implementations for KeyMaterial

* Remove vestigial empty file
  • Loading branch information
cdata committed Oct 4, 2022
1 parent 5d80833 commit 2f2b759
Show file tree
Hide file tree
Showing 37 changed files with 1,269 additions and 334 deletions.
File renamed without changes.
48 changes: 28 additions & 20 deletions .github/workflows/run_test_suite.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,31 +14,39 @@ jobs:
~/.cargo
./target
key: ${{ runner.os }}-cargo-artifacts-${{ hashFiles('**/Cargo.toml') }}
- name: 'Install environment packages'
run: |
sudo apt-get update -qqy
sudo apt-get install jq
- name: 'Run tests'
run: cargo test
shell: bash
- name: 'Setup Browserstack environment'
uses: 'browserstack/github-actions/setup-env@master'
with:
username: ${{ secrets.BROWSERSTACK_USERNAME }}
access-key: ${{ secrets.BROWSERSTACK_TOKEN }}
build-name: BUILD_INFO
project-name: REPO_NAME
- name: 'Open connection to Browserstack'
uses: 'browserstack/github-actions/setup-local@master'
with:
local-testing: start
local-identifier: random
- name: 'Run browser tests for ucan-key-support crate'
- name: 'Install Rust/WASM test dependencies'
run: |
cd ./ucan-key-support
BROWSERSTACK=1 ./scripts/run_browser_tests.sh
rustup target install wasm32-unknown-unknown
cargo install toml-cli
WASM_BINDGEN_VERSION=`toml get ./Cargo.lock . | jq '.package | map(select(.name == "wasm-bindgen"))[0].version' | xargs echo`
cargo install wasm-bindgen-cli --vers "$WASM_BINDGEN_VERSION"
shell: bash
# See: https://github.com/SeleniumHQ/selenium/blob/5d108f9a679634af0bbc387e7e3811bc1565912b/.github/actions/setup-chrome/action.yml
- name: 'Setup Chrome and chromedriver'
run: |
wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | sudo apt-key add -
echo "deb http://dl.google.com/linux/chrome/deb/ stable main" | sudo tee -a /etc/apt/sources.list.d/google-chrome.list
sudo apt-get update -qqy
sudo apt-get -qqy install google-chrome-stable
CHROME_VERSION=$(google-chrome-stable --version)
CHROME_FULL_VERSION=${CHROME_VERSION%%.*}
CHROME_MAJOR_VERSION=${CHROME_FULL_VERSION//[!0-9]}
sudo rm /etc/apt/sources.list.d/google-chrome.list
export CHROMEDRIVER_VERSION=`curl -s https://chromedriver.storage.googleapis.com/LATEST_RELEASE_${CHROME_MAJOR_VERSION%%.*}`
curl -L -O "https://chromedriver.storage.googleapis.com/${CHROMEDRIVER_VERSION}/chromedriver_linux64.zip"
unzip chromedriver_linux64.zip && chmod +x chromedriver && sudo mv chromedriver /usr/local/bin
chromedriver -version
shell: bash
- name: 'Run Rust headless browser tests'
run: CHROMEDRIVER=/usr/local/bin/chromedriver cargo test --target wasm32-unknown-unknown
shell: bash
- name: 'Close connection to Browserstack'
uses: browserstack/github-actions/setup-local@master
if: always()
with:
local-testing: stop
publish-release:
runs-on: ubuntu-latest
needs: [run-test-suite]
Expand Down
20 changes: 10 additions & 10 deletions ucan-key-support/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ version = "0.4.0-alpha.1"

[features]
default = []
web = ["wasm-bindgen", "wasm-bindgen-futures", "js-sys", "web-sys", "ucan/web", "getrandom/js"]

[dependencies]
ucan = {path = "../ucan", version = "0.6.0-alpha.1" }
Expand All @@ -34,20 +33,21 @@ log = "0.4"
npm_rs = "0.2.1"

[dev-dependencies]
rand = "0.8"
rand = "~0.8"
# NOTE: This is needed so that rand can be included in WASM builds
getrandom = { version = "0.2.5", features = ["js"] }
wasm-bindgen-test = "0.3"
getrandom = { version = "~0.2", features = ["js"] }
wasm-bindgen-test = "~0.3"

[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies]
tokio = { version = "^1", features = ["macros", "rt"] }

[target.'cfg(target_arch = "wasm32")'.dependencies]
wasm-bindgen = { version = "0.2", optional = true }
wasm-bindgen-futures = { version = "0.4", optional = true }
js-sys = { version = "0.3", optional = true }
wasm-bindgen = { version = "0.2" }
wasm-bindgen-futures = { version = "0.4" }
js-sys = { version = "0.3" }

[target.'cfg(target_arch="wasm32")'.dependencies.web-sys]
[target.'cfg(target_arch = "wasm32")'.dependencies.web-sys]
version = "0.3"
optional = true
features = [
'Window',
'SubtleCrypto',
Expand All @@ -57,5 +57,5 @@ features = [
'DedicatedWorkerGlobalScope'
]

[target.'cfg(target_arch="wasm32")'.dev-dependencies]
[target.'cfg(target_arch = "wasm32")'.dev-dependencies]
pollster = "0.2.5"
52 changes: 0 additions & 52 deletions ucan-key-support/scripts/run_browser_tests.sh

This file was deleted.

20 changes: 14 additions & 6 deletions ucan-key-support/src/ed25519.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ use ed25519_zebra::{

use ucan::crypto::KeyMaterial;

pub const ED25519_MAGIC_BYTES: [u8; 2] = [0xed, 0x01];
pub use ucan::crypto::did::ED25519_MAGIC_BYTES;
pub use ucan::crypto::JwtSignatureAlgorithm;

pub fn bytes_to_ed25519_key(bytes: Vec<u8>) -> Result<Box<dyn KeyMaterial>> {
let public_key = Ed25519PublicKey::try_from(bytes.as_slice())?;
Expand All @@ -17,15 +18,15 @@ pub fn bytes_to_ed25519_key(bytes: Vec<u8>) -> Result<Box<dyn KeyMaterial>> {
#[derive(Clone)]
pub struct Ed25519KeyMaterial(pub Ed25519PublicKey, pub Option<Ed25519PrivateKey>);

#[cfg_attr(all(target_arch="wasm32", feature = "web"), async_trait(?Send))]
#[cfg_attr(any(not(target_arch = "wasm32"), not(feature = "web")), async_trait)]
#[cfg_attr(target_arch="wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
impl KeyMaterial for Ed25519KeyMaterial {
fn get_jwt_algorithm_name(&self) -> String {
"EdDSA".into()
JwtSignatureAlgorithm::EdDSA.to_string()
}

async fn get_did(&self) -> Result<String> {
let bytes = [ED25519_MAGIC_BYTES.as_slice(), self.0.as_ref()].concat();
let bytes = [ED25519_MAGIC_BYTES, self.0.as_ref()].concat();
Ok(format!("did:key:z{}", bs58::encode(bytes).into_string()))
}

Expand Down Expand Up @@ -58,7 +59,14 @@ mod tests {
ucan::Ucan,
};

#[tokio::test]
#[cfg(target_arch = "wasm32")]
use wasm_bindgen_test::{wasm_bindgen_test, wasm_bindgen_test_configure};

#[cfg(target_arch = "wasm32")]
wasm_bindgen_test_configure!(run_in_browser);

#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
#[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 = Ed25519PrivateKey::new(rng);
Expand Down
2 changes: 1 addition & 1 deletion ucan-key-support/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#[macro_use]
extern crate log;

#[cfg(all(target_arch = "wasm32", feature = "web"))]
#[cfg(target_arch = "wasm32")]
pub mod web_crypto;

pub mod ed25519;
Expand Down
28 changes: 16 additions & 12 deletions ucan-key-support/src/rsa.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,14 @@
use anyhow::{anyhow, Result};
use async_trait::async_trait;

use rsa::{
Hash, PaddingScheme, PublicKey, RsaPrivateKey, RsaPublicKey,
};
use rsa::pkcs1::{DecodeRsaPublicKey, EncodeRsaPublicKey};
use rsa::pkcs1::der::{Document, Encodable};
use rsa::pkcs1::{DecodeRsaPublicKey, EncodeRsaPublicKey};
use rsa::{Hash, PaddingScheme, PublicKey, RsaPrivateKey, RsaPublicKey};

use sha2::{Digest, Sha256};
use ucan::crypto::KeyMaterial;
use ucan::crypto::{JwtSignatureAlgorithm, KeyMaterial};

pub const RSA_MAGIC_BYTES: [u8; 2] = [0x85, 0x24];
pub const RSA_ALGORITHM: &str = "RSASSA-PKCS1-v1_5";
pub use ucan::crypto::did::RSA_MAGIC_BYTES;

pub fn bytes_to_rsa_key(bytes: Vec<u8>) -> Result<Box<dyn KeyMaterial>> {
// NOTE: DID bytes are PKCS1, but we are using PKCS8, so do the conversion here..
Expand All @@ -25,16 +22,16 @@ pub fn bytes_to_rsa_key(bytes: Vec<u8>) -> Result<Box<dyn KeyMaterial>> {
#[derive(Clone)]
pub struct RsaKeyMaterial(pub RsaPublicKey, pub Option<RsaPrivateKey>);

#[cfg_attr(all(target_arch="wasm32", feature = "web"), async_trait(?Send))]
#[cfg_attr(any(not(target_arch = "wasm32"), not(feature = "web")), async_trait)]
#[cfg_attr(target_arch="wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
impl KeyMaterial for RsaKeyMaterial {
fn get_jwt_algorithm_name(&self) -> String {
RSA_ALGORITHM.into()
JwtSignatureAlgorithm::RS256.to_string()
}

async fn get_did(&self) -> Result<String> {
let bytes = match self.0.to_pkcs1_der() {
Ok(document) => [RSA_MAGIC_BYTES.as_slice(), document.as_der()].concat(),
Ok(document) => [RSA_MAGIC_BYTES, document.as_der()].concat(),
Err(error) => {
// TODO: Probably shouldn't swallow this error...
warn!("Could not get RSA public key bytes for DID: {:?}", error);
Expand Down Expand Up @@ -95,7 +92,14 @@ mod tests {
use ucan::crypto::KeyMaterial;
use ucan::ucan::Ucan;

#[tokio::test]
#[cfg(target_arch = "wasm32")]
use wasm_bindgen_test::{wasm_bindgen_test, wasm_bindgen_test_configure};

#[cfg(target_arch = "wasm32")]
wasm_bindgen_test_configure!(run_in_browser);

#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
#[cfg_attr(not(target_arch = "wasm32"), tokio::test)]
async fn it_can_sign_and_verify_a_ucan() {
let private_key =
RsaPrivateKey::from_pkcs8_der(include_bytes!("./fixtures/rsa_key.pk8")).unwrap();
Expand Down
13 changes: 8 additions & 5 deletions ucan-key-support/src/web_crypto.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
use crate::rsa::{RsaKeyMaterial, RSA_ALGORITHM};
use crate::rsa::{RsaKeyMaterial};
use anyhow::{anyhow, Result};
use async_trait::async_trait;
use js_sys::{Array, ArrayBuffer, Boolean, Object, Reflect, Uint8Array};
use rsa::RsaPublicKey;
use rsa::pkcs1::DecodeRsaPublicKey;
use rsa::pkcs1::der::Encodable;
use ucan::crypto::KeyMaterial;
use ucan::crypto::JwtSignatureAlgorithm;
use wasm_bindgen::{JsCast, JsValue};
use wasm_bindgen_futures::JsFuture;
use web_sys::{Crypto, CryptoKey, CryptoKeyPair, SubtleCrypto};
Expand All @@ -17,6 +18,8 @@ pub fn convert_spki_to_rsa_public_key(spki_bytes: &[u8]) -> Result<Vec<u8>> {
Ok(Vec::from(&spki_bytes[24..]))
}

pub const WEB_CRYPTO_RSA_ALGORITHM: &str = "RSASSA-PKCS1-v1_5";

#[derive(Debug)]
pub struct WebCryptoRsaKeyMaterial(pub CryptoKey, pub Option<CryptoKey>);

Expand Down Expand Up @@ -46,7 +49,7 @@ impl WebCryptoRsaKeyMaterial {
Reflect::set(
&algorithm,
&JsValue::from("name"),
&JsValue::from(RSA_ALGORITHM),
&JsValue::from(WEB_CRYPTO_RSA_ALGORITHM),
)
.map_err(|error| anyhow!("{:?}", error))?;

Expand Down Expand Up @@ -105,7 +108,7 @@ impl WebCryptoRsaKeyMaterial {
#[async_trait(?Send)]
impl KeyMaterial for WebCryptoRsaKeyMaterial {
fn get_jwt_algorithm_name(&self) -> String {
RSA_ALGORITHM.into()
JwtSignatureAlgorithm::RS256.to_string()
}

async fn get_did(&self) -> Result<String> {
Expand Down Expand Up @@ -141,7 +144,7 @@ impl KeyMaterial for WebCryptoRsaKeyMaterial {
Reflect::set(
&algorithm,
&JsValue::from("name"),
&JsValue::from(RSA_ALGORITHM),
&JsValue::from(WEB_CRYPTO_RSA_ALGORITHM),
)
.map_err(|error| anyhow!("{:?}", error))?;

Expand Down Expand Up @@ -175,7 +178,7 @@ impl KeyMaterial for WebCryptoRsaKeyMaterial {
Reflect::set(
&algorithm,
&JsValue::from("name"),
&JsValue::from(RSA_ALGORITHM),
&JsValue::from(WEB_CRYPTO_RSA_ALGORITHM),
)
.map_err(|error| anyhow!("{:?}", error))?;
Reflect::set(
Expand Down
27 changes: 19 additions & 8 deletions ucan/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,24 +18,35 @@ edition = "2021"

[features]
default = []
web = ["instant/wasm-bindgen"]

[dependencies]
cid = "~0.8"
anyhow = "^1"
async-trait = "0.1"
async-trait = "~0.1"
async-recursion = "^1"
async-std = "^1"
serde_json = "^1"
serde = { version = "^1", features = ["derive"] }
base64 = "0.13"
textnonce = "^1"
log = "0.4"
base64 = "~0.13"
log = "~0.4"
url = "^2"
bs58 = "0.4"
bs58 = "~0.4"
unsigned-varint = "~0.7"
libipld-core = { version = "~0.14", features = ["serde-codec", "serde"] }
libipld-json = "~0.14"
strum = "~0.24"
strum_macros = "~0.24"
instant = { version = "0.1", features = ["wasm-bindgen", "stdweb"] }
rand = "~0.8"

[target.'cfg(target_arch = "wasm32")'.dependencies]
instant = { version = "0.1" }
# NOTE: This is needed so that rand can be included in WASM builds
getrandom = { version = "~0.2", features = ["js"] }

[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies]
tokio = { version = "^1", features = ["macros", "test-util"] }

[dev-dependencies]
did-key = "0.1"
tokio = { version = "^1", features = ["macros", "rt"] }
serde_ipld_dagcbor = "~0.2"
wasm-bindgen-test = "~0.3"
Loading

0 comments on commit 2f2b759

Please sign in to comment.