Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support custom KeyProvider #366

Merged
merged 4 commits into from
May 5, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ clippy:

test:
$(Change_Work_Path) && RUSTFLAGS='-W warnings' RUST_BACKTRACE=full cargo test --all --features ws,unstable,tls,upnp
cargo clean
$(Change_Work_Path) && RUSTFLAGS='-W warnings' RUST_BACKTRACE=full cargo test --all --features ws,unstable,tls,upnp,secio-async-trait

fuzz:
cargo +nightly fuzz run secio_crypto_decrypt_cipher -- -max_total_time=60
Expand All @@ -44,7 +46,7 @@ features-check:
$(Change_Work_Path) && cargo build --features async-runtime,async-timer,unstable --no-default-features
# required wasm32-unknown-unknown target
$(Change_Work_Path) && cargo build --features wasm-timer,unstable --no-default-features --target=wasm32-unknown-unknown

$(Change_Work_Path) && cargo build --features wasm-timer,unstable,secio-async-trait --no-default-features --target=wasm32-unknown-unknown
bench_p2p:
cd bench && cargo run --release

Expand Down
6 changes: 3 additions & 3 deletions bench/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,17 +31,17 @@ enum Notify {
Message(bytes::Bytes),
}

pub fn create<F>(secio: bool, meta: ProtocolMeta, shandle: F) -> Service<F>
pub fn create<F>(secio: bool, meta: ProtocolMeta, shandle: F) -> Service<F, SecioKeyPair>
where
F: ServiceHandle + Unpin,
F: ServiceHandle + Unpin + 'static,
{
let builder = ServiceBuilder::default()
.insert_protocol(meta)
.forever(true);

if secio {
builder
.key_pair(SecioKeyPair::secp256k1_generated())
.key_provider(SecioKeyPair::secp256k1_generated())
.build(shandle)
} else {
builder.build(shandle)
Expand Down
1 change: 1 addition & 0 deletions secio/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ futures = { version = "0.3.0" }
tokio = { version = "1.0", features = ["io-util"] }
tokio-util = { version = "0.7.0", features = ["codec"] }
log = "0.4.1"
async-trait = { version = "0.1", optional = true }

molecule = "0.7.0"

Expand Down
6 changes: 4 additions & 2 deletions secio/src/codec/hmac_compat/openssl_impl.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
use openssl::{
hash::MessageDigest,
memcmp,
pkey::{PKey, Private},
sign::Signer,
};
Expand All @@ -15,6 +14,7 @@ pub struct Hmac {

impl Hmac {
/// Returns the size of the hash in bytes.
#[cfg(test)]
#[inline]
pub fn num_bytes(&self) -> usize {
self.digest.size()
Expand All @@ -32,19 +32,21 @@ impl Hmac {
}

/// Signs the data.
#[cfg(test)]
pub fn sign(&mut self, crypted_data: &[u8]) -> Vec<u8> {
let mut sign = Signer::new(self.digest, &self.key).expect("init openssl signer ctx fail");
sign.update(crypted_data).expect("openssl hmac update fail");
sign.sign_to_vec().expect("hmac sign oneshot fail")
}

/// Verifies that the data matches the expected hash.
#[cfg(test)]
pub fn verify(&mut self, crypted_data: &[u8], expected_hash: &[u8]) -> bool {
let n = self.sign(crypted_data);
if n.len() != expected_hash.len() {
return false;
}
memcmp::eq(&n, expected_hash)
openssl::memcmp::eq(&n, expected_hash)
}

/// Return a multi-step hmac context
Expand Down
3 changes: 3 additions & 0 deletions secio/src/codec/hmac_compat/ring_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ pub struct Hmac(ring::hmac::Key);

impl Hmac {
/// Returns the size of the hash in bytes.
#[cfg(test)]
#[inline]
pub fn num_bytes(&self) -> usize {
self.0.algorithm().digest_algorithm().output_len
Expand All @@ -20,11 +21,13 @@ impl Hmac {
}

/// Signs the data.
#[cfg(test)]
pub fn sign(&mut self, crypted_data: &[u8]) -> ring::hmac::Tag {
ring::hmac::sign(&self.0, crypted_data)
}

/// Verifies that the data matches the expected hash.
#[cfg(test)]
pub fn verify(&mut self, crypted_data: &[u8], expected_hash: &[u8]) -> bool {
ring::hmac::verify(&self.0, crypted_data, expected_hash).is_ok()
}
Expand Down
5 changes: 1 addition & 4 deletions secio/src/codec/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,4 @@
/// Encryption and decryption stream
pub mod secure_stream;
// hmac compatible
mod hmac_compat;

// TODO: remove this pub use for next break version
pub use hmac_compat::Hmac;
pub(crate) mod hmac_compat;
12 changes: 6 additions & 6 deletions secio/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ pub enum SecioError {
/// Crypto error
CryptoError,

/// Sign operation not supported
NotSupportKeyProvider,

/// Failed to generate ephemeral key.
EphemeralKeyGenerationFailed,

Expand All @@ -29,9 +32,6 @@ pub enum SecioError {
/// The received frame was of invalid length.
FrameTooShort,

/// The hashes of the message didn't match.
HmacNotMatching,

/// Connect yourself
ConnectSelf,

Expand All @@ -58,11 +58,11 @@ impl PartialEq for SecioError {
| (NoSupportIntersection, NoSupportIntersection)
| (NonceVerificationFailed, NonceVerificationFailed)
| (FrameTooShort, FrameTooShort)
| (HmacNotMatching, HmacNotMatching)
| (ConnectSelf, ConnectSelf)
| (HandshakeParsingFailure, HandshakeParsingFailure)
| (SignatureVerificationFailed, SignatureVerificationFailed)
| (InvalidMessage, InvalidMessage) => true,
| (InvalidMessage, InvalidMessage)
| (NotSupportKeyProvider, NotSupportKeyProvider) => true,
_ => false,
}
}
Expand Down Expand Up @@ -113,12 +113,12 @@ impl fmt::Display for SecioError {
SecioError::NoSupportIntersection => write!(f, "No Support Intersection"),
SecioError::NonceVerificationFailed => write!(f, "Nonce Verification Failed"),
SecioError::FrameTooShort => write!(f, "Frame Too Short"),
SecioError::HmacNotMatching => write!(f, "Hmac Not Matching"),
SecioError::ConnectSelf => write!(f, "Connect Self"),
SecioError::HandshakeParsingFailure => write!(f, "Handshake Parsing Failure"),
SecioError::InvalidMessage => write!(f, "Invalid Message"),
SecioError::SignatureVerificationFailed => write!(f, "Signature Verification Failed"),
SecioError::InvalidProposition(e) => write!(f, "Invalid Proposition: {}", e),
SecioError::NotSupportKeyProvider => write!(f, "Sign operation not supported"),
}
}
}
52 changes: 33 additions & 19 deletions secio/src/handshake/handshake_context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use crate::{
handshake_struct::{Propose, PublicKey},
Config,
},
support, Digest,
support, Digest, KeyProvider, Pubkey,
};

use bytes::{Bytes, BytesMut};
Expand All @@ -20,8 +20,8 @@ use std::cmp::Ordering;

// This struct contains the whole context of a handshake, and is filled progressively
// throughout the various parts of the handshake.
pub struct HandshakeContext<T> {
pub(crate) config: Config,
pub struct HandshakeContext<T, K> {
pub(crate) config: Config<K>,
pub(crate) state: T,
}

Expand All @@ -30,7 +30,7 @@ pub struct Local {
// Locally-generated random number. The array size can be changed without any repercussion.
pub(crate) nonce: [u8; 16],
// Our local public key bytes:
pub(crate) public_key: Vec<u8>,
pub(crate) public_key: PublicKey,
// Our local proposition's raw bytes:
pub(crate) proposition_bytes: Bytes,
}
Expand Down Expand Up @@ -75,23 +75,28 @@ pub struct PubEphemeral {
pub(crate) local_tmp_pub_key: Vec<u8>,
}

impl HandshakeContext<()> {
pub fn new(config: Config) -> Self {
impl<K> HandshakeContext<(), K>
where
K: KeyProvider,
{
pub fn new(config: Config<K>) -> Self {
HandshakeContext { config, state: () }
}

// Setup local proposition.
pub fn with_local(self) -> HandshakeContext<Local> {
pub fn with_local(self) -> HandshakeContext<Local, K> {
let mut nonce = [0; 16];
rand::thread_rng().fill_bytes(&mut nonce);

let public_key = self.config.key.public_key();
let public_key = PublicKey {
key: self.config.key_provider.pubkey().serialize(),
};

// Send our proposition with our nonce, public key and supported protocols.
let mut proposition = Propose::new();
proposition.rand = nonce.to_vec();
let encode_key = public_key.clone();
proposition.pubkey = encode_key.encode();
let encode_key = public_key.clone().encode();
proposition.pubkey = encode_key;

proposition.exchange = self
.config
Expand Down Expand Up @@ -120,19 +125,22 @@ impl HandshakeContext<()> {
config: self.config,
state: Local {
nonce,
public_key: public_key.inner(),
public_key,
proposition_bytes,
},
}
}
}

impl HandshakeContext<Local> {
impl<K> HandshakeContext<Local, K>
where
K: KeyProvider,
{
// Process remote proposition.
pub fn with_remote(
self,
remote_bytes: BytesMut,
) -> Result<HandshakeContext<Remote>, SecioError> {
) -> Result<HandshakeContext<Remote, K>, SecioError> {
let propose = match Propose::decode(&remote_bytes) {
Some(prop) => prop,
None => {
Expand All @@ -152,7 +160,7 @@ impl HandshakeContext<Local> {
}
};

if public_key.inner_ref() == self.state.public_key {
if public_key == self.state.public_key {
return Err(SecioError::ConnectSelf);
}

Expand All @@ -168,7 +176,7 @@ impl HandshakeContext<Local> {

let oh2 = {
let mut ctx = crate::sha256_compat::Context::new();
ctx.update(&self.state.public_key);
ctx.update(self.state.public_key.inner_ref());
ctx.update(&nonce);
ctx.finish()
};
Expand Down Expand Up @@ -252,12 +260,15 @@ impl HandshakeContext<Local> {
}
}

impl HandshakeContext<Remote> {
impl<K> HandshakeContext<Remote, K>
where
K: KeyProvider,
{
pub fn with_ephemeral(
self,
sk: crate::dh_compat::EphemeralPrivateKey,
pk: Vec<u8>,
) -> HandshakeContext<Ephemeral> {
) -> HandshakeContext<Ephemeral, K> {
HandshakeContext {
config: self.config,
state: Ephemeral {
Expand All @@ -269,11 +280,14 @@ impl HandshakeContext<Remote> {
}
}

impl HandshakeContext<Ephemeral> {
impl<K> HandshakeContext<Ephemeral, K>
where
K: KeyProvider,
{
pub fn take_private_key(
self,
) -> (
HandshakeContext<PubEphemeral>,
HandshakeContext<PubEphemeral, K>,
crate::dh_compat::EphemeralPrivateKey,
) {
let context = HandshakeContext {
Expand Down
40 changes: 13 additions & 27 deletions secio/src/handshake/handshake_struct.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,34 +119,19 @@ impl Exchange {

/// Public Key
#[derive(Clone, PartialEq, Ord, PartialOrd, Eq, Hash)]
pub enum PublicKey {
/// Secp256k1
Secp256k1(Vec<u8>),
pub struct PublicKey {
pub(crate) key: Vec<u8>,
}

impl PublicKey {
/// Get inner data
pub fn inner_ref(&self) -> &[u8] {
match self {
PublicKey::Secp256k1(ref key) => key,
}
&self.key
}

/// Get inner data
pub fn inner(self) -> Vec<u8> {
match self {
PublicKey::Secp256k1(key) => key,
}
}

/// Creates a public key directly from a slice
pub fn secp256k1_raw_key<K>(key: K) -> Result<Self, crate::error::SecioError>
where
K: AsRef<[u8]>,
{
crate::secp256k1_compat::pubkey_from_slice(key.as_ref())
.map(|key| PublicKey::Secp256k1(crate::secp256k1_compat::serialize_pubkey(&key)))
.map_err(|_| crate::error::SecioError::SecretGenerationFailed)
self.key
}

/// Encode with molecule
Expand All @@ -164,10 +149,11 @@ impl PublicKey {
pub fn decode(data: &[u8]) -> Option<Self> {
let reader = handshake_mol::PublicKeyReader::from_compatible_slice(data).ok()?;
let union = reader.to_enum();

match union {
handshake_mol::PublicKeyUnionReader::Secp256k1(reader) => {
Some(PublicKey::Secp256k1(reader.raw_data().to_owned()))
}
handshake_mol::PublicKeyUnionReader::Secp256k1(reader) => Some(PublicKey {
key: reader.raw_data().to_owned(),
}),
}
}

Expand All @@ -190,7 +176,7 @@ impl fmt::Debug for PublicKey {
#[cfg(test)]
mod tests {
use super::{Exchange, Propose, PublicKey};
use crate::SecioKeyPair;
use crate::{KeyProvider, SecioKeyPair};
use bytes::Bytes;

#[test]
Expand Down Expand Up @@ -230,13 +216,13 @@ mod tests {
let raw = privkey.public_key();
let inner = raw.inner_ref();

let other = PublicKey::secp256k1_raw_key(inner).unwrap();
assert_eq!(raw, other);
let other = <SecioKeyPair as KeyProvider>::Pubkey::from_slice(inner).unwrap();
assert_eq!(raw.inner_ref(), other.serialize());
let uncompressed = crate::secp256k1_compat::pubkey_from_slice(inner)
.map(|key| key.serialize_uncompressed().to_vec())
.unwrap();

let other_1 = PublicKey::secp256k1_raw_key(uncompressed).unwrap();
assert_eq!(raw, other_1);
let other_1 = <SecioKeyPair as KeyProvider>::Pubkey::from_slice(&uncompressed).unwrap();
assert_eq!(raw.inner_ref(), other_1.serialize());
}
}
Loading