├── .gitignore ├── .gitmodules ├── Cargo.toml ├── LICENSE ├── README.md ├── build.rs └── src ├── client.rs ├── crypto.rs ├── errors.rs ├── lib.rs ├── server.rs └── test.rs /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "shs1-c"] 2 | path = shs1-c 3 | url = https://github.com/AljoschaMeyer/shs1-c 4 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "secret_handshake" 3 | version = "5.0.0" 4 | authors = ["AljoschaMeyer "] 5 | description = "Implementation of the secret-handshake protocol." 6 | repository = "https://github.com/sunrise-choir/secret-handshake-rs" 7 | readme = "README.md" 8 | license = "LGPL-3.0" 9 | 10 | [dependencies] 11 | sodiumoxide = "0.0.16" 12 | libc = "0.2" 13 | futures-core = "0.2.0-alpha" 14 | futures-io = "0.2.0-alpha" 15 | 16 | [dev-dependencies] 17 | async-ringbuffer = "0.3.0" 18 | atm-io-utils = "0.2.0" 19 | futures = "0.2.0-alpha" 20 | 21 | [build-dependencies] 22 | cc = "1.0.0" 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. 166 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Secret-Handshake 2 | 3 | Rust wrapper around [shs1-c](https://github.com/AljoschaMeyer/shs1-c). 4 | 5 | [API documentation](https://docs.rs/secret_handshake) 6 | 7 | The `examples` folder contains executables for use with the [shs1-testsuite](https://github.com/AljoschaMeyer/shs1-testsuite). Run `cargo build --example client` or `cargo build --example server` to compile them. 8 | 9 | ### Building 10 | 11 | This module depends on [libsodium](https://github.com/jedisct1/libsodium). 12 | 13 | This also contains [shs1-c](https://github.com/AljoschaMeyer/shs1-c) as a git submodule, so be sure to perform the right git magic when cloning, updating etc. 14 | -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | extern crate cc; 2 | 3 | fn main() { 4 | cc::Build::new() 5 | .file("shs1-c/src/shs1.c") 6 | .include("shs1-c/src") 7 | .compile("libshs1.a"); 8 | } 9 | -------------------------------------------------------------------------------- /src/client.rs: -------------------------------------------------------------------------------- 1 | //! Asynchronously initiate handshakes. 2 | 3 | use std::marker::PhantomData; 4 | use std::mem::uninitialized; 5 | use std::io::ErrorKind::{WriteZero, UnexpectedEof}; 6 | 7 | use sodiumoxide::crypto::{box_, sign}; 8 | use sodiumoxide::utils::memzero; 9 | use futures_core::{Poll, Future}; 10 | use futures_core::Async::{Ready, Pending}; 11 | use futures_core::task::Context; 12 | use futures_io::{AsyncRead, AsyncWrite, Error}; 13 | 14 | use crypto::*; 15 | use errors::HandshakeError; 16 | 17 | /// Performs the client side of a handshake. 18 | pub struct ClientHandshaker<'a, S>(UnsafeClientHandshaker, PhantomData<&'a u8>); 19 | 20 | impl<'a, S: AsyncRead + AsyncWrite> ClientHandshaker<'a, S> { 21 | /// Creates a new ClientHandshaker to connect to a server with known public key 22 | /// and app key over the given `stream`. 23 | pub fn new(stream: S, 24 | network_identifier: &'a [u8; NETWORK_IDENTIFIER_BYTES], 25 | client_longterm_pk: &'a sign::PublicKey, 26 | client_longterm_sk: &'a sign::SecretKey, 27 | client_ephemeral_pk: &'a box_::PublicKey, 28 | client_ephemeral_sk: &'a box_::SecretKey, 29 | server_longterm_pk: &'a sign::PublicKey) 30 | -> ClientHandshaker<'a, S> { 31 | ClientHandshaker(UnsafeClientHandshaker::new(stream, 32 | network_identifier, 33 | client_longterm_pk, 34 | client_longterm_sk, 35 | client_ephemeral_pk, 36 | client_ephemeral_sk, 37 | server_longterm_pk), 38 | PhantomData) 39 | } 40 | } 41 | 42 | /// Future implementation to asynchronously drive a handshake. 43 | impl<'a, S: AsyncRead + AsyncWrite> Future for ClientHandshaker<'a, S> { 44 | type Item = (Outcome, S); 45 | type Error = (HandshakeError, S); 46 | 47 | fn poll(&mut self, cx: &mut Context) -> Poll { 48 | self.0.poll(cx) 49 | } 50 | } 51 | 52 | /// Performs the client side of a handshake. This copies the keys so that it isn't constrainted by 53 | /// their lifetime. 54 | pub struct OwningClientHandshaker { 55 | network_identifier: Box<[u8; NETWORK_IDENTIFIER_BYTES]>, 56 | client_longterm_pk: Box, 57 | client_longterm_sk: Box, 58 | client_ephemeral_pk: Box, 59 | client_ephemeral_sk: Box, 60 | server_longterm_pk: Box, 61 | inner: UnsafeClientHandshaker, 62 | } 63 | 64 | impl OwningClientHandshaker { 65 | /// Creates a new OwningClientHandshaker to connect to a server with known public key 66 | /// and app key over the given `stream`. 67 | pub fn new(stream: S, 68 | network_identifier: [u8; NETWORK_IDENTIFIER_BYTES], 69 | client_longterm_pk: sign::PublicKey, 70 | client_longterm_sk: sign::SecretKey, 71 | client_ephemeral_pk: box_::PublicKey, 72 | client_ephemeral_sk: box_::SecretKey, 73 | server_longterm_pk: sign::PublicKey) 74 | -> OwningClientHandshaker { 75 | let network_identifier = Box::new(network_identifier.clone()); 76 | let client_longterm_pk = Box::new(client_longterm_pk.clone()); 77 | let client_longterm_sk = Box::new(client_longterm_sk.clone()); 78 | let client_ephemeral_pk = Box::new(client_ephemeral_pk.clone()); 79 | let client_ephemeral_sk = Box::new(client_ephemeral_sk.clone()); 80 | let server_longterm_pk = Box::new(server_longterm_pk.clone()); 81 | 82 | OwningClientHandshaker { 83 | inner: UnsafeClientHandshaker::new(stream, 84 | network_identifier.as_ref(), 85 | client_longterm_pk.as_ref(), 86 | client_longterm_sk.as_ref(), 87 | client_ephemeral_pk.as_ref(), 88 | client_ephemeral_sk.as_ref(), 89 | server_longterm_pk.as_ref()), 90 | network_identifier, 91 | client_longterm_pk, 92 | client_longterm_sk, 93 | client_ephemeral_pk, 94 | client_ephemeral_sk, 95 | server_longterm_pk, 96 | } 97 | } 98 | } 99 | 100 | /// Future implementation to asynchronously drive a handshake. 101 | impl Future for OwningClientHandshaker { 102 | type Item = (Outcome, S); 103 | type Error = (HandshakeError, S); 104 | 105 | fn poll(&mut self, cx: &mut Context) -> Poll { 106 | self.inner.poll(cx) 107 | } 108 | } 109 | 110 | // Performs the client side of a handshake. 111 | struct UnsafeClientHandshaker { 112 | stream: Option, 113 | client: Client, 114 | state: State, 115 | data: [u8; MSG3_BYTES], // used to hold and cache the results of `client.create_client_challenge` and `client.create_client_auth`, and any data read from the server 116 | offset: usize, // offset into the data array at which to read/write 117 | } 118 | 119 | impl UnsafeClientHandshaker { 120 | // Creates a new UnsafeClientHandshaker to connect to a server with known public key 121 | // and app key over the given `stream`. 122 | fn new(stream: S, 123 | network_identifier: *const [u8; NETWORK_IDENTIFIER_BYTES], 124 | client_longterm_pk: *const sign::PublicKey, 125 | client_longterm_sk: *const sign::SecretKey, 126 | client_ephemeral_pk: *const box_::PublicKey, 127 | client_ephemeral_sk: *const box_::SecretKey, 128 | server_longterm_pk: *const sign::PublicKey) 129 | -> UnsafeClientHandshaker { 130 | unsafe { 131 | let mut ret = UnsafeClientHandshaker { 132 | stream: Some(stream), 133 | client: Client::new(network_identifier, 134 | &(*client_longterm_pk).0, 135 | &(*client_longterm_sk).0, 136 | &(*client_ephemeral_pk).0, 137 | &(*client_ephemeral_sk).0, 138 | &(*server_longterm_pk).0), 139 | state: WriteMsg1, 140 | data: [0; MSG3_BYTES], 141 | offset: 0, 142 | }; 143 | ret.client 144 | .create_msg1(&mut *(&mut ret.data as *mut [u8; MSG3_BYTES] as 145 | *mut [u8; MSG1_BYTES])); 146 | 147 | ret 148 | } 149 | } 150 | } 151 | 152 | // Zero buffered handshake data on dropping. 153 | impl Drop for UnsafeClientHandshaker { 154 | fn drop(&mut self) { 155 | memzero(&mut self.data); 156 | } 157 | } 158 | 159 | // Future implementation to asynchronously drive a handshake. 160 | impl Future for UnsafeClientHandshaker { 161 | type Item = (Outcome, S); 162 | type Error = (HandshakeError, S); 163 | 164 | fn poll(&mut self, cx: &mut Context) -> Poll { 165 | let mut stream = self.stream 166 | .take() 167 | .expect("Polled UnsafeClientHandshaker after completion"); 168 | 169 | match self.state { 170 | WriteMsg1 => { 171 | while self.offset < MSG1_BYTES { 172 | match stream.poll_write(cx, &self.data[self.offset..MSG1_BYTES]) { 173 | Ok(Ready(written)) => { 174 | if written == 0 { 175 | return Err((Error::new(WriteZero, "failed to write msg1").into(), 176 | stream)); 177 | } 178 | self.offset += written; 179 | } 180 | Ok(Pending) => { 181 | self.stream = Some(stream); 182 | return Ok(Pending); 183 | } 184 | Err(e) => return Err((e.into(), stream)), 185 | } 186 | } 187 | 188 | self.stream = Some(stream); 189 | self.offset = 0; 190 | self.state = FlushMsg1; 191 | 192 | return self.poll(cx); 193 | } 194 | 195 | FlushMsg1 => { 196 | match stream.poll_flush(cx) { 197 | Ok(Ready(())) => {} 198 | Ok(Pending) => { 199 | self.stream = Some(stream); 200 | return Ok(Pending); 201 | } 202 | Err(e) => return Err((e.into(), stream)), 203 | } 204 | 205 | self.stream = Some(stream); 206 | self.state = ReadMsg2; 207 | return self.poll(cx); 208 | } 209 | 210 | ReadMsg2 => { 211 | while self.offset < MSG2_BYTES { 212 | match stream.poll_read(cx, &mut self.data[self.offset..MSG2_BYTES]) { 213 | Ok(Ready(read)) => { 214 | if read == 0 { 215 | return Err((Error::new(UnexpectedEof, "failed to read msg2") 216 | .into(), 217 | stream)); 218 | } 219 | self.offset += read; 220 | } 221 | Ok(Pending) => { 222 | self.stream = Some(stream); 223 | return Ok(Pending); 224 | } 225 | Err(e) => return Err((e.into(), stream)), 226 | } 227 | } 228 | 229 | if !self.client 230 | .verify_msg2(unsafe { 231 | &*(&self.data as *const [u8; MSG3_BYTES] as 232 | *const [u8; MSG2_BYTES]) 233 | }) { 234 | return Err((HandshakeError::CryptoError, stream)); 235 | } 236 | 237 | self.stream = Some(stream); 238 | self.offset = 0; 239 | self.state = WriteMsg3; 240 | self.client.create_msg3(&mut self.data); 241 | return self.poll(cx); 242 | } 243 | 244 | WriteMsg3 => { 245 | while self.offset < MSG3_BYTES { 246 | match stream.poll_write(cx, &self.data[self.offset..MSG3_BYTES]) { 247 | Ok(Ready(written)) => { 248 | if written == 0 { 249 | return Err((Error::new(WriteZero, "failed to write msg3").into(), 250 | stream)); 251 | } 252 | self.offset += written; 253 | } 254 | Ok(Pending) => { 255 | self.stream = Some(stream); 256 | return Ok(Pending); 257 | } 258 | Err(e) => return Err((e.into(), stream)), 259 | } 260 | } 261 | 262 | self.stream = Some(stream); 263 | self.offset = 0; 264 | self.state = FlushMsg3; 265 | return self.poll(cx); 266 | } 267 | 268 | FlushMsg3 => { 269 | match stream.poll_flush(cx) { 270 | Ok(Ready(())) => {} 271 | Ok(Pending) => { 272 | self.stream = Some(stream); 273 | return Ok(Pending); 274 | } 275 | Err(e) => return Err((e.into(), stream)), 276 | } 277 | 278 | self.stream = Some(stream); 279 | self.state = ReadMsg4; 280 | return self.poll(cx); 281 | } 282 | 283 | ReadMsg4 => { 284 | while self.offset < MSG4_BYTES { 285 | match stream.poll_read(cx, &mut self.data[self.offset..MSG4_BYTES]) { 286 | Ok(Ready(read)) => { 287 | if read == 0 { 288 | return Err((Error::new(UnexpectedEof, "failed to read msg4") 289 | .into(), 290 | stream)); 291 | } 292 | self.offset += read; 293 | } 294 | Ok(Pending) => { 295 | self.stream = Some(stream); 296 | return Ok(Pending); 297 | } 298 | Err(e) => return Err((e.into(), stream)), 299 | } 300 | } 301 | 302 | if !self.client 303 | .verify_msg4(unsafe { 304 | &*(&self.data as *const [u8; MSG3_BYTES] as 305 | *const [u8; MSG4_BYTES]) 306 | }) { 307 | return Err((HandshakeError::CryptoError, stream)); 308 | } 309 | 310 | let mut outcome = unsafe { uninitialized() }; 311 | self.client.outcome(&mut outcome); 312 | return Ok(Ready((outcome, stream))); 313 | } 314 | } 315 | } 316 | } 317 | 318 | // State for the future state machine. 319 | enum State { 320 | WriteMsg1, 321 | FlushMsg1, 322 | ReadMsg2, 323 | WriteMsg3, 324 | FlushMsg3, 325 | ReadMsg4, 326 | } 327 | use client::State::*; 328 | -------------------------------------------------------------------------------- /src/crypto.rs: -------------------------------------------------------------------------------- 1 | //! Low-level bindings to shs1-c. You probably don't need to use this 2 | //! module directly. 3 | 4 | use std::mem::uninitialized; 5 | 6 | use sodiumoxide::crypto::{box_, sign, scalarmult, secretbox, auth}; 7 | use sodiumoxide::crypto::hash::sha256; 8 | use sodiumoxide::utils::memzero; 9 | 10 | /// Length of a network identifier in bytes. 11 | pub const NETWORK_IDENTIFIER_BYTES: usize = 32; 12 | 13 | /// Length of msg1 in bytes. 14 | pub const MSG1_BYTES: usize = 64; 15 | /// Length of msg2 in bytes. 16 | pub const MSG2_BYTES: usize = 64; 17 | /// Length of msg3 in bytes. 18 | pub const MSG3_BYTES: usize = 112; 19 | /// Length of msg4 in bytes. 20 | pub const MSG4_BYTES: usize = 80; 21 | 22 | /// The data resulting from a handshake: Keys and nonces suitable for encrypted 23 | /// two-way communication with the peer via box-stream-rs, and the longterm 24 | /// public key of the peer. 25 | #[repr(C)] 26 | #[derive(Debug)] 27 | pub struct Outcome { 28 | encryption_key: [u8; secretbox::KEYBYTES], 29 | encryption_nonce: [u8; secretbox::NONCEBYTES], 30 | padding_encryption: [u8; 8], 31 | decryption_key: [u8; secretbox::KEYBYTES], 32 | decryption_nonce: [u8; secretbox::NONCEBYTES], 33 | padding_decryption: [u8; 8], 34 | peer_longterm_pk: [u8; sign::PUBLICKEYBYTES], 35 | } 36 | 37 | /// Zero out all sensitive data when going out of scope 38 | impl Drop for Outcome { 39 | fn drop(&mut self) { 40 | memzero(&mut self.encryption_key); 41 | memzero(&mut self.encryption_nonce); 42 | memzero(&mut self.decryption_key); 43 | memzero(&mut self.decryption_nonce); 44 | } 45 | } 46 | 47 | impl Outcome { 48 | /// The negotiated key that should be used to encrypt messages to the peer. 49 | pub fn encryption_key(&self) -> secretbox::Key { 50 | secretbox::Key(self.encryption_key) 51 | } 52 | 53 | /// The negotiated initial nonce that should be used to encrypt messages to the peer. 54 | pub fn encryption_nonce(&self) -> secretbox::Nonce { 55 | secretbox::Nonce(self.encryption_nonce) 56 | } 57 | 58 | /// The negotiated key that should be used to decrypt messages from the peer. 59 | pub fn decryption_key(&self) -> secretbox::Key { 60 | secretbox::Key(self.decryption_key) 61 | } 62 | 63 | /// The negotiated initial nonce that should be used to decrypt messages from the peer. 64 | pub fn decryption_nonce(&self) -> secretbox::Nonce { 65 | secretbox::Nonce(self.decryption_nonce) 66 | } 67 | 68 | /// The longterm public key of the peer. 69 | pub fn peer_longterm_pk(&self) -> sign::PublicKey { 70 | sign::PublicKey(self.peer_longterm_pk) 71 | } 72 | } 73 | 74 | /// The struct used in the C code to perform the client side of a handshake. 75 | #[repr(C)] 76 | // #[derive(Debug)] 77 | pub struct Client { 78 | // inputs 79 | app: *const [u8; auth::KEYBYTES], 80 | pub_: *const [u8; sign::PUBLICKEYBYTES], 81 | sec: *const [u8; sign::SECRETKEYBYTES], 82 | eph_pub: *const [u8; box_::PUBLICKEYBYTES], 83 | eph_sec: *const [u8; box_::SECRETKEYBYTES], 84 | server_pub: *const [u8; sign::PUBLICKEYBYTES], 85 | // intermediate results 86 | shared_secret: [u8; scalarmult::GROUPELEMENTBYTES], 87 | server_lterm_shared: [u8; scalarmult::GROUPELEMENTBYTES], 88 | hello: [u8; sign::SIGNATUREBYTES + sign::PUBLICKEYBYTES], 89 | shared_hash: [u8; sha256::DIGESTBYTES], 90 | server_eph_pub: [u8; box_::PUBLICKEYBYTES], 91 | } 92 | 93 | impl Client { 94 | /// Creates and initializes a new `Client`. 95 | pub fn new(app: *const [u8; auth::KEYBYTES], 96 | pub_: *const [u8; sign::PUBLICKEYBYTES], 97 | sec: *const [u8; sign::SECRETKEYBYTES], 98 | eph_pub: *const [u8; box_::PUBLICKEYBYTES], 99 | eph_sec: *const [u8; box_::SECRETKEYBYTES], 100 | server_pub: *const [u8; sign::PUBLICKEYBYTES]) 101 | -> Client { 102 | Client { 103 | app, 104 | pub_, 105 | sec, 106 | eph_pub, 107 | eph_sec, 108 | server_pub, 109 | shared_secret: unsafe { uninitialized() }, 110 | server_lterm_shared: unsafe { uninitialized() }, 111 | hello: unsafe { uninitialized() }, 112 | shared_hash: unsafe { uninitialized() }, 113 | server_eph_pub: unsafe { uninitialized() }, 114 | } 115 | } 116 | 117 | /// Writes the client challenge into `challenge` and updates the client state. 118 | pub fn create_msg1(&mut self, challenge: &mut [u8; MSG1_BYTES]) { 119 | unsafe { shs1_create_client_challenge(challenge, self) } 120 | } 121 | 122 | /// Verifies the given server `challenge` and updates the client state. 123 | pub fn verify_msg2(&mut self, challenge: &[u8; MSG1_BYTES]) -> bool { 124 | unsafe { shs1_verify_server_challenge(challenge, self) } 125 | } 126 | 127 | /// Writes the client authentication into `auth` and updates the client state. 128 | pub fn create_msg3(&mut self, auth: &mut [u8; MSG3_BYTES]) -> i32 { 129 | unsafe { shs1_create_client_auth(auth, self) } 130 | } 131 | 132 | /// Verifies the given server `ack`knowledgement and updates the client state. 133 | pub fn verify_msg4(&mut self, ack: &[u8; MSG4_BYTES]) -> bool { 134 | unsafe { shs1_verify_server_ack(ack, self) } 135 | } 136 | 137 | /// Computes the outcome of the handshake and writes it into `outcome`. 138 | pub fn outcome(&mut self, outcome: &mut Outcome) { 139 | unsafe { shs1_client_outcome(outcome, self) } 140 | } 141 | 142 | /// Zeros out all sensitive data in the `Client`. 143 | fn clean(&mut self) { 144 | unsafe { shs1_client_clean(self) } 145 | } 146 | } 147 | 148 | /// Zero out all sensitive data when going out of scope. 149 | impl Drop for Client { 150 | fn drop(&mut self) { 151 | self.clean(); 152 | } 153 | } 154 | 155 | /// The struct used in the C code to perform the server side of a handshake. 156 | #[repr(C)] 157 | // #[derive(Debug)] 158 | pub struct Server { 159 | app: *const [u8; auth::KEYBYTES], 160 | pub_: *const [u8; sign::PUBLICKEYBYTES], 161 | sec: *const [u8; sign::SECRETKEYBYTES], 162 | eph_pub: *const [u8; box_::PUBLICKEYBYTES], 163 | eph_sec: *const [u8; box_::SECRETKEYBYTES], 164 | //intermediate results 165 | client_hello: [u8; sign::SIGNATUREBYTES + sign::PUBLICKEYBYTES], 166 | shared_hash: [u8; sha256::DIGESTBYTES], 167 | client_eph_pub: [u8; box_::PUBLICKEYBYTES], 168 | client_pub: [u8; sign::PUBLICKEYBYTES], 169 | box_sec: [u8; sha256::DIGESTBYTES], 170 | } 171 | 172 | impl Server { 173 | /// Creates and initializes a new `Server`. 174 | pub fn new(app: *const [u8; auth::KEYBYTES], 175 | pub_: *const [u8; sign::PUBLICKEYBYTES], 176 | sec: *const [u8; sign::SECRETKEYBYTES], 177 | eph_pub: *const [u8; box_::PUBLICKEYBYTES], 178 | eph_sec: *const [u8; box_::SECRETKEYBYTES]) 179 | -> Server { 180 | Server { 181 | app, 182 | pub_, 183 | sec, 184 | eph_pub, 185 | eph_sec, 186 | client_hello: unsafe { uninitialized() }, 187 | shared_hash: unsafe { uninitialized() }, 188 | client_eph_pub: unsafe { uninitialized() }, 189 | client_pub: unsafe { uninitialized() }, 190 | box_sec: unsafe { uninitialized() }, 191 | } 192 | } 193 | 194 | /// Verifies the given client `challenge` and updates the server state. 195 | pub fn verify_msg1(&mut self, challenge: &[u8; MSG1_BYTES]) -> bool { 196 | unsafe { shs1_verify_client_challenge(challenge, self) } 197 | } 198 | 199 | /// Writes the server challenge into `challenge` and updates the server state. 200 | pub fn create_msg2(&mut self, challenge: &mut [u8; MSG2_BYTES]) { 201 | unsafe { shs1_create_server_challenge(challenge, self) } 202 | } 203 | 204 | /// Verifies the given client `auth`entication and updates the server state. 205 | pub fn verify_msg3(&mut self, auth: &[u8; MSG3_BYTES]) -> bool { 206 | unsafe { shs1_verify_client_auth(auth, self) } 207 | } 208 | 209 | /// Writes the server acknowledgement into `ack` and updates the server state. 210 | pub fn create_msg4(&mut self, ack: *mut [u8; MSG4_BYTES]) { 211 | unsafe { shs1_create_server_ack(ack, self) } 212 | } 213 | 214 | /// Computes the outcome of the handshake and writes it into `outcome`. 215 | pub fn outcome(&mut self, outcome: &mut Outcome) { 216 | unsafe { shs1_server_outcome(outcome, self) } 217 | } 218 | 219 | /// Zeros out all sensitive data in the `Server`. 220 | pub fn clean(&mut self) { 221 | unsafe { shs1_server_clean(self) } 222 | } 223 | 224 | /// Returns the longterm public key of the client. This will return 225 | /// uninitialized memory if called before the server verified msg3. 226 | pub unsafe fn client_longterm_pub(&self) -> [u8; sign::PUBLICKEYBYTES] { 227 | self.client_pub 228 | } 229 | } 230 | 231 | /// Zero out all sensitive data when going out of scope. 232 | impl Drop for Server { 233 | fn drop(&mut self) { 234 | self.clean(); 235 | } 236 | } 237 | 238 | extern "C" { 239 | // client side 240 | fn shs1_create_client_challenge(challenge: *mut [u8; MSG1_BYTES], client: *mut Client); 241 | fn shs1_verify_server_challenge(challenge: *const [u8; MSG1_BYTES], 242 | client: *mut Client) 243 | -> bool; 244 | fn shs1_create_client_auth(auth: *mut [u8; MSG3_BYTES], client: *mut Client) -> i32; 245 | fn shs1_verify_server_ack(ack: *const [u8; MSG4_BYTES], client: *mut Client) -> bool; 246 | fn shs1_client_outcome(outcome: *mut Outcome, client: *mut Client); 247 | fn shs1_client_clean(client: *mut Client); 248 | // server side 249 | fn shs1_verify_client_challenge(challenge: *const [u8; MSG1_BYTES], 250 | server: *mut Server) 251 | -> bool; 252 | fn shs1_create_server_challenge(challenge: *mut [u8; MSG2_BYTES], server: *mut Server); 253 | fn shs1_verify_client_auth(auth: *const [u8; MSG3_BYTES], server: *mut Server) -> bool; 254 | fn shs1_create_server_ack(ack: *mut [u8; MSG4_BYTES], server: *mut Server); 255 | fn shs1_server_outcome(outcome: *mut Outcome, server: *mut Server); 256 | fn shs1_server_clean(server: *mut Server); 257 | } 258 | -------------------------------------------------------------------------------- /src/errors.rs: -------------------------------------------------------------------------------- 1 | //! The errors that an be emitted when performing handshakes. 2 | 3 | use std::error::Error; 4 | use std::fmt::{self, Display, Formatter}; 5 | 6 | use futures_io; 7 | 8 | /// Errors that can occur during a handshake. 9 | #[derive(Debug)] 10 | pub enum HandshakeError { 11 | /// An io error occured during the handshake. 12 | IoError(futures_io::Error), 13 | /// The peer did not provide correct authentication. 14 | /// 15 | /// This error is non-fatal, and the underyling connection should be closed when it is emitted. 16 | CryptoError, 17 | } 18 | 19 | impl Display for HandshakeError { 20 | fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> { 21 | match *self { 22 | HandshakeError::IoError(ref err) => write!(f, "Handshake error: {}", err), 23 | HandshakeError::CryptoError => write!(f, "Handshake error: crypto error"), 24 | } 25 | } 26 | } 27 | 28 | impl Error for HandshakeError { 29 | fn description(&self) -> &str { 30 | match *self { 31 | HandshakeError::IoError(ref err) => err.description(), 32 | HandshakeError::CryptoError => "the peer did not provide valid authentication", 33 | } 34 | } 35 | 36 | fn cause(&self) -> Option<&Error> { 37 | match *self { 38 | HandshakeError::IoError(ref err) => Some(err), 39 | HandshakeError::CryptoError => None, 40 | } 41 | } 42 | } 43 | 44 | impl From for HandshakeError { 45 | fn from(err: futures_io::Error) -> HandshakeError { 46 | HandshakeError::IoError(err) 47 | } 48 | } 49 | 50 | /// Errors that can occur during a filtering handshake. 51 | #[derive(Debug)] 52 | pub enum FilteringHandshakeError { 53 | /// An io error occured during the handshake. 54 | IoError(futures_io::Error), 55 | /// The filter function errored. 56 | /// 57 | /// This error is non-fatal, and the underyling connection should be closed when it is emitted. 58 | FilterError(FnErr), 59 | /// The peer did not provide correct authentication. 60 | /// 61 | /// This error is non-fatal, and the underyling connection should be closed when it is emitted. 62 | CryptoError, 63 | /// The peer was rejected by the filter function. 64 | /// 65 | /// This error is non-fatal, and the underyling connection should be closed when it is emitted. 66 | Rejected, 67 | } 68 | 69 | impl Display for FilteringHandshakeError { 70 | fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> { 71 | match *self { 72 | FilteringHandshakeError::IoError(ref err) => write!(f, "Handshake error: {}", err), 73 | FilteringHandshakeError::FilterError(ref err) => write!(f, "Handshake error: {}", err), 74 | FilteringHandshakeError::CryptoError => write!(f, "Handshake error: crypto error"), 75 | FilteringHandshakeError::Rejected => write!(f, "Handshake error: peer rejected"), 76 | } 77 | } 78 | } 79 | 80 | impl Error for FilteringHandshakeError { 81 | fn description(&self) -> &str { 82 | match *self { 83 | FilteringHandshakeError::IoError(ref err) => err.description(), 84 | FilteringHandshakeError::FilterError(ref err) => err.description(), 85 | FilteringHandshakeError::CryptoError => "the peer did not provide valid authentication", 86 | FilteringHandshakeError::Rejected => "the peer was rejected by the filter function", 87 | } 88 | } 89 | 90 | fn cause(&self) -> Option<&Error> { 91 | match *self { 92 | FilteringHandshakeError::IoError(ref err) => Some(err), 93 | FilteringHandshakeError::FilterError(ref err) => Some(err), 94 | FilteringHandshakeError::CryptoError => None, 95 | FilteringHandshakeError::Rejected => None, 96 | } 97 | } 98 | } 99 | 100 | impl From for FilteringHandshakeError { 101 | fn from(err: futures_io::Error) -> FilteringHandshakeError { 102 | FilteringHandshakeError::IoError(err) 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Implementation of the [secret-handshake](https://github.com/auditdrivencrypto/secret-handshake) 2 | //! protocol version 1. 3 | //! 4 | //! This library uses libsodium internally. In application code, call 5 | //! [`sodiumoxide::init()`](https://dnaq.github.io/sodiumoxide/sodiumoxide/fn.init.html) 6 | //! before performing any handshakes. 7 | 8 | #![deny(missing_docs)] 9 | extern crate sodiumoxide; 10 | extern crate libc; 11 | extern crate futures_core; 12 | extern crate futures_io; 13 | 14 | pub mod crypto; 15 | pub mod errors; 16 | mod client; 17 | mod server; 18 | 19 | pub use client::*; 20 | pub use server::*; 21 | pub use crypto::{Outcome, NETWORK_IDENTIFIER_BYTES}; 22 | 23 | #[cfg(test)] 24 | extern crate async_ringbuffer; 25 | #[cfg(test)] 26 | extern crate atm_io_utils; 27 | #[cfg(test)] 28 | extern crate futures; 29 | 30 | #[cfg(test)] 31 | mod test; 32 | -------------------------------------------------------------------------------- /src/server.rs: -------------------------------------------------------------------------------- 1 | //! Asynchronously accept handshakes. 2 | 3 | use std::{error, io, fmt}; 4 | use std::error::Error; 5 | use std::io::ErrorKind::{WriteZero, UnexpectedEof}; 6 | use std::marker::PhantomData; 7 | use std::mem::uninitialized; 8 | 9 | use sodiumoxide::crypto::{box_, sign}; 10 | use sodiumoxide::utils::memzero; 11 | use futures_core::{Poll, Future, Never}; 12 | use futures_core::Async::{Ready, Pending}; 13 | use futures_core::task::Context; 14 | use futures_core::future::{FutureResult, ok}; 15 | use futures_io::{AsyncRead, AsyncWrite}; 16 | 17 | use crypto::*; 18 | use errors::*; 19 | 20 | /// Performs the server side of a handshake. 21 | pub struct ServerHandshaker<'a, S>(ServerHandshakerWithFilter<'a, 22 | S, 23 | fn(&sign::PublicKey) 24 | -> FutureResult, 25 | FutureResult>); 26 | 27 | impl<'a, S: AsyncRead + AsyncWrite> ServerHandshaker<'a, S> { 28 | /// Creates a new ServerHandshakerWithFilter to accept a connection from a 29 | /// client which knows the server's public key and uses the right app key 30 | /// over the given `stream`. 31 | pub fn new(stream: S, 32 | network_identifier: &'a [u8; NETWORK_IDENTIFIER_BYTES], 33 | server_longterm_pk: &'a sign::PublicKey, 34 | server_longterm_sk: &'a sign::SecretKey, 35 | server_ephemeral_pk: &'a box_::PublicKey, 36 | server_ephemeral_sk: &'a box_::SecretKey) 37 | -> ServerHandshaker<'a, S> { 38 | ServerHandshaker(ServerHandshakerWithFilter::new(stream, 39 | const_async_true, 40 | network_identifier, 41 | &server_longterm_pk, 42 | &server_longterm_sk, 43 | &server_ephemeral_pk, 44 | &server_ephemeral_sk)) 45 | } 46 | } 47 | 48 | /// Future implementation to asynchronously drive a handshake. 49 | impl<'a, S: AsyncRead + AsyncWrite> Future for ServerHandshaker<'a, S> { 50 | type Item = (Outcome, S); 51 | type Error = (HandshakeError, S); 52 | 53 | fn poll(&mut self, cx: &mut Context) -> Poll { 54 | match self.0.poll(cx) { 55 | Ok(foo) => Ok(foo), 56 | Err((err, stream)) => { 57 | let new_err = match err { 58 | FilteringHandshakeError::IoError(io_err) => io_err.into(), 59 | FilteringHandshakeError::FilterError(_) => unreachable!(), 60 | FilteringHandshakeError::CryptoError => HandshakeError::CryptoError, 61 | FilteringHandshakeError::Rejected => unreachable!(), 62 | }; 63 | 64 | Err((new_err, stream)) 65 | } 66 | } 67 | } 68 | } 69 | 70 | /// Performs the server side of a handshake. This copies the keys so that it isn't constrainted by 71 | /// their lifetime. 72 | pub struct OwningServerHandshaker(OwningServerHandshakerWithFilter FutureResult, 76 | FutureResult>); 77 | 78 | impl OwningServerHandshaker { 79 | /// Creates a new ServerHandshakerWithFilter to accept a connection from a 80 | /// client which knows the server's public key and uses the right app key 81 | /// over the given `stream`. 82 | pub fn new(stream: S, 83 | network_identifier: [u8; NETWORK_IDENTIFIER_BYTES], 84 | server_longterm_pk: sign::PublicKey, 85 | server_longterm_sk: sign::SecretKey, 86 | server_ephemeral_pk: box_::PublicKey, 87 | server_ephemeral_sk: box_::SecretKey) 88 | -> OwningServerHandshaker { 89 | OwningServerHandshaker(OwningServerHandshakerWithFilter::new(stream, 90 | const_async_true, 91 | network_identifier, 92 | server_longterm_pk, 93 | server_longterm_sk, 94 | server_ephemeral_pk, 95 | server_ephemeral_sk)) 96 | } 97 | } 98 | 99 | /// Future implementation to asynchronously drive a handshake. 100 | impl Future for OwningServerHandshaker { 101 | type Item = (Outcome, S); 102 | type Error = (HandshakeError, S); 103 | 104 | fn poll(&mut self, cx: &mut Context) -> Poll { 105 | match self.0.poll(cx) { 106 | Ok(foo) => Ok(foo), 107 | Err((err, stream)) => { 108 | let new_err = match err { 109 | FilteringHandshakeError::IoError(io_err) => io_err.into(), 110 | FilteringHandshakeError::FilterError(_) => unreachable!(), 111 | FilteringHandshakeError::CryptoError => HandshakeError::CryptoError, 112 | FilteringHandshakeError::Rejected => unreachable!(), 113 | }; 114 | 115 | Err((new_err, stream)) 116 | } 117 | } 118 | } 119 | } 120 | 121 | fn const_async_true(_: &sign::PublicKey) -> FutureResult { 122 | ok(true) 123 | } 124 | 125 | /// Performs the server side of a handshake. Allows filtering clients based on 126 | /// their longterm public key. 127 | pub struct ServerHandshakerWithFilter<'a, S, FilterFn, AsyncBool>(UnsafeServerHandshakerWithFilter, PhantomData<&'a u8>); 128 | 129 | impl<'a, S, FilterFn, AsyncBool> ServerHandshakerWithFilter<'a, S, FilterFn, AsyncBool> 130 | where S: AsyncRead + AsyncWrite, 131 | FilterFn: FnOnce(&sign::PublicKey) -> AsyncBool, 132 | AsyncBool: Future 133 | { 134 | /// Creates a new ServerHandshakerWithFilter to accept a connection from a 135 | /// client which knows the server's public key and uses the right app key 136 | /// over the given `stream`. 137 | /// 138 | /// Once the client has revealed its longterm public key, `filter_fn` is 139 | /// invoked. If the returned `AsyncBool` resolves to `Ok(Ready(false))`, 140 | /// the handshake is aborted. 141 | pub fn new(stream: S, 142 | filter_fn: FilterFn, 143 | network_identifier: &'a [u8; NETWORK_IDENTIFIER_BYTES], 144 | server_longterm_pk: &'a sign::PublicKey, 145 | server_longterm_sk: &'a sign::SecretKey, 146 | server_ephemeral_pk: &'a box_::PublicKey, 147 | server_ephemeral_sk: &'a box_::SecretKey) 148 | -> ServerHandshakerWithFilter<'a, S, FilterFn, AsyncBool> { 149 | ServerHandshakerWithFilter(UnsafeServerHandshakerWithFilter::new(stream, 150 | filter_fn, 151 | network_identifier, 152 | server_longterm_pk, 153 | server_longterm_sk, 154 | server_ephemeral_pk, 155 | server_ephemeral_sk), 156 | PhantomData) 157 | } 158 | } 159 | 160 | /// Future implementation to asynchronously drive a handshake. 161 | impl<'a, S, FilterFn, AsyncBool> Future for ServerHandshakerWithFilter<'a, S, FilterFn, AsyncBool> 162 | where S: AsyncRead + AsyncWrite, 163 | FilterFn: FnOnce(&sign::PublicKey) -> AsyncBool, 164 | AsyncBool: Future 165 | { 166 | type Item = (Outcome, S); 167 | type Error = (FilteringHandshakeError, S); 168 | 169 | fn poll(&mut self, cx: &mut Context) -> Poll { 170 | self.0.poll(cx) 171 | } 172 | } 173 | 174 | /// Performs the server side of a handshake. Allows filtering clients based on 175 | /// their longterm public key. This copies the keys so that it isn't constrainted by 176 | /// their lifetime. 177 | pub struct OwningServerHandshakerWithFilter { 178 | network_identifier: Box<[u8; NETWORK_IDENTIFIER_BYTES]>, 179 | server_longterm_pk: Box, 180 | server_longterm_sk: Box, 181 | server_ephemeral_pk: Box, 182 | server_ephemeral_sk: Box, 183 | inner: UnsafeServerHandshakerWithFilter, 184 | } 185 | 186 | impl OwningServerHandshakerWithFilter 187 | where S: AsyncRead + AsyncWrite, 188 | FilterFn: FnOnce(&sign::PublicKey) -> AsyncBool, 189 | AsyncBool: Future 190 | { 191 | /// Creates a new OwningServerHandshakerWithFilter to accept a connection from a 192 | /// client which knows the server's public key and uses the right app key 193 | /// over the given `stream`. 194 | /// 195 | /// Once the client has revealed its longterm public key, `filter_fn` is 196 | /// invoked. If the returned `AsyncBool` resolves to `Ok(Ready(false))`, 197 | /// the handshake is aborted. 198 | pub fn new(stream: S, 199 | filter_fn: FilterFn, 200 | network_identifier: [u8; NETWORK_IDENTIFIER_BYTES], 201 | server_longterm_pk: sign::PublicKey, 202 | server_longterm_sk: sign::SecretKey, 203 | server_ephemeral_pk: box_::PublicKey, 204 | server_ephemeral_sk: box_::SecretKey) 205 | -> OwningServerHandshakerWithFilter { 206 | let network_identifier = Box::new(network_identifier.clone()); 207 | let server_longterm_pk = Box::new(server_longterm_pk.clone()); 208 | let server_longterm_sk = Box::new(server_longterm_sk.clone()); 209 | let server_ephemeral_pk = Box::new(server_ephemeral_pk.clone()); 210 | let server_ephemeral_sk = Box::new(server_ephemeral_sk.clone()); 211 | 212 | OwningServerHandshakerWithFilter { 213 | inner: UnsafeServerHandshakerWithFilter::new(stream, 214 | filter_fn, 215 | network_identifier.as_ref(), 216 | server_longterm_pk.as_ref(), 217 | server_longterm_sk.as_ref(), 218 | server_ephemeral_pk.as_ref(), 219 | server_ephemeral_sk.as_ref()), 220 | network_identifier, 221 | server_longterm_pk, 222 | server_longterm_sk, 223 | server_ephemeral_pk, 224 | server_ephemeral_sk, 225 | } 226 | } 227 | } 228 | 229 | /// Future implementation to asynchronously drive a handshake. 230 | impl Future for OwningServerHandshakerWithFilter 231 | where S: AsyncRead + AsyncWrite, 232 | FilterFn: FnOnce(&sign::PublicKey) -> AsyncBool, 233 | AsyncBool: Future 234 | { 235 | type Item = (Outcome, S); 236 | type Error = (FilteringHandshakeError, S); 237 | 238 | fn poll(&mut self, cx: &mut Context) -> Poll { 239 | self.inner.poll(cx) 240 | } 241 | } 242 | 243 | // Performs the server side of a handshake. Allows filtering clients based on 244 | // their longterm public key. 245 | struct UnsafeServerHandshakerWithFilter { 246 | stream: Option, 247 | filter: Option>, 248 | server: Server, 249 | state: State, 250 | data: [u8; MSG3_BYTES], // used to hold and cache the results of `server.create_server_challenge` and `server.create_server_ack`, and any data read from the client 251 | offset: usize, // offset into the data array at which to read/write 252 | } 253 | 254 | // Zero buffered handshake data on dropping. 255 | impl Drop for UnsafeServerHandshakerWithFilter { 256 | fn drop(&mut self) { 257 | memzero(&mut self.data); 258 | } 259 | } 260 | 261 | impl UnsafeServerHandshakerWithFilter 262 | where S: AsyncRead + AsyncWrite, 263 | FilterFn: FnOnce(&sign::PublicKey) -> AsyncBool, 264 | AsyncBool: Future 265 | { 266 | /// Creates a new ServerHandshakerWithFilter to accept a connection from a 267 | /// client which knows the server's public key and uses the right app key 268 | /// over the given `stream`. 269 | /// 270 | /// Once the client has revealed its longterm public key, `filter_fn` is 271 | /// invoked. If the returned `AsyncBool` resolves to `Ok(Ready(false))`, 272 | /// the handshake is aborted. 273 | pub fn new(stream: S, 274 | filter_fn: FilterFn, 275 | network_identifier: *const [u8; NETWORK_IDENTIFIER_BYTES], 276 | server_longterm_pk: *const sign::PublicKey, 277 | server_longterm_sk: *const sign::SecretKey, 278 | server_ephemeral_pk: *const box_::PublicKey, 279 | server_ephemeral_sk: *const box_::SecretKey) 280 | -> UnsafeServerHandshakerWithFilter { 281 | unsafe { 282 | UnsafeServerHandshakerWithFilter { 283 | stream: Some(stream), 284 | filter: Some(FilterFun(filter_fn)), 285 | server: Server::new(network_identifier, 286 | &(*server_longterm_pk).0, 287 | &(*server_longterm_sk).0, 288 | &(*server_ephemeral_pk).0, 289 | &(*server_ephemeral_sk).0), 290 | state: ReadMsg1, 291 | data: [0; MSG3_BYTES], 292 | offset: 0, 293 | } 294 | } 295 | } 296 | } 297 | 298 | /// Future implementation to asynchronously drive a handshake. 299 | impl Future for UnsafeServerHandshakerWithFilter 300 | where S: AsyncRead + AsyncWrite, 301 | FilterFn: FnOnce(&sign::PublicKey) -> AsyncBool, 302 | AsyncBool: Future 303 | { 304 | type Item = (Outcome, S); 305 | type Error = (FilteringHandshakeError, S); 306 | 307 | fn poll(&mut self, cx: &mut Context) -> Poll { 308 | let mut stream = self.stream 309 | .take() 310 | .expect("Polled ServerHandshaker after completion"); 311 | 312 | match self.state { 313 | ReadMsg1 => { 314 | while self.offset < MSG1_BYTES { 315 | match stream.poll_read(cx, &mut self.data[self.offset..MSG1_BYTES]) { 316 | Ok(Ready(read)) => { 317 | if read == 0 { 318 | return Err((io::Error::new(UnexpectedEof, "failed to read msg1") 319 | .into(), 320 | stream)); 321 | } 322 | self.offset += read; 323 | } 324 | Ok(Pending) => { 325 | self.stream = Some(stream); 326 | return Ok(Pending); 327 | } 328 | Err(e) => return Err((e.into(), stream)), 329 | } 330 | } 331 | 332 | if !self.server 333 | .verify_msg1(unsafe { 334 | &*(&self.data as *const [u8; MSG3_BYTES] as 335 | *const [u8; MSG1_BYTES]) 336 | }) { 337 | return Err((FilteringHandshakeError::CryptoError, stream)); 338 | } 339 | 340 | self.stream = Some(stream); 341 | self.offset = 0; 342 | self.state = WriteMsg2; 343 | self.server 344 | .create_msg2(unsafe { 345 | &mut *(&mut self.data as *mut [u8; MSG3_BYTES] as 346 | *mut [u8; MSG2_BYTES]) 347 | }); 348 | return self.poll(cx); 349 | } 350 | 351 | WriteMsg2 => { 352 | while self.offset < MSG2_BYTES { 353 | match stream.poll_write(cx, &self.data[self.offset..MSG2_BYTES]) { 354 | Ok(Ready(written)) => { 355 | if written == 0 { 356 | return Err((io::Error::new(WriteZero, "failed to write msg2") 357 | .into(), 358 | stream)); 359 | } 360 | self.offset += written; 361 | } 362 | Ok(Pending) => { 363 | self.stream = Some(stream); 364 | return Ok(Pending); 365 | } 366 | Err(e) => return Err((e.into(), stream)), 367 | } 368 | } 369 | 370 | self.stream = Some(stream); 371 | self.offset = 0; 372 | self.state = FlushMsg2; 373 | return self.poll(cx); 374 | } 375 | 376 | FlushMsg2 => { 377 | match stream.poll_flush(cx) { 378 | Ok(Ready(())) => {} 379 | Ok(Pending) => { 380 | self.stream = Some(stream); 381 | return Ok(Pending); 382 | } 383 | Err(e) => return Err((e.into(), stream)), 384 | } 385 | 386 | self.stream = Some(stream); 387 | self.state = ReadMsg3; 388 | return self.poll(cx); 389 | } 390 | 391 | ReadMsg3 => { 392 | while self.offset < MSG3_BYTES { 393 | match stream.poll_read(cx, &mut self.data[self.offset..MSG3_BYTES]) { 394 | Ok(Ready(read)) => { 395 | if read == 0 { 396 | return Err((io::Error::new(UnexpectedEof, "failed to read msg3") 397 | .into(), 398 | stream)); 399 | } 400 | self.offset += read; 401 | } 402 | Ok(Pending) => { 403 | self.stream = Some(stream); 404 | return Ok(Pending); 405 | } 406 | Err(e) => return Err((e.into(), stream)), 407 | } 408 | } 409 | 410 | if !self.server.verify_msg3(&self.data) { 411 | return Err((FilteringHandshakeError::CryptoError, stream)); 412 | } 413 | 414 | let filter_fn = 415 | match self.filter 416 | .take() 417 | .expect("Attempted to poll ServerHandshaker after completion") { 418 | FilterFun(f) => f, 419 | FilterFuture(_) => unreachable!(), 420 | }; 421 | 422 | self.filter = 423 | Some(FilterFuture(filter_fn(&sign::PublicKey(unsafe { 424 | self.server.client_longterm_pub() 425 | })))); 426 | 427 | self.stream = Some(stream); 428 | self.offset = 0; 429 | self.state = FilterClient; 430 | return self.poll(cx); 431 | } 432 | 433 | FilterClient => { 434 | let mut filter_future = 435 | match self.filter 436 | .take() 437 | .expect("Attempted to poll ServerHandshaker after completion") { 438 | FilterFun(_) => unreachable!(), 439 | FilterFuture(f) => f, 440 | }; 441 | 442 | match filter_future.poll(cx) { 443 | Err(err) => return Err((FilteringHandshakeError::FilterError(err), stream)), 444 | Ok(Pending) => { 445 | self.filter = Some(FilterFuture(filter_future)); 446 | self.stream = Some(stream); 447 | return Ok(Pending); 448 | } 449 | Ok(Ready(is_authorized)) => { 450 | if !is_authorized { 451 | return Err((FilteringHandshakeError::Rejected, stream)); 452 | } 453 | 454 | self.stream = Some(stream); 455 | self.state = WriteMsg4; 456 | self.server 457 | .create_msg4(unsafe { 458 | &mut *(&mut self.data as *mut [u8; MSG3_BYTES] as 459 | *mut [u8; MSG4_BYTES]) 460 | }); 461 | 462 | return self.poll(cx); 463 | } 464 | } 465 | } 466 | 467 | WriteMsg4 => { 468 | while self.offset < MSG4_BYTES { 469 | match stream.poll_write(cx, &self.data[self.offset..MSG4_BYTES]) { 470 | Ok(Ready(written)) => { 471 | if written == 0 { 472 | return Err((io::Error::new(WriteZero, "failed to write msg4") 473 | .into(), 474 | stream)); 475 | } 476 | self.offset += written; 477 | } 478 | Ok(Pending) => { 479 | self.stream = Some(stream); 480 | return Ok(Pending); 481 | } 482 | Err(e) => return Err((e.into(), stream)), 483 | } 484 | } 485 | 486 | self.stream = Some(stream); 487 | self.offset = 0; 488 | self.state = FlushMsg4; 489 | return self.poll(cx); 490 | } 491 | 492 | FlushMsg4 => { 493 | match stream.poll_flush(cx) { 494 | Ok(Ready(())) => {} 495 | Ok(Pending) => { 496 | self.stream = Some(stream); 497 | return Ok(Pending); 498 | } 499 | Err(e) => return Err((e.into(), stream)), 500 | } 501 | 502 | let mut outcome = unsafe { uninitialized() }; 503 | self.server.outcome(&mut outcome); 504 | return Ok(Ready((outcome, stream))); 505 | } 506 | } 507 | } 508 | } 509 | 510 | /// A fatal error that occured during the execution of a handshake by a 511 | /// filtering server. 512 | #[derive(Debug)] 513 | pub enum ServerHandshakeError { 514 | /// An IO error occured during reading or writing. The contained error is 515 | /// guaranteed to not have kind `WouldBlock`. 516 | IoError(io::Error), 517 | /// The filter function errored, the error is wrapped in this variant. 518 | FilterFnError(FilterErr), 519 | } 520 | 521 | impl From for ServerHandshakeError { 522 | fn from(error: io::Error) -> Self { 523 | ServerHandshakeError::IoError(error) 524 | } 525 | } 526 | 527 | impl fmt::Display for ServerHandshakeError { 528 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { 529 | try!(fmt.write_str(self.description())); 530 | if let Some(cause) = self.cause() { 531 | try!(write!(fmt, ": {}", cause)); 532 | } 533 | Ok(()) 534 | } 535 | } 536 | 537 | impl error::Error for ServerHandshakeError { 538 | fn description(&self) -> &str { 539 | match *self { 540 | ServerHandshakeError::IoError(_) => "IO error during handshake", 541 | ServerHandshakeError::FilterFnError(_) => "Error during authentication", 542 | } 543 | } 544 | 545 | fn cause(&self) -> Option<&error::Error> { 546 | match *self { 547 | ServerHandshakeError::IoError(ref err) => Some(err), 548 | ServerHandshakeError::FilterFnError(ref err) => Some(err), 549 | } 550 | } 551 | } 552 | 553 | // State for the future state machine. 554 | enum State { 555 | ReadMsg1, 556 | WriteMsg2, 557 | FlushMsg2, 558 | ReadMsg3, 559 | FilterClient, 560 | WriteMsg4, 561 | FlushMsg4, 562 | } 563 | use server::State::*; 564 | 565 | enum FilterStuff { 566 | FilterFun(FilterFn), 567 | FilterFuture(AsyncBool), 568 | } 569 | use server::FilterStuff::*; 570 | 571 | /// Reason why a filtering server might reject the client although the handshake itself 572 | /// was executed without IO errors. 573 | #[derive(Debug, PartialEq, Eq, Copy, Clone)] 574 | pub enum ServerHandshakeFailureWithFilter { 575 | /// Received invalid msg1 from the client. 576 | InvalidMsg1, 577 | /// Received invalid msg3 from the client. 578 | InvalidMsg3, 579 | /// Filtered out the client based on its longterm public key. 580 | UnauthorizedClient, 581 | } 582 | -------------------------------------------------------------------------------- /src/test.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use super::crypto::*; 3 | 4 | use sodiumoxide::crypto::{box_, secretbox, sign, auth}; 5 | use sodiumoxide::randombytes::randombytes_into; 6 | use std::io; 7 | use futures::prelude::*; 8 | use futures::future::{ok, err, FutureResult}; 9 | use futures::executor::block_on; 10 | 11 | use async_ringbuffer::*; 12 | use atm_io_utils::Duplex; 13 | 14 | static APP: [u8; auth::KEYBYTES] = [111, 97, 159, 86, 19, 13, 53, 115, 66, 209, 32, 84, 255, 140, 15 | 143, 85, 157, 74, 32, 154, 156, 90, 29, 185, 141, 19, 184, 16 | 255, 104, 107, 124, 198]; 17 | 18 | static CLIENT_PUB: sign::PublicKey = 19 | sign::PublicKey([225, 162, 73, 136, 73, 119, 94, 84, 208, 102, 233, 120, 23, 46, 225, 245, 20 | 198, 79, 176, 0, 151, 208, 70, 146, 111, 23, 94, 101, 25, 192, 30, 35]); 21 | static CLIENT_SEC: sign::SecretKey = 22 | sign::SecretKey([243, 168, 6, 50, 44, 78, 192, 183, 210, 241, 189, 36, 183, 154, 132, 119, 23 | 115, 84, 47, 151, 32, 32, 26, 237, 64, 180, 69, 20, 95, 133, 92, 176, 225, 24 | 162, 73, 136, 73, 119, 94, 84, 208, 102, 233, 120, 23, 46, 225, 245, 198, 25 | 79, 176, 0, 151, 208, 70, 146, 111, 23, 94, 101, 25, 192, 30, 35]); 26 | static CLIENT_EPH_PUB: box_::PublicKey = 27 | box_::PublicKey([79, 79, 77, 238, 254, 215, 129, 197, 235, 41, 185, 208, 47, 32, 146, 37, 28 | 255, 237, 208, 215, 182, 92, 201, 106, 85, 86, 157, 41, 53, 165, 177, 32]); 29 | static CLIENT_EPH_SEC: box_::SecretKey = 30 | box_::SecretKey([80, 169, 55, 157, 134, 142, 219, 152, 125, 240, 174, 209, 225, 109, 46, 188, 31 | 97, 224, 193, 187, 198, 58, 226, 193, 24, 235, 213, 214, 49, 55, 213, 104]); 32 | 33 | static SERVER_PUB: sign::PublicKey = 34 | sign::PublicKey([42, 190, 113, 153, 16, 248, 187, 195, 163, 201, 187, 204, 86, 238, 66, 151, 35 | 52, 115, 160, 4, 244, 1, 12, 76, 170, 129, 66, 12, 202, 54, 1, 70]); 36 | static SERVER_SEC: sign::SecretKey = 37 | sign::SecretKey([118, 98, 17, 77, 86, 116, 58, 146, 99, 84, 198, 164, 35, 220, 73, 213, 246, 38 | 224, 242, 230, 175, 116, 71, 218, 56, 37, 212, 66, 163, 14, 74, 209, 42, 39 | 190, 113, 153, 16, 248, 187, 195, 163, 201, 187, 204, 86, 238, 66, 151, 52, 40 | 115, 160, 4, 244, 1, 12, 76, 170, 129, 66, 12, 202, 54, 1, 70]); 41 | static SERVER_EPH_PUB: box_::PublicKey = 42 | box_::PublicKey([166, 12, 63, 218, 235, 136, 61, 99, 232, 142, 165, 147, 88, 93, 79, 177, 23, 43 | 148, 129, 57, 179, 24, 192, 174, 90, 62, 40, 83, 51, 9, 97, 82]); 44 | static SERVER_EPH_SEC: box_::SecretKey = 45 | box_::SecretKey([176, 248, 210, 185, 226, 76, 162, 153, 239, 144, 57, 206, 218, 97, 2, 215, 46 | 155, 5, 223, 189, 22, 28, 137, 85, 228, 233, 93, 79, 217, 203, 63, 125]); 47 | 48 | static EXP_CLIENT_ENC_KEY: secretbox::Key = 49 | secretbox::Key([162, 29, 153, 150, 123, 225, 10, 173, 175, 201, 160, 34, 190, 179, 158, 14, 50 | 176, 105, 232, 238, 97, 66, 133, 194, 250, 148, 199, 7, 34, 157, 174, 24]); 51 | static EXP_CLIENT_ENC_NONCE: secretbox::Nonce = 52 | secretbox::Nonce([44, 140, 79, 227, 23, 153, 202, 203, 81, 40, 114, 59, 56, 167, 63, 166, 53 | 201, 9, 50, 152, 0, 255, 226, 147]); 54 | static EXP_CLIENT_DEC_KEY: secretbox::Key = 55 | secretbox::Key([125, 136, 153, 7, 109, 241, 239, 84, 228, 176, 141, 23, 58, 129, 90, 228, 56 | 188, 93, 191, 224, 209, 67, 147, 187, 45, 204, 178, 17, 77, 225, 117, 98]); 57 | static EXP_CLIENT_DEC_NONCE: secretbox::Nonce = 58 | secretbox::Nonce([211, 6, 20, 155, 178, 209, 30, 107, 1, 3, 140, 242, 73, 101, 116, 234, 249, 59 | 127, 131, 227, 142, 66, 240, 195]); 60 | static EXP_SERVER_PUB: sign::PublicKey = 61 | sign::PublicKey([42, 190, 113, 153, 16, 248, 187, 195, 163, 201, 187, 204, 86, 238, 66, 151, 62 | 52, 115, 160, 4, 244, 1, 12, 76, 170, 129, 66, 12, 202, 54, 1, 70]); 63 | 64 | static EXP_SERVER_ENC_KEY: secretbox::Key = 65 | secretbox::Key([125, 136, 153, 7, 109, 241, 239, 84, 228, 176, 141, 23, 58, 129, 90, 228, 66 | 188, 93, 191, 224, 209, 67, 147, 187, 45, 204, 178, 17, 77, 225, 117, 98]); 67 | static EXP_SERVER_ENC_NONCE: secretbox::Nonce = 68 | secretbox::Nonce([211, 6, 20, 155, 178, 209, 30, 107, 1, 3, 140, 242, 73, 101, 116, 234, 249, 69 | 127, 131, 227, 142, 66, 240, 195]); 70 | static EXP_SERVER_DEC_KEY: secretbox::Key = 71 | secretbox::Key([162, 29, 153, 150, 123, 225, 10, 173, 175, 201, 160, 34, 190, 179, 158, 14, 72 | 176, 105, 232, 238, 97, 66, 133, 194, 250, 148, 199, 7, 34, 157, 174, 24]); 73 | static EXP_SERVER_DEC_NONCE: secretbox::Nonce = 74 | secretbox::Nonce([44, 140, 79, 227, 23, 153, 202, 203, 81, 40, 114, 59, 56, 167, 63, 166, 75 | 201, 9, 50, 152, 0, 255, 226, 147]); 76 | static EXP_CLIENT_PUB: sign::PublicKey = 77 | sign::PublicKey([225, 162, 73, 136, 73, 119, 94, 84, 208, 102, 233, 120, 23, 46, 225, 245, 78 | 198, 79, 176, 0, 151, 208, 70, 146, 111, 23, 94, 101, 25, 192, 30, 35]); 79 | 80 | static CLIENT_MSGS: [u8; MSG1_BYTES + MSG3_BYTES] = [ 81 | 211,6,20,155,178,209,30,107,1,3,140,242,73,101,116,234,249,127,131,227,142,66,240,195,13,50,38,96,7,208,124,180,79,79,77,238,254,215,129,197,235,41,185,208,47,32,146,37,255,237,208,215,182,92,201,106,85,86,157,41,53,165,177,32, // end msg1 82 | 80,34,24,195,46,211,235,66,91,89,65,98,137,26,86,197,32,4,153,142,160,18,56,180,12,171,127,38,44,53,74,64,55,188,22,25,161,25,7,243,200,196,145,249,207,211,88,178,0,206,173,234,188,20,251,240,199,169,94,180,212,32,150,226,138,44,141,235,33,152,91,215,31,126,48,48,220,239,97,225,103,79,190,56,227,103,142,195,124,10,21,76,66,11,194,11,220,15,163,66,138,232,228,12,130,172,4,137,52,159,64,98 // end msg3 83 | ]; 84 | 85 | static SERVER_MSGS: [u8; MSG2_BYTES + MSG4_BYTES] = [ 86 | 44,140,79,227,23,153,202,203,81,40,114,59,56,167,63,166,201,9,50,152,0,255,226,147,22,43,84,99,107,198,198,219,166,12,63,218,235,136,61,99,232,142,165,147,88,93,79,177,23,148,129,57,179,24,192,174,90,62,40,83,51,9,97,82, // end msg2 87 | 72,114,92,105,109,48,17,14,25,150,242,50,148,70,49,25,222,254,255,124,194,144,84,114,190,148,252,189,159,132,157,173,92,14,247,198,87,232,141,83,84,79,226,43,194,95,14,8,138,233,96,40,126,153,205,36,95,203,200,202,221,118,126,99,47,216,209,219,3,133,240,216,166,182,182,226,215,116,177,66 // end msg4 88 | ]; 89 | 90 | #[test] 91 | // A client and a server can perform a handshake. 92 | fn success() { 93 | let (writer_a, reader_a) = ring_buffer(2); 94 | let (writer_b, reader_b) = ring_buffer(2); 95 | 96 | let client_duplex = Duplex::new(reader_a, writer_b); 97 | let server_duplex = Duplex::new(reader_b, writer_a); 98 | 99 | let (client_longterm_pk, client_longterm_sk) = sign::gen_keypair(); 100 | let (client_ephemeral_pk, client_ephemeral_sk) = box_::gen_keypair(); 101 | let (server_longterm_pk, server_longterm_sk) = sign::gen_keypair(); 102 | let (server_ephemeral_pk, server_ephemeral_sk) = box_::gen_keypair(); 103 | 104 | let client = ClientHandshaker::new(client_duplex, 105 | &APP, 106 | &client_longterm_pk, 107 | &client_longterm_sk, 108 | &client_ephemeral_pk, 109 | &client_ephemeral_sk, 110 | &server_longterm_pk); 111 | 112 | let server = ServerHandshaker::new(server_duplex, 113 | &APP, 114 | &server_longterm_pk, 115 | &server_longterm_sk, 116 | &server_ephemeral_pk, 117 | &server_ephemeral_sk); 118 | 119 | let ((client_outcome, _), (server_outcome, _)) = block_on(client.join(server)).ok().unwrap(); 120 | 121 | assert_eq!(client_outcome.encryption_key(), 122 | server_outcome.decryption_key()); 123 | assert_eq!(client_outcome.encryption_nonce(), 124 | server_outcome.decryption_nonce()); 125 | assert_eq!(client_outcome.decryption_key(), 126 | server_outcome.encryption_key()); 127 | assert_eq!(client_outcome.decryption_nonce(), 128 | server_outcome.encryption_nonce()); 129 | 130 | assert_eq!(client_outcome.peer_longterm_pk(), server_longterm_pk); 131 | assert_eq!(server_outcome.peer_longterm_pk(), client_longterm_pk); 132 | } 133 | // 134 | // // A client handles partial reads/writes and WouldBlock errors on the underlying stream. 135 | // quickcheck! { 136 | // fn test_client_success_randomized_async(write_ops: PartialWithErrors, read_ops: PartialWithErrors) -> bool { 137 | // let mut stream = MockDuplex::new(); 138 | // stream.add_read_data(&SERVER_MSGS[..]); 139 | // let stream = PartialAsyncWrite::new(stream, write_ops); 140 | // let stream = PartialAsyncRead::new(stream, read_ops); 141 | // 142 | // let client = ClientHandshaker::new(stream, 143 | // &APP, 144 | // &CLIENT_PUB, 145 | // &CLIENT_SEC, 146 | // &CLIENT_EPH_PUB, 147 | // &CLIENT_EPH_SEC, 148 | // &SERVER_PUB); 149 | // 150 | // let outcome = client.wait().unwrap().0.unwrap(); 151 | // assert_eq!(outcome.encryption_key(), EXP_CLIENT_ENC_KEY); 152 | // assert_eq!(outcome.encryption_nonce(), EXP_CLIENT_ENC_NONCE); 153 | // assert_eq!(outcome.decryption_key(), EXP_CLIENT_DEC_KEY); 154 | // assert_eq!(outcome.decryption_nonce(), EXP_CLIENT_DEC_NONCE); 155 | // assert_eq!(outcome.peer_longterm_pk(), EXP_SERVER_PUB); 156 | // return true; 157 | // } 158 | // } 159 | // 160 | // #[test] 161 | // // A client propagates io errors in the handshake. 162 | // fn test_client_io_error() { 163 | // let mut stream = MockDuplex::new(); 164 | // stream.add_read_data(&SERVER_MSGS[..]); 165 | // let read_ops = vec![PartialOp::Unlimited, 166 | // PartialOp::Err(io::ErrorKind::NotFound)]; 167 | // let stream = PartialAsyncWrite::new(stream, vec![]); 168 | // let stream = PartialAsyncRead::new(stream, read_ops); 169 | // 170 | // let client = ClientHandshaker::new(stream, 171 | // &APP, 172 | // &CLIENT_PUB, 173 | // &CLIENT_SEC, 174 | // &CLIENT_EPH_PUB, 175 | // &CLIENT_EPH_SEC, 176 | // &SERVER_PUB); 177 | // 178 | // assert_eq!(client.wait().unwrap_err().0.kind(), io::ErrorKind::NotFound); 179 | // } 180 | // 181 | // #[test] 182 | // // A client errors WriteZero if writing msg1 to the underlying stream returns Ok(0). 183 | // fn test_client_write0_msg1() { 184 | // let mut stream = MockDuplex::new(); 185 | // stream.add_read_data(&SERVER_MSGS[..]); 186 | // let write_ops = vec![PartialOp::Limited(0)]; 187 | // let stream = PartialAsyncWrite::new(stream, write_ops); 188 | // let stream = PartialAsyncRead::new(stream, vec![]); 189 | // 190 | // let client = ClientHandshaker::new(stream, 191 | // &APP, 192 | // &CLIENT_PUB, 193 | // &CLIENT_SEC, 194 | // &CLIENT_EPH_PUB, 195 | // &CLIENT_EPH_SEC, 196 | // &SERVER_PUB); 197 | // 198 | // assert_eq!(client.wait().unwrap_err().0.kind(), 199 | // io::ErrorKind::WriteZero); 200 | // } 201 | // 202 | // #[test] 203 | // // A client errors UnexpectedEof if reading msg2 from the underlying stream returns Ok(0). 204 | // fn test_client_read0_msg2() { 205 | // let mut stream = MockDuplex::new(); 206 | // stream.add_read_data(&SERVER_MSGS[..]); 207 | // let read_ops = vec![PartialOp::Limited(0)]; 208 | // let stream = PartialAsyncWrite::new(stream, vec![]); 209 | // let stream = PartialAsyncRead::new(stream, read_ops); 210 | // 211 | // let client = ClientHandshaker::new(stream, 212 | // &APP, 213 | // &CLIENT_PUB, 214 | // &CLIENT_SEC, 215 | // &CLIENT_EPH_PUB, 216 | // &CLIENT_EPH_SEC, 217 | // &SERVER_PUB); 218 | // 219 | // assert_eq!(client.wait().unwrap_err().0.kind(), 220 | // io::ErrorKind::UnexpectedEof); 221 | // } 222 | // 223 | // #[test] 224 | // // A client errors WriteZero if writing msg3 to the underlying stream returns Ok(0). 225 | // fn test_client_write0_msg3() { 226 | // let mut stream = MockDuplex::new(); 227 | // stream.add_read_data(&SERVER_MSGS[..]); 228 | // let write_ops = vec![PartialOp::Unlimited, 229 | // PartialOp::Limited(8), 230 | // PartialOp::Limited(0)]; 231 | // let stream = PartialAsyncWrite::new(stream, write_ops); 232 | // let stream = PartialAsyncRead::new(stream, vec![]); 233 | // 234 | // let client = ClientHandshaker::new(stream, 235 | // &APP, 236 | // &CLIENT_PUB, 237 | // &CLIENT_SEC, 238 | // &CLIENT_EPH_PUB, 239 | // &CLIENT_EPH_SEC, 240 | // &SERVER_PUB); 241 | // 242 | // assert_eq!(client.wait().unwrap_err().0.kind(), 243 | // io::ErrorKind::WriteZero); 244 | // } 245 | // 246 | // #[test] 247 | // // A client errors UnexpectedEof if reading msg4 from the underlying stream returns Ok(0). 248 | // fn test_client_read0_msg4() { 249 | // let mut stream = MockDuplex::new(); 250 | // stream.add_read_data(&SERVER_MSGS[..]); 251 | // let read_ops = vec![PartialOp::Unlimited, 252 | // PartialOp::Limited(8), 253 | // PartialOp::Limited(0)]; 254 | // let stream = PartialAsyncWrite::new(stream, vec![]); 255 | // let stream = PartialAsyncRead::new(stream, read_ops); 256 | // 257 | // let client = ClientHandshaker::new(stream, 258 | // &APP, 259 | // &CLIENT_PUB, 260 | // &CLIENT_SEC, 261 | // &CLIENT_EPH_PUB, 262 | // &CLIENT_EPH_SEC, 263 | // &SERVER_PUB); 264 | // 265 | // assert_eq!(client.wait().unwrap_err().0.kind(), 266 | // io::ErrorKind::UnexpectedEof); 267 | // } 268 | // 269 | // // A server handles partial reads/writes and WouldBlock errors on the underlying stream. 270 | // quickcheck! { 271 | // fn test_server_success_randomized_async(write_ops: PartialWithErrors, read_ops: PartialWithErrors) -> bool { 272 | // let mut stream = MockDuplex::new(); 273 | // stream.add_read_data(&CLIENT_MSGS[..]); 274 | // let stream = PartialAsyncWrite::new(stream, write_ops); 275 | // let stream = PartialAsyncRead::new(stream, read_ops); 276 | // 277 | // let server = ServerHandshaker::new(stream, 278 | // &APP, 279 | // &SERVER_PUB, 280 | // &SERVER_SEC, 281 | // &SERVER_EPH_PUB, 282 | // &SERVER_EPH_SEC); 283 | // 284 | // let outcome = server.wait().unwrap().0.unwrap(); 285 | // assert_eq!(outcome.encryption_key(), EXP_SERVER_ENC_KEY); 286 | // assert_eq!(outcome.encryption_nonce(), EXP_SERVER_ENC_NONCE); 287 | // assert_eq!(outcome.decryption_key(), EXP_SERVER_DEC_KEY); 288 | // assert_eq!(outcome.decryption_nonce(), EXP_SERVER_DEC_NONCE); 289 | // assert_eq!(outcome.peer_longterm_pk(), EXP_CLIENT_PUB); 290 | // return true; 291 | // } 292 | // } 293 | // 294 | // #[test] 295 | // // A server propagates io errors in the handshake. 296 | // fn test_server_io_error() { 297 | // let mut stream = MockDuplex::new(); 298 | // stream.add_read_data(&CLIENT_MSGS[..]); 299 | // let read_ops = vec![PartialOp::Unlimited, 300 | // PartialOp::Err(io::ErrorKind::NotFound)]; 301 | // let stream = PartialAsyncWrite::new(stream, vec![]); 302 | // let stream = PartialAsyncRead::new(stream, read_ops); 303 | // 304 | // let server = ServerHandshaker::new(stream, 305 | // &APP, 306 | // &SERVER_PUB, 307 | // &SERVER_SEC, 308 | // &SERVER_EPH_PUB, 309 | // &SERVER_EPH_SEC); 310 | // 311 | // assert_eq!(server.wait().unwrap_err().0.kind(), io::ErrorKind::NotFound); 312 | // } 313 | // 314 | // #[test] 315 | // // A server errors UnexpectedEof if reading msg1 from the underlying stream returns Ok(0). 316 | // fn test_server_read0_msg1() { 317 | // let mut stream = MockDuplex::new(); 318 | // stream.add_read_data(&CLIENT_MSGS[..]); 319 | // let read_ops = vec![PartialOp::Limited(0)]; 320 | // let stream = PartialAsyncWrite::new(stream, vec![]); 321 | // let stream = PartialAsyncRead::new(stream, read_ops); 322 | // 323 | // let server = ServerHandshaker::new(stream, 324 | // &APP, 325 | // &SERVER_PUB, 326 | // &SERVER_SEC, 327 | // &SERVER_EPH_PUB, 328 | // &SERVER_EPH_SEC); 329 | // 330 | // assert_eq!(server.wait().unwrap_err().0.kind(), 331 | // io::ErrorKind::UnexpectedEof); 332 | // } 333 | // 334 | // #[test] 335 | // // A server errors WriteZero if writing msg2 to the underlying stream returns Ok(0). 336 | // fn test_server_write0_msg2() { 337 | // let mut stream = MockDuplex::new(); 338 | // stream.add_read_data(&CLIENT_MSGS[..]); 339 | // let write_ops = vec![PartialOp::Limited(0)]; 340 | // let stream = PartialAsyncWrite::new(stream, write_ops); 341 | // let stream = PartialAsyncRead::new(stream, vec![]); 342 | // 343 | // let server = ServerHandshaker::new(stream, 344 | // &APP, 345 | // &SERVER_PUB, 346 | // &SERVER_SEC, 347 | // &SERVER_EPH_PUB, 348 | // &SERVER_EPH_SEC); 349 | // 350 | // assert_eq!(server.wait().unwrap_err().0.kind(), 351 | // io::ErrorKind::WriteZero); 352 | // } 353 | // 354 | // #[test] 355 | // // A server errors UnexpectedEof if reading msg3 from the underlying stream returns Ok(0). 356 | // fn test_server_read0_msg3() { 357 | // let mut stream = MockDuplex::new(); 358 | // stream.add_read_data(&CLIENT_MSGS[..]); 359 | // let read_ops = vec![PartialOp::Unlimited, 360 | // PartialOp::Limited(8), 361 | // PartialOp::Limited(0)]; 362 | // let stream = PartialAsyncWrite::new(stream, vec![]); 363 | // let stream = PartialAsyncRead::new(stream, read_ops); 364 | // 365 | // let server = ServerHandshaker::new(stream, 366 | // &APP, 367 | // &SERVER_PUB, 368 | // &SERVER_SEC, 369 | // &SERVER_EPH_PUB, 370 | // &SERVER_EPH_SEC); 371 | // 372 | // assert_eq!(server.wait().unwrap_err().0.kind(), 373 | // io::ErrorKind::UnexpectedEof); 374 | // } 375 | // 376 | // #[test] 377 | // // A server errors WriteZero if writing msg4 to the underlying stream returns Ok(0). 378 | // fn test_server_write0_msg4() { 379 | // let mut stream = MockDuplex::new(); 380 | // stream.add_read_data(&CLIENT_MSGS[..]); 381 | // let write_ops = vec![PartialOp::Unlimited, 382 | // PartialOp::Limited(8), 383 | // PartialOp::Limited(0)]; 384 | // let stream = PartialAsyncWrite::new(stream, write_ops); 385 | // let stream = PartialAsyncRead::new(stream, vec![]); 386 | // 387 | // let server = ServerHandshaker::new(stream, 388 | // &APP, 389 | // &SERVER_PUB, 390 | // &SERVER_SEC, 391 | // &SERVER_EPH_PUB, 392 | // &SERVER_EPH_SEC); 393 | // 394 | // assert_eq!(server.wait().unwrap_err().0.kind(), 395 | // io::ErrorKind::WriteZero); 396 | // } 397 | // 398 | // fn const_async_true(_: &sign::PublicKey) -> FutureResult { 399 | // ok(true) 400 | // } 401 | // 402 | // #[test] 403 | // // A filtering server accepts a client if the filter function returns true. 404 | // fn test_filter_server_accept() { 405 | // let mut stream = MockDuplex::new(); 406 | // stream.add_read_data(&CLIENT_MSGS[..]); 407 | // 408 | // let server = ServerHandshakerWithFilter::new(stream, 409 | // const_async_true, 410 | // &APP, 411 | // &SERVER_PUB, 412 | // &SERVER_SEC, 413 | // &SERVER_EPH_PUB, 414 | // &SERVER_EPH_SEC); 415 | // 416 | // let outcome = server.wait().unwrap().0.unwrap(); 417 | // assert_eq!(outcome.encryption_key(), EXP_SERVER_ENC_KEY); 418 | // assert_eq!(outcome.encryption_nonce(), EXP_SERVER_ENC_NONCE); 419 | // assert_eq!(outcome.decryption_key(), EXP_SERVER_DEC_KEY); 420 | // assert_eq!(outcome.decryption_nonce(), EXP_SERVER_DEC_NONCE); 421 | // assert_eq!(outcome.peer_longterm_pk(), EXP_CLIENT_PUB); 422 | // } 423 | // 424 | // fn const_async_false(_: &sign::PublicKey) -> FutureResult { 425 | // ok(false) 426 | // } 427 | // 428 | // #[test] 429 | // // A filtering server rejects a client if the filter function returns false. 430 | // fn test_filter_server_reject() { 431 | // let mut stream = MockDuplex::new(); 432 | // stream.add_read_data(&CLIENT_MSGS[..]); 433 | // 434 | // let server = ServerHandshakerWithFilter::new(stream, 435 | // const_async_false, 436 | // &APP, 437 | // &SERVER_PUB, 438 | // &SERVER_SEC, 439 | // &SERVER_EPH_PUB, 440 | // &SERVER_EPH_SEC); 441 | // 442 | // assert!(server.wait().unwrap().0.unwrap_err() == 443 | // ServerHandshakeFailureWithFilter::UnauthorizedClient); 444 | // } 445 | // 446 | // #[test] 447 | // // A filtering server propagates io errors in the handshake. 448 | // fn test_filter_server_io_error() { 449 | // let mut stream = MockDuplex::new(); 450 | // stream.add_read_data(&CLIENT_MSGS[..]); 451 | // let read_ops = vec![PartialOp::Unlimited, 452 | // PartialOp::Err(io::ErrorKind::NotFound)]; 453 | // let stream = PartialAsyncWrite::new(stream, vec![]); 454 | // let stream = PartialAsyncRead::new(stream, read_ops); 455 | // 456 | // let server = ServerHandshakerWithFilter::new(stream, 457 | // const_async_true, 458 | // &APP, 459 | // &SERVER_PUB, 460 | // &SERVER_SEC, 461 | // &SERVER_EPH_PUB, 462 | // &SERVER_EPH_SEC); 463 | // 464 | // match server.wait().unwrap_err().0 { 465 | // ServerHandshakeError::IoError(e) => assert_eq!(e.kind(), io::ErrorKind::NotFound), 466 | // ServerHandshakeError::FilterFnError(_) => assert!(false), 467 | // } 468 | // } 469 | // 470 | // fn const_async_error(_: &sign::PublicKey) -> FutureResult { 471 | // err(()) 472 | // } 473 | // 474 | // #[test] 475 | // // A filtering server propagates filter function errors in the handshake. 476 | // fn test_filter_server_filter_error() { 477 | // let mut stream = MockDuplex::new(); 478 | // stream.add_read_data(&CLIENT_MSGS[..]); 479 | // 480 | // let server = ServerHandshakerWithFilter::new(stream, 481 | // const_async_error, 482 | // &APP, 483 | // &SERVER_PUB, 484 | // &SERVER_SEC, 485 | // &SERVER_EPH_PUB, 486 | // &SERVER_EPH_SEC); 487 | // 488 | // match server.wait().unwrap_err().0 { 489 | // ServerHandshakeError::IoError(_) => assert!(false), 490 | // ServerHandshakeError::FilterFnError(e) => assert_eq!(e, ()), 491 | // } 492 | // } 493 | --------------------------------------------------------------------------------