feat(crypto-js): Implement OlmMachine.sign.

This commit is contained in:
Ivan Enderlin
2022-08-16 12:14:48 +02:00
parent 3f0509e7b1
commit 2dffe03c8d
5 changed files with 251 additions and 1 deletions

View File

@@ -26,6 +26,7 @@ pub mod olm;
pub mod requests;
pub mod responses;
pub mod sync_events;
pub mod types;
pub mod vodozemac;
mod tracing;

View File

@@ -16,6 +16,7 @@ use crate::{
vodozemac,
responses::{self, response_from_string},
sync_events,
types,
};
/// State machine implementation of the Olm/Megolm encryption protocol
@@ -297,6 +298,16 @@ impl OlmMachine {
})
}
/// Sign the given message using our device key and if available
/// cross-signing master key.
pub fn sign(&self, message: String) -> Promise {
let me = self.inner.clone();
future_to_promise::<_, types::Signatures>(async move {
Ok(me.sign(&message).await.into())
})
}
/// Invalidate the currently active outbound group session for the
/// given room.
///

View File

@@ -0,0 +1,165 @@
use js_sys::Map;
use wasm_bindgen::prelude::*;
use crate::{
identifiers::{DeviceKeyId, UserId},
vodozemac::Ed25519Signature,
};
/// A collection of `Signature`.
#[wasm_bindgen]
#[derive(Debug, Default)]
pub struct Signatures {
inner: matrix_sdk_crypto::types::Signatures,
}
impl From<matrix_sdk_crypto::types::Signatures> for Signatures {
fn from(inner: matrix_sdk_crypto::types::Signatures) -> Self {
Self { inner }
}
}
#[wasm_bindgen]
impl Signatures {
/// Creates a new, empty, signatures collection.
#[wasm_bindgen(constructor)]
pub fn new() -> Self {
matrix_sdk_crypto::types::Signatures::new().into()
}
/// Add the given signature from the given signer and the given key ID to
/// the collection.
#[wasm_bindgen(js_name = "addSignature")]
pub fn add_signature(
&mut self,
signer: &UserId,
key_id: &DeviceKeyId,
signature: &Ed25519Signature,
) -> Option<MaybeSignature> {
self.inner
.add_signature(signer.inner.clone(), key_id.inner.clone(), signature.inner)
.map(Into::into)
}
/// Try to find an Ed25519 signature from the given signer with
/// the given key ID.
#[wasm_bindgen(js_name = "getSignature")]
pub fn get_signature(&self, signer: &UserId, key_id: &DeviceKeyId) -> Option<Ed25519Signature> {
self.inner.get_signature(signer.inner.as_ref(), key_id.inner.as_ref()).map(Into::into)
}
/// Get the map of signatures that belong to the given user.
pub fn get(&self, signer: &UserId) -> Option<Map> {
let map = Map::new();
for (device_key_id, maybe_signature) in
self.inner.get(signer.inner.as_ref()).map(|map| {
map.iter().map(|(device_key_id, maybe_signature)| {
(
device_key_id.as_str().to_owned(),
MaybeSignature::from(maybe_signature.clone()),
)
})
})?
{
map.set(&device_key_id.into(), &maybe_signature.into());
}
Some(map)
}
/// Remove all the signatures we currently hold.
pub fn clear(&mut self) {
self.inner.clear();
}
/// Do we hold any signatures or is our collection completely
/// empty.
#[wasm_bindgen(getter, js_name = "isEmpty")]
pub fn is_empty(&self) -> bool {
self.inner.is_empty()
}
/// How many signatures do we currently hold.
#[wasm_bindgen(getter)]
pub fn count(&self) -> usize {
self.inner.signature_count()
}
}
/// Represents a potentially decoded signature (but not a validated
/// one).
#[wasm_bindgen]
#[derive(Debug)]
pub struct Signature {
inner: matrix_sdk_crypto::types::Signature,
}
impl From<matrix_sdk_crypto::types::Signature> for Signature {
fn from(inner: matrix_sdk_crypto::types::Signature) -> Self {
Self { inner }
}
}
#[wasm_bindgen]
impl Signature {
/// Get the Ed25519 signature, if this is one.
#[wasm_bindgen(getter)]
pub fn ed25519(&self) -> Option<Ed25519Signature> {
self.inner.ed25519().map(Into::into)
}
/// Convert the signature to a base64 encoded string.
#[wasm_bindgen(js_name = "toBase64")]
pub fn to_base64(&self) -> String {
self.inner.to_base64()
}
}
type MaybeSignatureInner =
Result<matrix_sdk_crypto::types::Signature, matrix_sdk_crypto::types::InvalidSignature>;
/// Represents a signature that is either valid _or_ that could not be
/// decoded.
#[wasm_bindgen]
#[derive(Debug)]
pub struct MaybeSignature {
inner: MaybeSignatureInner,
}
impl From<MaybeSignatureInner> for MaybeSignature {
fn from(inner: MaybeSignatureInner) -> Self {
Self { inner }
}
}
#[wasm_bindgen]
impl MaybeSignature {
/// Check whether the signature has been successfully decoded.
#[wasm_bindgen(getter, js_name = "isValid")]
pub fn is_valid(&self) -> bool {
self.inner.is_ok()
}
/// Check whether the signature could not be successfully decoded.
#[wasm_bindgen(getter, js_name = "isInvalid")]
pub fn is_invalid(&self) -> bool {
self.inner.is_err()
}
/// The signature, if successfully decoded.
#[wasm_bindgen(getter)]
pub fn signature(&self) -> Option<Signature> {
self.inner.as_ref().cloned().map(Into::into).ok()
}
/// The base64 encoded string that is claimed to contain a
/// signature but could not be decoded, if any.
#[wasm_bindgen(getter, js_name = "invalidSignatureSource")]
pub fn invalid_signature_source(&self) -> Option<String> {
match &self.inner {
Ok(_) => None,
Err(signature) => Some(signature.source.clone()),
}
}
}

