ssh_encoding/lib.rs
1#![no_std]
2#![cfg_attr(docsrs, feature(doc_auto_cfg))]
3#![doc = include_str!("../README.md")]
4#![doc(
5 html_logo_url = "https://raw.githubusercontent.com/RustCrypto/media/6ee8e381/logo.svg",
6 html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/media/6ee8e381/logo.svg"
7)]
8#![forbid(unsafe_code)]
9#![warn(
10 clippy::alloc_instead_of_core,
11 clippy::arithmetic_side_effects,
12 clippy::mod_module_files,
13 clippy::panic,
14 clippy::panic_in_result_fn,
15 clippy::std_instead_of_alloc,
16 clippy::std_instead_of_core,
17 clippy::unwrap_used,
18 missing_docs,
19 rust_2018_idioms,
20 unused_lifetimes,
21 unused_qualifications
22)]
23
24//! ## Conventions used in this crate
25//!
26//! This crate uses the following type labels which are described in [RFC4251 § 5], and also lists
27//! types with [`Decode`]/[`Encode`] trait impls which are compatible with this format:
28//!
29//! ### `byte`, `byte[n]`, `byte[]`: arbitrary 8-bit value (octet) or sequence thereof
30//! #### [`Decode`]/[`Encode`] trait impls:
31//! - `byte`: `u8`
32//! - `byte[n]`: `[u8; N]`
33//! - `byte[]`: `Vec<u8>`, `bytes::Bytes` (requires `bytes` crate feature)
34//!
35//! Fixed length data is sometimes represented as an array of bytes, written
36//! `byte[n]` where `n` is the number of bytes in the array.
37//!
38//! `byte[]` is a newer convention from OpenSSH for describing arbitrary
39//! length bytestrings (similar to `string`, see below) but identifies data
40//! which is inherently binary in nature, as opposed to text.
41//!
42//!
43//! ### `boolean`: boolean value stored as a single byte
44//! #### [`Decode`]/[`Encode`] trait impls: `bool`
45//!
46//! The value 0 represents FALSE, and the value 1 represents TRUE. All non-zero
47//! values MUST be interpreted as TRUE; however, applications MUST NOT
48//! store values other than 0 and 1.
49//!
50//! ### `uint32`: 32-bit unsigned integer
51//! #### [`Decode`]/[`Encode`] trait impls: `u32`, `usize`
52//!
53//! Stored as four bytes in the order of decreasing significance (network byte order).
54//!
55//! For example: the value `699921578` (`0x29b7f4aa`) is stored as
56//! `29 b7 f4 aa`.
57//!
58//! ### `uint64`: 64-bit unsigned integer
59//! #### [`Decode`]/[`Encode`] trait impls: `u64`
60//!
61//! Stored as eight bytes in the order of decreasing significance (network byte order).
62//!
63//! ### `string`: arbitrary length *binary* string
64//! #### [`Decode`]/[`Encode`] trait impls: `Vec<u8>`, `String`, `bytes::Bytes` (requires `bytes` crate feature)
65//!
66//! *NOTE: `string` is effectively equivalent to `byte[]`, however the latter is not defined in
67//! [RFC4251] and so trait impls in this crate for bytestring types like `[u8; N]` and `Vec<u8>`
68//! are described as being impls of `string`*.
69//!
70//! Strings are allowed to contain arbitrary binary data, including null characters and 8-bit
71//! characters.
72//!
73//! They are stored as a `uint32` containing its length (number of bytes that follow) and
74//! zero (= empty string) or more bytes that are the value of the string. Terminating null
75//! characters are not used.
76//!
77//! Strings are also used to store text. In that case, US-ASCII is used for internal names, and
78//! ISO-10646 UTF-8 for text that might be displayed to the user.
79//!
80//! The terminating null character SHOULD NOT normally be stored in the string.
81//!
82//! For example: the US-ASCII string "testing" is represented as `00 00 00 07 t e s t i n g`.
83//! The UTF-8 mapping does not alter the encoding of US-ASCII characters.
84//!
85//! ### `mpint`: multiple precision integers in two's complement format
86//! #### [`Decode`]/[`Encode`] trait impls: `Mpint`
87//!
88//! Stored as a byte string, 8 bits per byte, MSB first (a.k.a. big endian).
89//!
90//! Negative numbers have the value 1 as the most significant bit of the first byte of
91//! the data partition. If the most significant bit would be set for
92//! a positive number, the number MUST be preceded by a zero byte.
93//! Unnecessary leading bytes with the value 0 or 255 MUST NOT be
94//! included. The value zero MUST be stored as a string with zero
95//! bytes of data.
96//!
97//! By convention, a number that is used in modular computations in
98//! `Z_n` SHOULD be represented in the range `0 <= x < n`.
99//!
100//! #### Examples:
101//!
102//! value (hex) | representation (hex)
103//! -------------------|---------------------
104//! `0` | `00 00 00 00`
105//! `9a378f9b2e332a7` | `00 00 00 08 09 a3 78 f9 b2 e3 32 a7`
106//! `80` | `00 00 00 02 00 80`
107//! `-1234` | `00 00 00 02 ed cc`
108//! `-deadbeef` | `00 00 00 05 ff 21 52 41 11`
109//!
110//! ### `name-list`: string containing a comma-separated list of names
111//! #### [`Decode`]/[`Encode`] trait impls: `Vec<String>`
112//!
113//! A `name-list` is represented as a `uint32` containing its length
114//! (number of bytes that follow) followed by a comma-separated list of zero or more
115//! names. A name MUST have a non-zero length, and it MUST NOT
116//! contain a comma (",").
117//!
118//! As this is a list of names, all the elements contained are names and MUST be in US-ASCII.
119//!
120//! Context may impose additional restrictions on the names. For example,
121//! the names in a name-list may have to be a list of valid algorithm
122//! identifiers (see Section 6 below), or a list of [RFC3066] language
123//! tags. The order of the names in a name-list may or may not be
124//! significant. Again, this depends on the context in which the list
125//! is used.
126//!
127//! Terminating null characters MUST NOT be used, neither
128//! for the individual names, nor for the list as a whole.
129//!
130//! #### Examples:
131//!
132//! value | representation (hex)
133//! ---------------------------|---------------------
134//! `()`, the empty name-list | `00 00 00 00`
135//! `("zlib")` | `00 00 00 04 7a 6c 69 62`
136//! `("zlib,none")` | `00 00 00 09 7a 6c 69 62 2c 6e 6f 6e 65`
137//!
138//! [RFC3066]: https://datatracker.ietf.org/doc/html/rfc3066
139//! [RFC4251]: https://datatracker.ietf.org/doc/html/rfc4251
140//! [RFC4251 § 5]: https://datatracker.ietf.org/doc/html/rfc4251#section-5
141//!
142//! ## Deriving [`Decode`] and [`Encode`]
143//!
144//! The traits [`Decode`] and [`Encode`] can be derived for any struct or enum where all its fields
145//! implement [`Decode`] and [`Encode`] respectively.
146//!
147//! To use this functionality, enable the `derive` crate feature for `ssh-encoding`.
148//!
149//! ### Example
150//!
151//! Here is an example of how you could define a handful of the SSH message types:
152//!
153#![cfg_attr(all(feature = "alloc", feature = "derive"), doc = "```")]
154#![cfg_attr(not(all(feature = "alloc", feature = "derive")), doc = "```ignore")]
155//! use ssh_encoding::{Decode, Encode};
156//!
157//! #[derive(Debug, PartialEq, Encode, Decode)]
158//! #[repr(u8)]
159//! enum Message {
160//! Disconnect {
161//! reason_code: u32,
162//! description: String,
163//! language_tag: String,
164//! } = 1,
165//! EcdhInit {
166//! client_public_key: Vec<u8>,
167//! } = 30,
168//! EcdhReply {
169//! host_key: HostKey,
170//! server_public_key: Vec<u8>,
171//! #[ssh(length_prefixed)]
172//! host_signature: HostSignature,
173//! } = 31,
174//! }
175//!
176//! #[derive(Debug, PartialEq, Encode, Decode)]
177//! #[ssh(length_prefixed)]
178//! struct HostKey {
179//! key_type: String,
180//! ecdsa_curve_identifier: String,
181//! ecdsa_public_key: Vec<u8>,
182//! }
183//!
184//! #[derive(Debug, PartialEq, Encode, Decode)]
185//! struct HostSignature {
186//! signature_type: String,
187//! signature: Vec<u8>,
188//! }
189//!
190//! let message = Message::EcdhReply {
191//! host_key: HostKey {
192//! key_type: "ecdsa-sha2-nistp256".into(),
193//! ecdsa_curve_identifier: "nistp256".into(),
194//! ecdsa_public_key: vec![0x01, 0x02, 0x03],
195//! },
196//! server_public_key: vec![0x04, 0x05, 0x06],
197//! host_signature: HostSignature {
198//! signature_type: "ecdsa-sha2-nistp256".into(),
199//! signature: vec![0x07, 0x08, 0x09],
200//! },
201//! };
202//!
203//! let encoded = message.encode_vec().unwrap();
204//! assert_eq!(&encoded[..13], &[31, 0, 0, 0, 42, 0, 0, 0, 19, 101, 99, 100, 115]);
205//! let decoded = Message::decode(&mut &encoded[..]).unwrap();
206//! assert_eq!(message, decoded);
207//! ```
208
209#[cfg(feature = "alloc")]
210#[macro_use]
211extern crate alloc;
212
213mod checked;
214mod decode;
215mod encode;
216mod error;
217mod label;
218#[cfg(feature = "alloc")]
219mod mpint;
220#[macro_use]
221mod reader;
222mod writer;
223
224#[cfg(feature = "base64")]
225pub mod base64;
226#[cfg(feature = "pem")]
227pub mod pem;
228
229pub use crate::{
230 checked::CheckedSum,
231 decode::Decode,
232 encode::Encode,
233 error::{Error, Result},
234 label::{Label, LabelError},
235 reader::Reader,
236 writer::Writer,
237};
238
239#[cfg(feature = "alloc")]
240pub use crate::mpint::Mpint;
241
242#[cfg(feature = "base64")]
243pub use crate::{base64::Base64Reader, base64::Base64Writer};
244
245#[cfg(feature = "bigint")]
246pub use bigint;
247
248#[cfg(feature = "bytes")]
249pub use bytes;
250
251#[cfg(feature = "digest")]
252pub use crate::writer::DigestWriter;
253#[cfg(feature = "digest")]
254pub use digest;
255
256#[cfg(feature = "pem")]
257pub use crate::pem::{DecodePem, EncodePem};
258
259#[cfg(feature = "derive")]
260pub use ssh_derive::{Decode, Encode};
261
262#[cfg(all(doc, feature = "alloc"))]
263use alloc::vec::Vec;
264
265#[cfg(feature = "bigint")]
266pub use bigint::BoxedUint as Uint;
267
268/// Non-zero [`Uint`].
269#[cfg(feature = "bigint")]
270pub type NonZeroUint = bigint::NonZero<Uint>;
271
272/// Odd [`Uint`].
273#[cfg(feature = "bigint")]
274pub type OddUint = bigint::Odd<Uint>;