Update Ed25519KeyPair representation, add key generation

This commit is contained in:
Riley 2020-04-19 12:36:05 -05:00 committed by GitHub
parent ef482071f7
commit c3f8399c26
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 74 additions and 77 deletions

View File

@ -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();
///

View File

@ -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();
}
}

View File

@ -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",