View File

@@ -25,6 +25,37 @@ impl Ed25519PublicKey {
}
}
/// An Ed25519 digital signature, can be used to verify the
/// authenticity of a message.
#[wasm_bindgen]
#[derive(Debug)]
pub struct Ed25519Signature {
pub(crate) inner: vodozemac::Ed25519Signature,
}
impl From<vodozemac::Ed25519Signature> for Ed25519Signature {
fn from(inner: vodozemac::Ed25519Signature) -> Self {
Self { inner }
}
}
#[wasm_bindgen]
impl Ed25519Signature {
/// Try to create an Ed25519 signature from an unpadded base64
/// representation.
#[wasm_bindgen(constructor)]
pub fn new(signature: String) -> Result<Ed25519Signature, JsError> {
Ok(Self { inner: vodozemac::Ed25519Signature::from_base64(signature.as_str())? })
}
/// Serialize a Ed25519 signature to an unpadded base64
/// representation.
#[wasm_bindgen(js_name = "toBase64")]
pub fn to_base64(&self) -> String {
self.inner.to_base64()
}
}
/// A Curve25519 public key.
#[wasm_bindgen]
#[derive(Debug, Clone)]

View File

@@ -1,4 +1,4 @@
const { OlmMachine, UserId, DeviceId, RoomId, DeviceLists, RequestType, KeysUploadRequest, KeysQueryRequest, KeysClaimRequest, EncryptionSettings, DecryptedRoomEvent, VerificationState, CrossSigningStatus } = require('../pkg/matrix_sdk_crypto_js');
const { OlmMachine, UserId, DeviceId, DeviceKeyId, RoomId, DeviceLists, RequestType, KeysUploadRequest, KeysQueryRequest, KeysClaimRequest, EncryptionSettings, DecryptedRoomEvent, VerificationState, CrossSigningStatus, MaybeSignature } = require('../pkg/matrix_sdk_crypto_js');
describe(OlmMachine.name, () => {
test('can be instantiated with the async initializer', async () => {
@@ -350,4 +350,46 @@ describe(OlmMachine.name, () => {
expect(crossSigningStatus.hasSelfSigning).toStrictEqual(false);
expect(crossSigningStatus.hasUserSigning).toStrictEqual(false);
});
test('can sign a message', async () => {
const m = await machine();
const signatures = await m.sign('foo');
expect(signatures.isEmpty).toStrictEqual(false);
expect(signatures.count).toStrictEqual(1);
let base64;
// `get`
{
const signature = signatures.get(user);
expect(signature.has('ed25519:foobar')).toStrictEqual(true);
const s = signature.get('ed25519:foobar');
expect(s).toBeInstanceOf(MaybeSignature);
expect(s.isValid).toStrictEqual(true);
expect(s.isInvalid).toStrictEqual(false);
expect(s.invalidSignatureSource).toBeUndefined();
base64 = s.signature.toBase64();
expect(base64).toMatch(/^[A-Za-z0-9\+/]+$/);
expect(s.signature.ed25519.toBase64()).toStrictEqual(base64);
}
// `getSignature`
{
const signature = signatures.getSignature(user, new DeviceKeyId('ed25519:foobar'));
expect(signature.toBase64()).toStrictEqual(base64);
}
// Unknown signatures.
{
expect(signatures.get(new UserId('@hello:example.org'))).toBeUndefined();
expect(signatures.getSignature(user, new DeviceKeyId('world:foobar'))).toBeUndefined();
}
});
});