Update Ed25519KeyPair representation, add key generation
This commit is contained in:
parent
ef482071f7
commit
c3f8399c26
@ -78,16 +78,13 @@ static REFERENCE_HASH_FIELDS_TO_REMOVE: &[&str] = &["age_ts", "signatures", "uns
|
||||
/// A homeserver signs JSON with a key pair:
|
||||
///
|
||||
/// ```rust
|
||||
/// const PUBLIC_KEY: &str = "XGX0JRS2Af3be3knz2fBiRbApjm2Dh61gXDJA8kcJNI";
|
||||
/// const PRIVATE_KEY: &str = "YJDBA9Xnr2sVqXD9Vj7XVUnmFZcZrlw8Md7kMW+3XA0";
|
||||
/// const PKCS8: &str = "MFMCAQEwBQYDK2VwBCIEINjozvdfbsGEt6DD+7Uf4PiJ/YvTNXV2mIPc/tA0T+6toSMDIQDdM+tpNzNWQM9NFpfgr4B9S7LHszOrVRp9NfKmeXS3aQ";
|
||||
///
|
||||
/// let public_key = base64::decode_config(&PUBLIC_KEY, base64::STANDARD_NO_PAD).unwrap();
|
||||
/// let private_key = base64::decode_config(&PRIVATE_KEY, base64::STANDARD_NO_PAD).unwrap();
|
||||
/// let document = base64::decode_config(&PKCS8, base64::STANDARD_NO_PAD).unwrap();
|
||||
///
|
||||
/// // Create an Ed25519 key pair.
|
||||
/// let key_pair = ruma_signatures::Ed25519KeyPair::new(
|
||||
/// &public_key,
|
||||
/// &private_key,
|
||||
/// &document,
|
||||
/// "1".to_string(), // The "version" of the key.
|
||||
/// ).unwrap();
|
||||
///
|
||||
@ -404,16 +401,13 @@ pub fn reference_hash(value: &Value) -> Result<String, Error> {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// const PUBLIC_KEY: &str = "XGX0JRS2Af3be3knz2fBiRbApjm2Dh61gXDJA8kcJNI";
|
||||
/// const PRIVATE_KEY: &str = "YJDBA9Xnr2sVqXD9Vj7XVUnmFZcZrlw8Md7kMW+3XA0";
|
||||
/// const PKCS8: &str = "MFMCAQEwBQYDK2VwBCIEINjozvdfbsGEt6DD+7Uf4PiJ/YvTNXV2mIPc/tA0T+6toSMDIQDdM+tpNzNWQM9NFpfgr4B9S7LHszOrVRp9NfKmeXS3aQ";
|
||||
///
|
||||
/// let public_key = base64::decode_config(&PUBLIC_KEY, base64::STANDARD_NO_PAD).unwrap();
|
||||
/// let private_key = base64::decode_config(&PRIVATE_KEY, base64::STANDARD_NO_PAD).unwrap();
|
||||
/// let document = base64::decode_config(&PKCS8, base64::STANDARD_NO_PAD).unwrap();
|
||||
///
|
||||
/// // Create an Ed25519 key pair.
|
||||
/// let key_pair = ruma_signatures::Ed25519KeyPair::new(
|
||||
/// &public_key,
|
||||
/// &private_key,
|
||||
/// &document,
|
||||
/// "1".to_string(), // The "version" of the key.
|
||||
/// ).unwrap();
|
||||
///
|
||||
|
61
src/keys.rs
61
src/keys.rs
@ -5,7 +5,7 @@ use std::{
|
||||
fmt::{Debug, Formatter, Result as FmtResult},
|
||||
};
|
||||
|
||||
use ring::signature::Ed25519KeyPair as RingEd25519KeyPair;
|
||||
use ring::signature::{Ed25519KeyPair as RingEd25519KeyPair, KeyPair as _};
|
||||
|
||||
use crate::{signatures::Signature, Algorithm, Error};
|
||||
|
||||
@ -20,13 +20,9 @@ pub trait KeyPair: Sized {
|
||||
}
|
||||
|
||||
/// An Ed25519 key pair.
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub struct Ed25519KeyPair {
|
||||
/// The public key.
|
||||
public_key: Vec<u8>,
|
||||
|
||||
/// The private key.
|
||||
private_key: Vec<u8>,
|
||||
/// Ring's Keypair type
|
||||
keypair: RingEd25519KeyPair,
|
||||
|
||||
/// The version of the key pair.
|
||||
version: String,
|
||||
@ -37,8 +33,7 @@ impl Ed25519KeyPair {
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// * public_key: The public key of the key pair.
|
||||
/// * private_key: The private key of the key pair.
|
||||
/// * document: PKCS8-formatted bytes containing the private & public keys.
|
||||
/// * version: The "version" of the key used for this signature.
|
||||
/// Versions are used as an identifier to distinguish signatures generated from different keys
|
||||
/// but using the same algorithm on the same homeserver.
|
||||
@ -47,29 +42,35 @@ impl Ed25519KeyPair {
|
||||
///
|
||||
/// Returns an error if the public and private keys provided are invalid for the implementing
|
||||
/// algorithm.
|
||||
pub fn new(public_key: &[u8], private_key: &[u8], version: String) -> Result<Self, Error> {
|
||||
if let Err(error) = RingEd25519KeyPair::from_seed_and_public_key(private_key, public_key) {
|
||||
return Err(Error::new(error.to_string()));
|
||||
}
|
||||
pub fn new(document: &[u8], version: String) -> Result<Self, Error> {
|
||||
let keypair = RingEd25519KeyPair::from_pkcs8(document)
|
||||
.map_err(|error| Error::new(error.to_string()))?;
|
||||
|
||||
Ok(Self {
|
||||
public_key: public_key.to_owned(),
|
||||
private_key: private_key.to_owned(),
|
||||
version,
|
||||
})
|
||||
Ok(Self { keypair, version })
|
||||
}
|
||||
|
||||
/// Generates a new key pair.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// Returns a Vec<u8> representing a pkcs8-encoded private/public keypair
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns an error if the generation failed.
|
||||
pub fn generate() -> Result<Vec<u8>, Error> {
|
||||
let document = RingEd25519KeyPair::generate_pkcs8(&ring::rand::SystemRandom::new())
|
||||
.map_err(|e| Error::new(e.to_string()))?;
|
||||
|
||||
Ok(document.as_ref().to_vec())
|
||||
}
|
||||
}
|
||||
|
||||
impl KeyPair for Ed25519KeyPair {
|
||||
fn sign(&self, message: &[u8]) -> Signature {
|
||||
// Okay to unwrap because we verified the input in `new`.
|
||||
let ring_key_pair =
|
||||
RingEd25519KeyPair::from_seed_and_public_key(&self.private_key, &self.public_key)
|
||||
.unwrap();
|
||||
|
||||
Signature {
|
||||
algorithm: Algorithm::Ed25519,
|
||||
signature: ring_key_pair.sign(message).as_ref().to_vec(),
|
||||
signature: self.keypair.sign(message).as_ref().to_vec(),
|
||||
version: self.version.clone(),
|
||||
}
|
||||
}
|
||||
@ -79,7 +80,7 @@ impl Debug for Ed25519KeyPair {
|
||||
fn fmt(&self, formatter: &mut Formatter<'_>) -> FmtResult {
|
||||
formatter
|
||||
.debug_struct("Ed25519KeyPair")
|
||||
.field("public_key", &self.public_key)
|
||||
.field("public_key", &self.keypair.public_key())
|
||||
.field("version", &self.version)
|
||||
.finish()
|
||||
}
|
||||
@ -94,3 +95,13 @@ pub type PublicKeyMap = HashMap<String, PublicKeySet>;
|
||||
///
|
||||
/// This is represented as a map from key ID to Base64-encoded signature.
|
||||
pub type PublicKeySet = HashMap<String, String>;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Ed25519KeyPair;
|
||||
|
||||
#[test]
|
||||
fn generate_key() {
|
||||
Ed25519KeyPair::generate().unwrap();
|
||||
}
|
||||
}
|
||||
|
72
src/lib.rs
72
src/lib.rs
@ -181,14 +181,26 @@ mod test {
|
||||
use std::collections::HashMap;
|
||||
|
||||
use base64::{decode_config, STANDARD_NO_PAD};
|
||||
use ring::signature::{Ed25519KeyPair as RingEd25519KeyPair, KeyPair as _};
|
||||
use serde_json::{from_str, json, to_string, to_value, Value};
|
||||
|
||||
use super::{
|
||||
canonical_json, hash_and_sign_event, sign_json, verify_event, verify_json, Ed25519KeyPair,
|
||||
};
|
||||
|
||||
const PUBLIC_KEY: &str = "XGX0JRS2Af3be3knz2fBiRbApjm2Dh61gXDJA8kcJNI";
|
||||
const PRIVATE_KEY: &str = "YJDBA9Xnr2sVqXD9Vj7XVUnmFZcZrlw8Md7kMW+3XA0";
|
||||
const PKCS8: &str = "MFMCAQEwBQYDK2VwBCIEINjozvdfbsGEt6DD+7Uf4PiJ/YvTNXV2mIPc/tA0T+6toSMDIQDdM+tpNzNWQM9NFpfgr4B9S7LHszOrVRp9NfKmeXS3aQ";
|
||||
|
||||
/// Convenience method for getting the public key as a string
|
||||
fn public_key_string() -> String {
|
||||
base64::encode_config(
|
||||
&RingEd25519KeyPair::from_pkcs8(
|
||||
&base64::decode_config(PKCS8, STANDARD_NO_PAD).unwrap(),
|
||||
)
|
||||
.unwrap()
|
||||
.public_key(),
|
||||
STANDARD_NO_PAD,
|
||||
)
|
||||
}
|
||||
|
||||
/// Convenience for converting a string of JSON into its canonical form.
|
||||
fn test_canonical_json(input: &str) -> String {
|
||||
@ -292,12 +304,7 @@ mod test {
|
||||
#[test]
|
||||
fn sign_empty_json() {
|
||||
let key_pair = Ed25519KeyPair::new(
|
||||
decode_config(&PUBLIC_KEY, STANDARD_NO_PAD)
|
||||
.unwrap()
|
||||
.as_slice(),
|
||||
decode_config(&PRIVATE_KEY, STANDARD_NO_PAD)
|
||||
.unwrap()
|
||||
.as_slice(),
|
||||
decode_config(&PKCS8, STANDARD_NO_PAD).unwrap().as_slice(),
|
||||
"1".to_string(),
|
||||
)
|
||||
.unwrap();
|
||||
@ -308,16 +315,16 @@ mod test {
|
||||
|
||||
assert_eq!(
|
||||
to_string(&value).unwrap(),
|
||||
r#"{"signatures":{"domain":{"ed25519:1":"K8280/U9SSy9IVtjBuVeLr+HpOB4BQFWbg+UZaADMtTdGYI7Geitb76LTrr5QV/7Xg4ahLwYGYZzuHGZKM5ZAQ"}}}"#
|
||||
r#"{"signatures":{"domain":{"ed25519:1":"lXjsnvhVlz8t3etR+6AEJ0IT70WujeHC1CFjDDsVx0xSig1Bx7lvoi1x3j/2/GPNjQM4a2gD34UqsXFluaQEBA"}}}"#
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn verify_empty_json() {
|
||||
let value = from_str(r#"{"signatures":{"domain":{"ed25519:1":"K8280/U9SSy9IVtjBuVeLr+HpOB4BQFWbg+UZaADMtTdGYI7Geitb76LTrr5QV/7Xg4ahLwYGYZzuHGZKM5ZAQ"}}}"#).unwrap();
|
||||
let value = from_str(r#"{"signatures":{"domain":{"ed25519:1":"lXjsnvhVlz8t3etR+6AEJ0IT70WujeHC1CFjDDsVx0xSig1Bx7lvoi1x3j/2/GPNjQM4a2gD34UqsXFluaQEBA"}}}"#).unwrap();
|
||||
|
||||
let mut signature_set = HashMap::new();
|
||||
signature_set.insert("ed25519:1".to_string(), PUBLIC_KEY.to_string());
|
||||
signature_set.insert("ed25519:1".to_string(), public_key_string());
|
||||
|
||||
let mut public_key_map = HashMap::new();
|
||||
public_key_map.insert("domain".to_string(), signature_set);
|
||||
@ -328,12 +335,7 @@ mod test {
|
||||
#[test]
|
||||
fn sign_minimal_json() {
|
||||
let key_pair = Ed25519KeyPair::new(
|
||||
decode_config(&PUBLIC_KEY, STANDARD_NO_PAD)
|
||||
.unwrap()
|
||||
.as_slice(),
|
||||
decode_config(&PRIVATE_KEY, STANDARD_NO_PAD)
|
||||
.unwrap()
|
||||
.as_slice(),
|
||||
decode_config(&PKCS8, STANDARD_NO_PAD).unwrap().as_slice(),
|
||||
"1".to_string(),
|
||||
)
|
||||
.unwrap();
|
||||
@ -353,7 +355,7 @@ mod test {
|
||||
|
||||
assert_eq!(
|
||||
to_string(&alpha_value).unwrap(),
|
||||
r#"{"one":1,"signatures":{"domain":{"ed25519:1":"KqmLSbO39/Bzb0QIYE82zqLwsA+PDzYIpIRA2sRQ4sL53+sN6/fpNSoqE7BP7vBZhG6kYdD13EIMJpvhJI+6Bw"}},"two":"Two"}"#
|
||||
r#"{"one":1,"signatures":{"domain":{"ed25519:1":"t6Ehmh6XTDz7qNWI0QI5tNPSliWLPQP/+Fzz3LpdCS7q1k2G2/5b5Embs2j4uG3ZeivejrzqSVoBcdocRpa+AQ"}},"two":"Two"}"#
|
||||
);
|
||||
|
||||
let mut reverse_alpha_value =
|
||||
@ -362,18 +364,18 @@ mod test {
|
||||
|
||||
assert_eq!(
|
||||
to_string(&reverse_alpha_value).unwrap(),
|
||||
r#"{"one":1,"signatures":{"domain":{"ed25519:1":"KqmLSbO39/Bzb0QIYE82zqLwsA+PDzYIpIRA2sRQ4sL53+sN6/fpNSoqE7BP7vBZhG6kYdD13EIMJpvhJI+6Bw"}},"two":"Two"}"#
|
||||
r#"{"one":1,"signatures":{"domain":{"ed25519:1":"t6Ehmh6XTDz7qNWI0QI5tNPSliWLPQP/+Fzz3LpdCS7q1k2G2/5b5Embs2j4uG3ZeivejrzqSVoBcdocRpa+AQ"}},"two":"Two"}"#
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn verify_minimal_json() {
|
||||
let value = from_str(
|
||||
r#"{"one":1,"signatures":{"domain":{"ed25519:1":"KqmLSbO39/Bzb0QIYE82zqLwsA+PDzYIpIRA2sRQ4sL53+sN6/fpNSoqE7BP7vBZhG6kYdD13EIMJpvhJI+6Bw"}},"two":"Two"}"#
|
||||
r#"{"one":1,"signatures":{"domain":{"ed25519:1":"t6Ehmh6XTDz7qNWI0QI5tNPSliWLPQP/+Fzz3LpdCS7q1k2G2/5b5Embs2j4uG3ZeivejrzqSVoBcdocRpa+AQ"}},"two":"Two"}"#
|
||||
).unwrap();
|
||||
|
||||
let mut signature_set = HashMap::new();
|
||||
signature_set.insert("ed25519:1".to_string(), PUBLIC_KEY.to_string());
|
||||
signature_set.insert("ed25519:1".to_string(), public_key_string());
|
||||
|
||||
let mut public_key_map = HashMap::new();
|
||||
public_key_map.insert("domain".to_string(), signature_set);
|
||||
@ -381,7 +383,7 @@ mod test {
|
||||
assert!(verify_json(&public_key_map, &value).is_ok());
|
||||
|
||||
let reverse_value = from_str(
|
||||
r#"{"two":"Two","signatures":{"domain":{"ed25519:1":"KqmLSbO39/Bzb0QIYE82zqLwsA+PDzYIpIRA2sRQ4sL53+sN6/fpNSoqE7BP7vBZhG6kYdD13EIMJpvhJI+6Bw"}},"one":1}"#
|
||||
r#"{"two":"Two","signatures":{"domain":{"ed25519:1":"t6Ehmh6XTDz7qNWI0QI5tNPSliWLPQP/+Fzz3LpdCS7q1k2G2/5b5Embs2j4uG3ZeivejrzqSVoBcdocRpa+AQ"}},"one":1}"#
|
||||
).unwrap();
|
||||
|
||||
assert!(verify_json(&public_key_map, &reverse_value).is_ok());
|
||||
@ -389,10 +391,10 @@ mod test {
|
||||
|
||||
#[test]
|
||||
fn fail_verify_json() {
|
||||
let value = from_str(r#"{"not":"empty","signatures":{"domain":"K8280/U9SSy9IVtjBuVeLr+HpOB4BQFWbg+UZaADMtTdGYI7Geitb76LTrr5QV/7Xg4ahLwYGYZzuHGZKM5ZAQ"}}"#).unwrap();
|
||||
let value = from_str(r#"{"not":"empty","signatures":{"domain":"lXjsnvhVlz8t3etR+6AEJ0IT70WujeHC1CFjDDsVx0xSig1Bx7lvoi1x3j/2/GPNjQM4a2gD34UqsXFluaQEBA"}}"#).unwrap();
|
||||
|
||||
let mut signature_set = HashMap::new();
|
||||
signature_set.insert("ed25519:1".to_string(), PUBLIC_KEY.to_string());
|
||||
signature_set.insert("ed25519:1".to_string(), public_key_string());
|
||||
|
||||
let mut public_key_map = HashMap::new();
|
||||
public_key_map.insert("domain".to_string(), signature_set);
|
||||
@ -403,12 +405,7 @@ mod test {
|
||||
#[test]
|
||||
fn sign_minimal_event() {
|
||||
let key_pair = Ed25519KeyPair::new(
|
||||
decode_config(&PUBLIC_KEY, STANDARD_NO_PAD)
|
||||
.unwrap()
|
||||
.as_slice(),
|
||||
decode_config(&PRIVATE_KEY, STANDARD_NO_PAD)
|
||||
.unwrap()
|
||||
.as_slice(),
|
||||
decode_config(&PKCS8, STANDARD_NO_PAD).unwrap().as_slice(),
|
||||
"1".to_string(),
|
||||
)
|
||||
.unwrap();
|
||||
@ -435,19 +432,14 @@ mod test {
|
||||
|
||||
assert_eq!(
|
||||
to_string(&value).unwrap(),
|
||||
r#"{"auth_events":[],"content":{},"depth":3,"hashes":{"sha256":"5jM4wQpv6lnBo7CLIghJuHdW+s2CMBJPUOGOC89ncos"},"origin":"domain","origin_server_ts":1000000,"prev_events":[],"room_id":"!x:domain","sender":"@a:domain","signatures":{"domain":{"ed25519:1":"KxwGjPSDEtvnFgU00fwFz+l6d2pJM6XBIaMEn81SXPTRl16AqLAYqfIReFGZlHi5KLjAWbOoMszkwsQma+lYAg"}},"type":"X","unsigned":{"age_ts":1000000}}"#
|
||||
r#"{"auth_events":[],"content":{},"depth":3,"hashes":{"sha256":"5jM4wQpv6lnBo7CLIghJuHdW+s2CMBJPUOGOC89ncos"},"origin":"domain","origin_server_ts":1000000,"prev_events":[],"room_id":"!x:domain","sender":"@a:domain","signatures":{"domain":{"ed25519:1":"PxOFMn6ORll8PFSQp0IRF6037MEZt3Mfzu/ROiT/gb/ccs1G+f6Ddoswez4KntLPBI3GKCGIkhctiK37JOy2Aw"}},"type":"X","unsigned":{"age_ts":1000000}}"#
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sign_redacted_event() {
|
||||
let key_pair = Ed25519KeyPair::new(
|
||||
decode_config(&PUBLIC_KEY, STANDARD_NO_PAD)
|
||||
.unwrap()
|
||||
.as_slice(),
|
||||
decode_config(&PRIVATE_KEY, STANDARD_NO_PAD)
|
||||
.unwrap()
|
||||
.as_slice(),
|
||||
decode_config(&PKCS8, STANDARD_NO_PAD).unwrap().as_slice(),
|
||||
"1".to_string(),
|
||||
)
|
||||
.unwrap();
|
||||
@ -473,14 +465,14 @@ mod test {
|
||||
|
||||
assert_eq!(
|
||||
to_string(&value).unwrap(),
|
||||
r#"{"content":{"body":"Here is the message content"},"event_id":"$0:domain","hashes":{"sha256":"onLKD1bGljeBWQhWZ1kaP9SorVmRQNdN5aM2JYU2n/g"},"origin":"domain","origin_server_ts":1000000,"room_id":"!r:domain","sender":"@u:domain","signatures":{"domain":{"ed25519:1":"Wm+VzmOUOz08Ds+0NTWb1d4CZrVsJSikkeRxh6aCcUwu6pNC78FunoD7KNWzqFn241eYHYMGCA5McEiVPdhzBA"}},"type":"m.room.message","unsigned":{"age_ts":1000000}}"#
|
||||
r#"{"content":{"body":"Here is the message content"},"event_id":"$0:domain","hashes":{"sha256":"onLKD1bGljeBWQhWZ1kaP9SorVmRQNdN5aM2JYU2n/g"},"origin":"domain","origin_server_ts":1000000,"room_id":"!r:domain","sender":"@u:domain","signatures":{"domain":{"ed25519:1":"D2V+qWBJssVuK/pEUJtwaYMdww2q1fP4PRCo226ChlLz8u8AWmQdLKes19NMjs/X0Hv0HIjU0c1TDKFMtGuoCA"}},"type":"m.room.message","unsigned":{"age_ts":1000000}}"#
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn verify_minimal_event() {
|
||||
let mut signature_set = HashMap::new();
|
||||
signature_set.insert("ed25519:1".to_string(), PUBLIC_KEY.to_string());
|
||||
signature_set.insert("ed25519:1".to_string(), public_key_string());
|
||||
|
||||
let mut public_key_map = HashMap::new();
|
||||
public_key_map.insert("domain".to_string(), signature_set);
|
||||
@ -500,7 +492,7 @@ mod test {
|
||||
"sender": "@a:domain",
|
||||
"signatures": {
|
||||
"domain": {
|
||||
"ed25519:1": "KxwGjPSDEtvnFgU00fwFz+l6d2pJM6XBIaMEn81SXPTRl16AqLAYqfIReFGZlHi5KLjAWbOoMszkwsQma+lYAg"
|
||||
"ed25519:1": "PxOFMn6ORll8PFSQp0IRF6037MEZt3Mfzu/ROiT/gb/ccs1G+f6Ddoswez4KntLPBI3GKCGIkhctiK37JOy2Aw"
|
||||
}
|
||||
},
|
||||
"type": "X",
|
||||
|
Loading…
x
Reference in New Issue
Block a user