Skip to content

Commit

Permalink
Reintroduce all the ed25519 cryptosuites.
Browse files Browse the repository at this point in the history
  • Loading branch information
timothee-haudebourg committed Jul 20, 2023
1 parent 551370e commit c90d8c2
Show file tree
Hide file tree
Showing 22 changed files with 1,308 additions and 149 deletions.
3 changes: 3 additions & 0 deletions ssi-crypto/src/signature.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
pub enum SignatureError {
#[error("unknown verification method")]
UnknownVerificationMethod,

#[error("internal signer error")]
InternalError,
}

pub trait Signer<M> {
Expand Down
17 changes: 17 additions & 0 deletions ssi-crypto/src/verification.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ pub enum VerificationError {
#[error("invalid key")]
InvalidKey,

/// Key not found.
#[error("unknown key")]
UnknownKey,

/// Cryptographic key is not used correctly.
#[error("invalid use of key with <{0}>")]
InvalidKeyUse(ProofPurpose),
Expand Down Expand Up @@ -94,6 +98,15 @@ macro_rules! proof_purposes {
}
}

impl BitOr<ProofPurpose> for ProofPurpose {
type Output = ProofPurposes;

fn bitor(self, other: Self) -> ProofPurposes {
let result: ProofPurposes = self.into();
result | other
}
}

/// Set of proof purposes.
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ProofPurposes {
Expand Down Expand Up @@ -125,6 +138,10 @@ macro_rules! proof_purposes {
}
}

pub fn contains_all(&self, p: Self) -> bool {
*self & p == p
}

pub fn insert(&mut self, p: ProofPurpose) {
match p {
$(
Expand Down
6 changes: 3 additions & 3 deletions ssi-jwk/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,7 @@ impl Default for Algorithm {
}

impl JWK {
#[cfg(any(feature = "ed25519"))]
#[cfg(feature = "ed25519")]
pub fn generate_ed25519() -> Result<JWK, Error> {
#[cfg(feature = "ring")]
{
Expand Down Expand Up @@ -476,9 +476,9 @@ impl JWK {
#[allow(unused_variables)]
let (codec, k) = multicodec.parts();
match codec {
#[cfg(any(feature = "ed25519"))]
#[cfg(feature = "ed25519")]
ssi_multicodec::ED25519_PUB => ed25519_parse(k),
#[cfg(any(feature = "ed25519"))]
#[cfg(feature = "ed25519")]
ssi_multicodec::ED25519_PRIV => ed25519_parse_private(k),
#[cfg(feature = "secp256k1")]
ssi_multicodec::SECP256K1_PUB => secp256k1_parse(k),
Expand Down
178 changes: 172 additions & 6 deletions ssi-jws/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
// TODO reinstate Error::MissingFeatures ?

pub mod error;
use base64::DecodeError;
pub use base64::DecodeError as Base64DecodeError;
pub use error::Error;
use serde::{Deserialize, Serialize};
Expand Down Expand Up @@ -118,7 +117,7 @@ impl CompactJWS {
/// Decode the payload bytes.
///
/// The header is necessary to know how the payload is encoded.
pub fn decode_payload(&self, header: &Header) -> Result<Cow<[u8]>, DecodeError> {
pub fn decode_payload(&self, header: &Header) -> Result<Cow<[u8]>, Base64DecodeError> {
if header.base64urlencode_payload.unwrap_or(true) {
Ok(Cow::Owned(base64::decode_config(
self.payload(),
Expand All @@ -134,23 +133,90 @@ impl CompactJWS {
&self.0[self.signature_start()..]
}

pub fn decode_signature(&self) -> Result<Vec<u8>, DecodeError> {
pub fn decode_signature(&self) -> Result<Vec<u8>, Base64DecodeError> {
base64::decode_config(self.signature(), base64::URL_SAFE_NO_PAD)
}

/// Decodes the entire JWS.
pub fn decode(&self) -> Result<JWSParts, DecodeError> {
let header = self.decode_header().map_err(DecodeError::Header)?;
let payload = self.decode_payload(&header).map_err(DecodeError::Payload)?;
let signature = self.decode_signature().map_err(DecodeError::Signature)?;
Ok((header, payload, signature))
}

/// Returns the signing bytes.
///
/// It is the concatenation of the Base64 encoded headers, a period '.' and
/// the Base64 encoded payload.
pub fn signing_bytes(&self) -> &[u8] {
&self.0[..self.payload_end()]
}

pub fn as_bytes(&self) -> &[u8] {
&self.0
}
}

pub type JWSParts<'a> = (Header, Cow<'a, [u8]>, Vec<u8>);

#[derive(Debug, thiserror::Error)]
pub enum DecodeError {
#[error("invalid header: {0}")]
Header(InvalidHeader),

#[error("invalid payload: {0}")]
Payload(Base64DecodeError),

#[error("invalid signature: {0}")]
Signature(Base64DecodeError),
}

#[derive(Debug, thiserror::Error)]
#[error("invalid compact JWS")]
pub struct InvalidCompactJWS<B>(pub B);

/// JWS in UTF-8 compact serialized form.
///
/// Contrarily to [`CompactJWS`], this type guarantees that the payload is
/// a valid UTF-8 string, meaning the whole compact JWS is an UTF-8 string.
/// This does not necessarily mean the payload is base64 encoded.
#[repr(transparent)]
pub struct CompactJWSStr(CompactJWS);

impl CompactJWSStr {
pub fn new(data: &str) -> Result<&Self, InvalidCompactJWS<&str>> {
let inner = CompactJWS::new(data.as_bytes()).map_err(|_| InvalidCompactJWS(data))?;
Ok(unsafe { std::mem::transmute(inner) })
}

/// Creates a new compact JWS without checking the data.
///
/// # Safety
///
/// The input `data` must represent a valid compact JWS where the payload
/// is an UTF-8 string.
pub unsafe fn new_unchecked(data: &[u8]) -> &Self {
std::mem::transmute(data)
}

pub fn as_str(&self) -> &str {
unsafe {
// Safety: we already checked that the bytes are a valid UTF-8
// string.
std::str::from_utf8_unchecked(self.0.as_bytes())
}
}
}

impl Deref for CompactJWSStr {
type Target = CompactJWS;

fn deref(&self) -> &Self::Target {
&self.0
}
}

/// JWS in compact serialized form.
pub struct CompactJWSBuf(Vec<u8>);

Expand Down Expand Up @@ -212,6 +278,89 @@ impl Deref for CompactJWSBuf {
}
}

/// JWS in compact serialized form, with a base64 payload.
///
/// Contrarily to [`CompactJWS`], this type guarantees that the payload is
/// a valid UTF-8 string, meaning the whole compact JWS is an UTF-8 string.
/// This does not necessarily mean the payload is base64 encoded.
pub struct CompactJWSString(String);

impl CompactJWSString {
pub fn new(bytes: Vec<u8>) -> Result<Self, InvalidCompactJWS<Vec<u8>>> {
match String::from_utf8(bytes) {
Ok(string) => {
if CompactJWS::check(string.as_bytes()) {
Ok(Self(string))
} else {
Err(InvalidCompactJWS(string.into_bytes()))
}
}
Err(e) => Err(InvalidCompactJWS(e.into_bytes())),
}
}

pub fn from_string(string: String) -> Result<Self, InvalidCompactJWS<String>> {
if CompactJWS::check(string.as_bytes()) {
Ok(Self(string))
} else {
Err(InvalidCompactJWS(string))
}
}

/// # Safety
///
/// The input `bytes` must represent a valid compact JWS where the payload
/// is UTF-8 encoded.
pub unsafe fn new_unchecked(bytes: Vec<u8>) -> Self {
Self(String::from_utf8_unchecked(bytes))
}

pub fn from_signing_bytes_and_signature(
signing_bytes: Vec<u8>,
signature: Vec<u8>,
) -> Result<Self, InvalidCompactJWS<Vec<u8>>> {
let mut bytes = signing_bytes;
bytes.push(b'.');
bytes.extend(signature);
Self::new(bytes)
}

/// # Safety
///
/// The input `signing_bytes` and `signature` must form a valid compact JWS
/// once concatenated with a `.`.
pub unsafe fn from_signing_bytes_and_signature_unchecked(
signing_bytes: Vec<u8>,
signature: Vec<u8>,
) -> Self {
let mut bytes = signing_bytes;
bytes.push(b'.');
bytes.extend(signature);
Self::new_unchecked(bytes)
}

pub fn as_compact_jws_str(&self) -> &CompactJWSStr {
unsafe { CompactJWSStr::new_unchecked(self.0.as_bytes()) }
}

pub fn into_signing_bytes(mut self) -> String {
self.0.truncate(self.payload_end()); // remove the signature.
self.0
}

pub fn into_string(self) -> String {
self.0
}
}

impl Deref for CompactJWSString {
type Target = CompactJWSStr;

fn deref(&self) -> &Self::Target {
self.as_compact_jws_str()
}
}

#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Default)]
pub struct Header {
#[serde(rename = "alg")]
Expand Down Expand Up @@ -268,14 +417,14 @@ pub struct Header {
#[derive(Debug, thiserror::Error)]
pub enum InvalidHeader {
#[error(transparent)]
Base64(DecodeError),
Base64(Base64DecodeError),

#[error(transparent)]
Json(serde_json::Error),
}

impl From<DecodeError> for InvalidHeader {
fn from(value: DecodeError) -> Self {
impl From<Base64DecodeError> for InvalidHeader {
fn from(value: Base64DecodeError) -> Self {
InvalidHeader::Base64(value)
}
}
Expand All @@ -287,6 +436,23 @@ impl From<serde_json::Error> for InvalidHeader {
}

impl Header {
/// Create a new header for a JWS with detached payload.
///
/// Detached means the payload will not be base64 encoded
/// when the `encode_signing_bytes` function is called.
/// This is done by setting the `b64` header parameter to `true`,
/// while adding `b64` to the list of critical parameters the
/// receiver must understand to decode the JWS.
pub fn new_detached(algorithm: Algorithm, key_id: Option<String>) -> Self {
Self {
algorithm,
key_id,
base64urlencode_payload: Some(false),
critical: Some(vec!["b64".to_string()]),
..Default::default()
}
}

/// Decode a JWS Protected Header.
pub fn decode(base_64: &[u8]) -> Result<Self, InvalidHeader> {
let header_json = base64::decode_config(base_64, base64::URL_SAFE_NO_PAD)?;
Expand Down
15 changes: 15 additions & 0 deletions ssi-multicodec/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,12 @@ impl MultiEncoded {
pub fn data(&self) -> &[u8] {
self.parts().1
}

/// Returns the raw bytes, including the codec prefix.
#[inline(always)]
pub fn as_bytes(&self) -> &[u8] {
&self.0
}
}

pub struct MultiEncodedBuf(Vec<u8>);
Expand All @@ -70,6 +76,15 @@ impl MultiEncodedBuf {
Ok(Self(bytes))
}

pub fn encode(codec: u64, bytes: &[u8]) -> Self {
let mut codec_buffer = [0u8; 10];
let encoded_codec = unsigned_varint::encode::u64(codec, &mut codec_buffer);
let mut result = Vec::with_capacity(encoded_codec.len() + bytes.len());
result.extend(encoded_codec);
result.extend(bytes);
Self(result)
}

/// Creates a new multi-encoded slice from the given `bytes` without
/// checking the codec.
///
Expand Down
8 changes: 8 additions & 0 deletions ssi-vc-ldp/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ description = "Verifiable Credential Data Integrity 1.0 implementation for the `
repository = "https://github.com/spruceid/ssi/"
documentation = "https://docs.rs/ssi-ldp/"

[features]
default = ["w3c", "ed25519"]
w3c = []
ed25519 = []

[dependencies]
ssi-verification-methods.workspace = true
async-trait.workspace = true
Expand All @@ -30,6 +35,9 @@ grdf.workspace = true
async-std = { version = "1.9", features = ["attributes"] }
treeldr-rust-macros.workspace = true
static-iref.workspace = true
rand = "0.7"
hashbrown = "0.13.0"
iref = { workspace = true, features = ["hashbrown"] }

[package.metadata.docs.rs]
all-features = true
Expand Down
Loading

0 comments on commit c90d8c2

Please sign in to comment.