[−]Crate ossuary
Ossuary
Ossuary is a Rust library for establishing an encrypted and authenticated communication channel between a client and a server.
It establishes a 1-to-1 client/server communication channel that requires reliable, in-order packet delivery, such as provided by TCP sockets.
Authentication and verification of remote hosts is optional, and requires an out-of-band exchange of host public keys, or a Trust-On-First-Use policy.
Ossuary includes a C FFI API, and can be built as a native dynamic or static library for linking into C or C++ binaries.
Protocol Overview:
The Ossuary protocol consists of two distinct stages: a handshaking stage and an established channel stage.
Handshake
All connections begin by handshaking. A few packets are exchanged back and forth to establish an encrypted channel with a negotiated session key, and to optionally authenticate the identity of the hosts. Handshake packets are generated by the Ossuary library itself; the application asks Ossuary for the next packet to send, and then is responsible for putting it on the wire.
Established Channel
Once the handshake completes successfully, Ossuary drops into the established channel stage. In this stage, Ossuary is completely passive, where the application gives it data to encrypt for transmission or decrypt for reception. Ossuary does not generate any unrequested packets in this stage.
In the case of errors, Ossuary always terminates the encrypted channel and drops back into the handshake stage. If it believes the channel might still be viable, the handshake stage will generate a packet to attempt to reset the remote host back into the handshake as well, and they will both attempt to recover the channel.
API Overview:
Ossuary fits into an application as a 'filter' on network traffic, and does not implement any network communication itself. Similarly, it does not have any persistent storage, leaving the storage of keys to the caller.
The application must always check to see if it should drop back into the handshake, in case the connection failed. The main loop would typically be split between two cases: handshaking, or established channel.
In pseudocode, typical use of the API looks like this:
fn main() { let socket = TCPSocket::get_socket_from_somewhere(); let secret_key = FileSystem::get_securely_stored_file("secret.key"); let ossuary = OssuaryConnection::new(ConnectionType::Whatever, Some(secret_key)); let net_read_buf = [0u8; 1024]; // encrypted! let net_write_buf = [0u8; 1024]; // encrypted! let internal_buf = [0u8; 1024]; // plaintext! loop { // Always read encrypted data off the wire and append to our read buf socket.read_and_append(&mut net_read_buf); // Two paths depending on if we are handshaking match ossuary.handshake_done() { false => { // Parse received handshake packets. ossuary.recv_handshake(&net_read_buf); // Possibly write handshake packets. ossuary.send_handshake(&mut net_write_buf); }, true => { // Decrypt data into internal buffer ossuary.recv_data(&net_read_buf, &mut internal_buf); // Do some application-specific thing with the data react_and_respond(&mut internal_buf); // Encrypt application's response ossuary.send_data(&internal_buf, &mut net_write_buf); }, } // Always write all encrypted data onto the wire and clear the write buf socket.write_and_clear(&mut net_write_buf); } }
A real implementation would also need to carefully handle removing consumed bytes from the read buffer, and handling errors at every step.
Since Ossuary leaves the actual wire transmission to the caller, it can be used over any physical channel, or with any transmission protocol, so long as data is delivered (to Ossuary) reliably and in-order. It is just as viable for UNIX Domain Sockets, D-Bus, named pipes, and other IPC mechanisms as for TCP, if you somehow have a threat model that distrusts those.
Ciphers:
- Ephemeral session keys: Curve25519 ECDH.
- Session encryption: ChaCha20 symmetrical cipher.
- Message authentication: Poly1305 MAC.
- Host authentication: Ed25519 signature scheme.
The handshake protocol:
A 3-packet (1.5 roundtrip) handshake is always performed.
The necessary fields to perform an ECDH key exchange and establish a shared session key are sent in the clear, while fields for host verification are encrypted with the established session key.
In the following diagram, fields in [single brackets] are sent in the clear, and those in [[double brackets]] are encrypted with the shared session key:
<client> --> [ session x25519 public key,
session nonce,
client random challenge ] --> <server>
<client> <-- [ session x25519 public key,
session nonce],
[[ auth ed25519 public key,
server random challenge,
signature(pubkey, nonce, challenge), ]] <-- <server>
<client> --> [[ auth ed25519 public key,
signature(pubkey, nonce, challenge), ]] --> <server>
Host authentication (verifying the identity of the remote server or client) is optional. In non-authenticated flows, "auth public key", "challenge", and "signature" fields are set to all 0s, but are still transmitted.
Fields
- session public key: Public part of randomly generated public/private key pair for this session, used to generate an ephemeral session key.
- challenge: Randomly generated string for remote party to sign to prove its identity in authenticated connections.
- auth public key: Public part of long-lived public/private key pair used for host authentication.
- signature: Signature, with long-lived private authentication key, of local party's session public key and nonce (the ECDH parameters) and remote party's random challenge, to prove host identity and prevent man-in-the-middle attacks.
Security Protections
Passive Snooping
Per-packet encryption with ChaCha20 prevents passive monitoring of the contents of the communication channel.
Malleability
Poly1305 MAC prevents active manipulation of packets in-flight, ensuring that any manipulation will cause the channel to terminate.
Replay Attacks
Poly1305 MAC combined with a nonce scheme prevents replay attacks, and prevents manipulation of message order.
Forward Secrecy
Per-session encryption with ephemeral x25519 keys ensures that the compromise of one session does not necessarily result in the compromise of any previous or future session.
Man-in-the-Middle
Host authentication with Ed25519 signature verification of ECDH parameters prevents man-in-the-middle attacks. Host authentication is optional, and requires out-of-band exchange of host public keys or a Trust On First Use policy, so MITM attacks may be possible if care is not taken.
Security Limitations
Code Quality
This software is not code reviewed, and no security analysis has been performed.
Keys In RAM
No efforts are taken to secure key data in RAM. Attacks from privileged local processes are possible.
Keys On Disk
No mechanisms are provided for storing keys on disk. Secure key storage is left as a task for the caller.
Side-Channel
No efforts are taken to protect against side channel attacks such as timing or cache analysis.
Software Dependencies
This software depends on third-party software libraries for all core cryptographic algorithms, which have not been code reviewed and are subject to change.
Trust-On-First-Use (TOFU)
Host authentication supports a trust-on-first-use policy, which opens the possibility of man-in-the-middle attacks if the first connection is compromised.
License
Copyright 2019 Trevor Bentley
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
Modules
clib | Ossuary API exposed with a C FFI |
Structs
OssuaryConnection | Context for interacting with an encrypted communication channel |
Enums
ConnectionType | Enum specifying the client or server role of a |
OssuaryError | Error produced by Ossuary or one of its dependencies |
Functions
generate_auth_keypair | Generate secret/public Ed25519 keypair for host authentication |