Switch to *ring* for crypto and flesh out the API.
This commit is contained in:
parent
6460312c79
commit
b080a934fb
@ -1,10 +1,3 @@
|
|||||||
before_install:
|
|
||||||
- "curl -LO https://github.com/jedisct1/libsodium/releases/download/1.0.11/libsodium-1.0.11.tar.gz"
|
|
||||||
- "tar -zxvf libsodium-1.0.11.tar.gz"
|
|
||||||
- "cd libsodium-1.0.11"
|
|
||||||
- "./configure"
|
|
||||||
- "make"
|
|
||||||
- "sudo make install"
|
|
||||||
language: "rust"
|
language: "rust"
|
||||||
notifications:
|
notifications:
|
||||||
email: false
|
email: false
|
||||||
@ -12,5 +5,3 @@ notifications:
|
|||||||
channels:
|
channels:
|
||||||
- "chat.freenode.net#ruma"
|
- "chat.freenode.net#ruma"
|
||||||
use_notice: true
|
use_notice: true
|
||||||
rust:
|
|
||||||
- "nightly"
|
|
||||||
|
13
Cargo.toml
13
Cargo.toml
@ -11,11 +11,8 @@ repository = "https://github.com/ruma/ruma-signatures"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
lazy_static = "0.2.1"
|
ring = "0.6.0-alpha1"
|
||||||
rustc-serialize = "0.3.19"
|
rustc-serialize = "0.3.22"
|
||||||
serde = "0.8.12"
|
serde = "0.8.19"
|
||||||
serde_json = "0.8.2"
|
serde_json = "0.8.4"
|
||||||
sodiumoxide = "0.0.12"
|
untrusted = "0.3.2"
|
||||||
|
|
||||||
[lib]
|
|
||||||
doctest = false
|
|
||||||
|
416
src/lib.rs
416
src/lib.rs
@ -1,27 +1,106 @@
|
|||||||
//! ruma-signatures provides functionality for creating digital signatures according to the
|
//! Crate **ruma_signatures** implements digital signatures according to the
|
||||||
//! [Matrix](https://matrix.org/) specification.
|
//! [Matrix](https://matrix.org/) specification.
|
||||||
|
//!
|
||||||
|
//! Digital signatures are used by Matrix homeservers to verify the authenticity of events in the
|
||||||
|
//! Matrix system. Each homeserver has one or more signing key pairs which it uses to sign all
|
||||||
|
//! events. Matrix clients and other Matrix homeservers can ask the homeserver for its public keys
|
||||||
|
//! and use those keys to verify the signed events.
|
||||||
|
//!
|
||||||
|
//! Each signing key pair has an identifier, which consists of the name of the digital signature
|
||||||
|
//! algorithm it uses and a "version" string, separated by a colon. The version is an arbitrary
|
||||||
|
//! identifier used to distinguish key pairs using the same algorithm from the same homeserver.
|
||||||
|
//!
|
||||||
|
//! # Signing JSON
|
||||||
|
//!
|
||||||
|
//! A homeserver signs JSON with a key pair:
|
||||||
|
//!
|
||||||
|
//! ```rust,no_run
|
||||||
|
//! # extern crate ruma_signatures;
|
||||||
|
//! # extern crate serde_json;
|
||||||
|
//! # fn main() {
|
||||||
|
//! # use ruma_signatures::KeyPair;
|
||||||
|
//! # let public_key = [0; 32];
|
||||||
|
//! # let private_key = [0; 32];
|
||||||
|
//! // Create an Ed25519 key pair.
|
||||||
|
//! let key_pair = ruma_signatures::Ed25519KeyPair::new(
|
||||||
|
//! &public_key, // &[u8]
|
||||||
|
//! &private_key, // &[u8]
|
||||||
|
//! "1".to_string(), // The "version" of the key.
|
||||||
|
//! ).unwrap();
|
||||||
|
//! let value = serde_json::from_str("{}").unwrap(); // An empty JSON object.
|
||||||
|
//! let signature = key_pair.sign(&value).unwrap(); // Creates a `Signature`.
|
||||||
|
//! # }
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! # Verifying signatures
|
||||||
|
//!
|
||||||
|
//! A client application or another homeserver can verify a signature:
|
||||||
|
//!
|
||||||
|
//! ```rust,no_run
|
||||||
|
//! # extern crate ruma_signatures;
|
||||||
|
//! # extern crate serde_json;
|
||||||
|
//! # fn main() {
|
||||||
|
//! # let public_key = [0; 32];
|
||||||
|
//! # let signature = ruma_signatures::Signature::new("ed25519:1", &[0; 32]).unwrap();
|
||||||
|
//! let value = serde_json::from_str("{}").unwrap(); // The same empty JSON object.
|
||||||
|
//! assert!(signature.verify(&public_key, &value).is_ok());
|
||||||
|
//! # }
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! # Signature sets
|
||||||
|
//!
|
||||||
|
//! Signatures that a homeserver has added to an event are stored in a JSON object under the
|
||||||
|
//! "signatures" key in the event's JSON representation:
|
||||||
|
//!
|
||||||
|
//! ```json
|
||||||
|
//! {
|
||||||
|
//! "content": {},
|
||||||
|
//! "event_type": "not.a.real.event",
|
||||||
|
//! "signatures": {
|
||||||
|
//! "example.com": {
|
||||||
|
//! "ed25519:1": "K8280/U9SSy9IVtjBuVeLr+HpOB4BQFWbg+UZaADMtTdGYI7Geitb76LTrr5QV/7Xg4ahLwYGYZzuHGZKM5ZAQ"
|
||||||
|
//! }
|
||||||
|
//! }
|
||||||
|
//! }
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! The keys inside the "signatures" object are the hostnames of homeservers that have added
|
||||||
|
//! signatures. Within each of those objects are a set of signatures, keyed by the signing key
|
||||||
|
//! pair's identifier.
|
||||||
|
//!
|
||||||
|
//! This inner object can be created by serializing a `SignatureSet`:
|
||||||
|
//!
|
||||||
|
//! ```rust,no_run
|
||||||
|
//! # extern crate ruma_signatures;
|
||||||
|
//! # extern crate serde;
|
||||||
|
//! # extern crate serde_json;
|
||||||
|
//! # fn main() {
|
||||||
|
//! # let signature = ruma_signatures::Signature::new("ed25519:1", &[0; 32]).unwrap();
|
||||||
|
//! let mut signature_set = ruma_signatures::SignatureSet::new();
|
||||||
|
//! signature_set.insert(signature);
|
||||||
|
//! let json = serde_json::to_string(&signature_set).unwrap();
|
||||||
|
//! # }
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! This code produces the object under the "example.com" key in the preceeding JSON. Similarly,
|
||||||
|
//! a `SignatureSet` can be produced by deserializing JSON that follows this form.
|
||||||
|
|
||||||
#[macro_use]
|
extern crate ring;
|
||||||
extern crate lazy_static;
|
|
||||||
extern crate rustc_serialize;
|
extern crate rustc_serialize;
|
||||||
extern crate serde;
|
extern crate serde;
|
||||||
extern crate serde_json;
|
extern crate serde_json;
|
||||||
extern crate sodiumoxide;
|
extern crate untrusted;
|
||||||
|
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::error::Error as StdError;
|
use std::error::Error as StdError;
|
||||||
use std::fmt::{Display, Formatter, Result as FmtResult};
|
use std::fmt::{Display, Formatter, Result as FmtResult};
|
||||||
|
|
||||||
|
use ring::signature::{ED25519, Ed25519KeyPair as RingEd25519KeyPair, verify};
|
||||||
use rustc_serialize::base64::{CharacterSet, Config, FromBase64, Newline, ToBase64};
|
use rustc_serialize::base64::{CharacterSet, Config, FromBase64, Newline, ToBase64};
|
||||||
use serde::{Deserialize, Deserializer, Error as SerdeError, Serialize, Serializer};
|
use serde::{Deserialize, Deserializer, Error as SerdeError, Serialize, Serializer};
|
||||||
use serde::de::{MapVisitor, Visitor};
|
use serde::de::{MapVisitor, Visitor};
|
||||||
use serde_json::{Value, to_string};
|
use serde_json::{Value, to_string};
|
||||||
use sodiumoxide::init;
|
use untrusted::Input;
|
||||||
use sodiumoxide::crypto::sign::{SecretKey, Signature as SodiumSignature, sign_detached};
|
|
||||||
|
|
||||||
lazy_static! {
|
|
||||||
static ref _LIBSODIUM_INIT: bool = init();
|
|
||||||
}
|
|
||||||
|
|
||||||
static BASE64_CONFIG: Config = Config {
|
static BASE64_CONFIG: Config = Config {
|
||||||
char_set: CharacterSet::Standard,
|
char_set: CharacterSet::Standard,
|
||||||
@ -30,12 +109,138 @@ static BASE64_CONFIG: Config = Config {
|
|||||||
line_length: None,
|
line_length: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// An error produced when signing data fails.
|
fn signable_json(value: &Value) -> Result<String, Error> {
|
||||||
|
if !value.is_object() {
|
||||||
|
return Err(Error::new("JSON value must be a JSON object"));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut owned_value = value.clone();
|
||||||
|
|
||||||
|
{
|
||||||
|
let mut hash = owned_value.as_object_mut().unwrap(); // Safe since we checked above.
|
||||||
|
hash.remove("signatures");
|
||||||
|
hash.remove("unsigned");
|
||||||
|
}
|
||||||
|
|
||||||
|
to_string(&owned_value).map_err(|error| Error::new(error.description()))
|
||||||
|
}
|
||||||
|
|
||||||
|
enum SplitError<'a> {
|
||||||
|
InvalidLength(usize),
|
||||||
|
UnknownAlgorithm(&'a str),
|
||||||
|
}
|
||||||
|
|
||||||
|
fn split_id(id: &str) -> Result<(Algorithm, String), SplitError> {
|
||||||
|
const SIGNATURE_ID_LENGTH: usize = 2;
|
||||||
|
|
||||||
|
let signature_id: Vec<&str> = id.split(':').collect();
|
||||||
|
|
||||||
|
let signature_id_length = signature_id.len();
|
||||||
|
|
||||||
|
if signature_id_length != SIGNATURE_ID_LENGTH {
|
||||||
|
return Err(SplitError::InvalidLength(signature_id_length));
|
||||||
|
}
|
||||||
|
|
||||||
|
let algorithm_input = signature_id[0];
|
||||||
|
|
||||||
|
let algorithm = match algorithm_input {
|
||||||
|
"ed25519" => Algorithm::Ed25519,
|
||||||
|
algorithm => return Err(SplitError::UnknownAlgorithm(algorithm)),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok((algorithm, signature_id[1].to_string()))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An Ed25519 key pair.
|
||||||
|
pub struct Ed25519KeyPair {
|
||||||
|
ring_key_pair: RingEd25519KeyPair,
|
||||||
|
version: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An error produced during signing or verification.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Error {
|
pub struct Error {
|
||||||
message: String,
|
message: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A cryptographic key pair for digitally signing data.
|
||||||
|
pub trait KeyPair: Sized {
|
||||||
|
/// Initializes a new key pair.
|
||||||
|
///
|
||||||
|
/// # Parameters
|
||||||
|
///
|
||||||
|
/// * public_key: The public key of the key pair.
|
||||||
|
/// * private_key: The private key of the key pair.
|
||||||
|
/// * 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.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// Returns an error if the public and private keys provided are invalid for the implementing
|
||||||
|
/// algorithm.
|
||||||
|
fn new(public_key: &[u8], private_key: &[u8], version: String) -> Result<Self, Error>;
|
||||||
|
|
||||||
|
/// Signs a JSON object.
|
||||||
|
///
|
||||||
|
/// # Parameters
|
||||||
|
///
|
||||||
|
/// * value: A JSON value to be signed according to the Matrix specification.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// Returns an error if the JSON value is not a JSON object.
|
||||||
|
fn sign(&self, value: &Value) -> Result<Signature, Error>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A single digital signature.
|
||||||
|
///
|
||||||
|
/// Signatures are originally generated from a `KeyPair`.
|
||||||
|
/// For verifying a signature, a `Signature` can be constructed from bytes using `new`.
|
||||||
|
#[derive(Debug, Eq, Hash, PartialEq)]
|
||||||
|
pub struct Signature {
|
||||||
|
algorithm: Algorithm,
|
||||||
|
signature: Vec<u8>,
|
||||||
|
version: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A set of signatures created by a single homeserver.
|
||||||
|
pub struct SignatureSet {
|
||||||
|
set: HashSet<Signature>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Serde Visitor for deserializing `SignatureSet`.
|
||||||
|
struct SignatureSetVisitor;
|
||||||
|
|
||||||
|
/// The algorithm used for signing data.
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||||
|
pub enum Algorithm {
|
||||||
|
/// The Ed25519 digital signature algorithm.
|
||||||
|
Ed25519,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl KeyPair for Ed25519KeyPair {
|
||||||
|
fn new(public_key: &[u8], private_key: &[u8], version: String) -> Result<Self, Error> {
|
||||||
|
Ok(Ed25519KeyPair {
|
||||||
|
ring_key_pair: RingEd25519KeyPair::from_bytes(
|
||||||
|
private_key,
|
||||||
|
public_key,
|
||||||
|
).map_err(|_| Error::new("invalid key pair"))?,
|
||||||
|
version: version,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sign(&self, value: &Value) -> Result<Signature, Error> {
|
||||||
|
let json = signable_json(value)?;
|
||||||
|
|
||||||
|
Ok(Signature {
|
||||||
|
algorithm: Algorithm::Ed25519,
|
||||||
|
signature: self.ring_key_pair.sign(json.as_bytes()).as_slice().to_vec(),
|
||||||
|
version: self.version.clone(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Error {
|
impl Error {
|
||||||
pub fn new<T>(message: T) -> Self where T: Into<String> {
|
pub fn new<T>(message: T) -> Self where T: Into<String> {
|
||||||
Error {
|
Error {
|
||||||
@ -56,58 +261,57 @@ impl Display for Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A single digital signature.
|
|
||||||
///
|
|
||||||
/// Generated from `SigningKey`.
|
|
||||||
#[derive(Debug, Eq, Hash, PartialEq)]
|
|
||||||
pub struct Signature {
|
|
||||||
algorithm: SigningAlgorithm,
|
|
||||||
signature: SodiumSignature,
|
|
||||||
version: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A set of signatures created by a single homeserver.
|
|
||||||
pub struct SignatureSet {
|
|
||||||
set: HashSet<Signature>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Serde Visitor for deserializing `SignatureSet`.
|
|
||||||
struct SignatureSetVisitor;
|
|
||||||
|
|
||||||
/// The algorithm used for signing.
|
|
||||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
|
||||||
pub enum SigningAlgorithm {
|
|
||||||
/// The Ed25519 digital signature algorithm.
|
|
||||||
Ed25519,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A signing key, consisting of an algorithm, a secret key, and a key version.
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct SigningKey {
|
|
||||||
algorithm: SigningAlgorithm,
|
|
||||||
key: SecretKey,
|
|
||||||
version: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Signature {
|
impl Signature {
|
||||||
|
/// Creates a signature from raw bytes.
|
||||||
|
pub fn new(id: &str, bytes: &[u8]) -> Result<Self, Error> {
|
||||||
|
let (algorithm, version) = split_id(id).map_err(|split_error| {
|
||||||
|
match split_error {
|
||||||
|
SplitError::InvalidLength(_) => Error::new("malformed signature ID"),
|
||||||
|
SplitError::UnknownAlgorithm(algorithm) => {
|
||||||
|
Error::new(format!("unknown algorithm: {}", algorithm))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(Signature {
|
||||||
|
algorithm: algorithm,
|
||||||
|
signature: bytes.to_vec(),
|
||||||
|
version: version,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/// The algorithm used to generate the signature.
|
/// The algorithm used to generate the signature.
|
||||||
pub fn algorithm(&self) -> SigningAlgorithm {
|
pub fn algorithm(&self) -> Algorithm {
|
||||||
self.algorithm
|
self.algorithm
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The raw bytes of the signature.
|
/// The raw bytes of the signature.
|
||||||
pub fn as_bytes(&self) -> &[u8] {
|
pub fn as_bytes(&self) -> &[u8] {
|
||||||
self.signature.as_ref()
|
self.signature.as_slice()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A Base64 encoding of the signature.
|
/// A Base64 encoding of the signature.
|
||||||
pub fn base64(&self) -> String {
|
pub fn base64(&self) -> String {
|
||||||
self.signature.as_ref().to_base64(BASE64_CONFIG)
|
self.signature.as_slice().to_base64(BASE64_CONFIG)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A string containing the signature algorithm and the key "version" separated by a colon.
|
/// A string containing the signature algorithm and the key "version" separated by a colon.
|
||||||
pub fn id(&self) -> String {
|
pub fn id(&self) -> String {
|
||||||
format!("ed25519:{}", self.version())
|
format!("{}:{}", self.algorithm, self.version)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Use the public key to verify the signature against the JSON object that was signed.
|
||||||
|
pub fn verify(&self, public_key: &[u8], value: &Value) -> Result<(), Error> {
|
||||||
|
match self.algorithm {
|
||||||
|
Algorithm::Ed25519 => {
|
||||||
|
verify(
|
||||||
|
&ED25519,
|
||||||
|
Input::from(public_key),
|
||||||
|
Input::from(signable_json(value)?.as_bytes()),
|
||||||
|
Input::from(self.as_bytes()),
|
||||||
|
).map_err(|_| Error::new("signature verification failed"))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The "version" of the key used for this signature.
|
/// The "version" of the key used for this signature.
|
||||||
@ -172,40 +376,25 @@ impl Visitor for SignatureSetVisitor {
|
|||||||
|
|
||||||
fn visit_map<M>(&mut self, mut visitor: M) -> Result<Self::Value, M::Error>
|
fn visit_map<M>(&mut self, mut visitor: M) -> Result<Self::Value, M::Error>
|
||||||
where M: MapVisitor {
|
where M: MapVisitor {
|
||||||
const SIGNATURE_ID_LENGTH: usize = 2;
|
|
||||||
|
|
||||||
let mut signature_set = SignatureSet::with_capacity(visitor.size_hint().0);
|
let mut signature_set = SignatureSet::with_capacity(visitor.size_hint().0);
|
||||||
|
|
||||||
while let Some((key, value)) = try!(visitor.visit::<String, String>()) {
|
while let Some((key, value)) = try!(visitor.visit::<String, String>()) {
|
||||||
let signature_id: Vec<&str> = key.split(':').collect();
|
let (algorithm, version) = split_id(&key).map_err(|split_error| {
|
||||||
|
match split_error {
|
||||||
|
SplitError::InvalidLength(length) => M::Error::invalid_length(length),
|
||||||
|
SplitError::UnknownAlgorithm(algorithm) => M::Error::invalid_value(algorithm),
|
||||||
|
}
|
||||||
|
})?;
|
||||||
|
|
||||||
let signature_id_length = signature_id.len();
|
let signature_bytes: Vec<u8> = match value.from_base64() {
|
||||||
|
|
||||||
if signature_id_length != SIGNATURE_ID_LENGTH {
|
|
||||||
return Err(M::Error::invalid_length(signature_id_length));
|
|
||||||
}
|
|
||||||
|
|
||||||
let algorithm_input = signature_id[0];
|
|
||||||
|
|
||||||
let algorithm = match algorithm_input {
|
|
||||||
"ed25519" => SigningAlgorithm::Ed25519,
|
|
||||||
_ => return Err(M::Error::invalid_value(algorithm_input)),
|
|
||||||
};
|
|
||||||
|
|
||||||
let raw_signature: Vec<u8> = match value.from_base64() {
|
|
||||||
Ok(raw) => raw,
|
Ok(raw) => raw,
|
||||||
Err(error) => return Err(M::Error::custom(error.description())),
|
Err(error) => return Err(M::Error::custom(error.description())),
|
||||||
};
|
};
|
||||||
|
|
||||||
let sodium_signature = match SodiumSignature::from_slice(&raw_signature) {
|
|
||||||
Some(s) => s,
|
|
||||||
None => return Err(M::Error::invalid_value("invalid Ed25519 signature")),
|
|
||||||
};
|
|
||||||
|
|
||||||
let signature = Signature {
|
let signature = Signature {
|
||||||
algorithm: algorithm,
|
algorithm: algorithm,
|
||||||
signature: sodium_signature,
|
signature: signature_bytes,
|
||||||
version: signature_id[1].to_string(),
|
version: version,
|
||||||
};
|
};
|
||||||
|
|
||||||
signature_set.insert(signature);
|
signature_set.insert(signature);
|
||||||
@ -217,48 +406,13 @@ impl Visitor for SignatureSetVisitor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SigningKey {
|
impl Display for Algorithm {
|
||||||
/// Initialize a new signing key.
|
fn fmt(&self, f: &mut Formatter) -> FmtResult {
|
||||||
///
|
let name = match *self {
|
||||||
/// # Parameters
|
Algorithm::Ed25519 => "ed25519",
|
||||||
///
|
|
||||||
/// * algorithm: The digital signature algorithm to use.
|
|
||||||
/// * key: A 64-byte secret key.
|
|
||||||
/// * 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.
|
|
||||||
pub fn new(algorithm: SigningAlgorithm, key: [u8; 64], version: String) -> Self {
|
|
||||||
SigningKey {
|
|
||||||
algorithm: algorithm,
|
|
||||||
key: SecretKey(key),
|
|
||||||
version: version,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sign a JSON object.
|
|
||||||
pub fn sign(&self, value: &Value) -> Result<Signature, Error> {
|
|
||||||
if !value.is_object() {
|
|
||||||
return Err(Error::new("JSON value must be a JSON object"));
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut owned_value = value.clone();
|
|
||||||
|
|
||||||
{
|
|
||||||
let mut hash = owned_value.as_object_mut().unwrap(); // Safe since we checked above.
|
|
||||||
hash.remove("signatures");
|
|
||||||
hash.remove("unsigned");
|
|
||||||
}
|
|
||||||
|
|
||||||
let json = match to_string(&owned_value) {
|
|
||||||
Ok(json) => json,
|
|
||||||
Err(error) => return Err(Error::new(error.description())),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(Signature {
|
write!(f, "{}", name)
|
||||||
algorithm: self.algorithm,
|
|
||||||
signature: sign_detached(json.as_bytes(), &self.key),
|
|
||||||
version: self.version.clone(),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -266,22 +420,34 @@ impl SigningKey {
|
|||||||
mod test {
|
mod test {
|
||||||
use rustc_serialize::base64::FromBase64;
|
use rustc_serialize::base64::FromBase64;
|
||||||
use serde_json::from_str;
|
use serde_json::from_str;
|
||||||
use sodiumoxide::crypto::sign::{SecretKey, Seed, keypair_from_seed};
|
|
||||||
|
|
||||||
use super::{SigningAlgorithm, SigningKey};
|
use super::{Ed25519KeyPair, KeyPair, Signature};
|
||||||
|
|
||||||
|
const PUBLIC_KEY: &'static str = "XGX0JRS2Af3be3knz2fBiRbApjm2Dh61gXDJA8kcJNI";
|
||||||
|
const PRIVATE_KEY: &'static str = "YJDBA9Xnr2sVqXD9Vj7XVUnmFZcZrlw8Md7kMW+3XA0";
|
||||||
|
|
||||||
|
const EMPTY_JSON_SIGNATURE: &'static str =
|
||||||
|
"K8280/U9SSy9IVtjBuVeLr+HpOB4BQFWbg+UZaADMtTdGYI7Geitb76LTrr5QV/7Xg4ahLwYGYZzuHGZKM5ZAQ";
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn empty_json() {
|
fn sign_empty_json() {
|
||||||
let seed_vec = "YJDBA9Xnr2sVqXD9Vj7XVUnmFZcZrlw8Md7kMW+3XA1".from_base64().unwrap();
|
let key_pair = Ed25519KeyPair::new(
|
||||||
let seed = Seed::from_slice(&seed_vec[..]).unwrap();
|
&PUBLIC_KEY.from_base64().unwrap(),
|
||||||
let (_pubkey, seckey) = keypair_from_seed(&seed);
|
&PRIVATE_KEY.from_base64().unwrap(),
|
||||||
let SecretKey(raw_seckey) = seckey;
|
"1".to_string(),
|
||||||
let signing_key = SigningKey::new(SigningAlgorithm::Ed25519, raw_seckey, "1".to_owned());
|
).unwrap();
|
||||||
let value = from_str("{}").unwrap();
|
let value = from_str("{}").unwrap();
|
||||||
let actual = signing_key.sign(&value).unwrap().base64();
|
let signature = key_pair.sign(&value).unwrap();
|
||||||
assert_eq!(
|
assert_eq!(signature.base64(), EMPTY_JSON_SIGNATURE);
|
||||||
actual,
|
}
|
||||||
"K8280/U9SSy9IVtjBuVeLr+HpOB4BQFWbg+UZaADMtTdGYI7Geitb76LTrr5QV/7Xg4ahLwYGYZzuHGZKM5ZAQ"
|
|
||||||
);
|
#[test]
|
||||||
|
fn verify_empty_json() {
|
||||||
|
let signature = Signature::new(
|
||||||
|
"ed25519:1",
|
||||||
|
&EMPTY_JSON_SIGNATURE.from_base64().unwrap(),
|
||||||
|
).unwrap();
|
||||||
|
let value = from_str("{}").unwrap();
|
||||||
|
assert!(signature.verify(&PUBLIC_KEY.from_base64().unwrap(), &value).is_ok());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user