clients::rdp connects over any caller-dialled TCP stream (direct, proxy or SSH tunnel), upgrades to TLS and logs on with NLA (CredSSP/NTLM), optionally falling back to TLS-only logon for servers without NLA such as xrdp (credentials are passed so they log straight in). The server certificate is pinned through a caller check on its SHA256 fingerprint before any credentials are sent. The desktop decodes into the VNC client's RGBA framebuffer; input (IronRDP's scancode/Unicode/mouse operations), Display Control resizes and the deactivation-reactivation sequence run on the session thread. IronRDP's releases need three vendored patches (vendor/README.md): picky and sspi with their RustCrypto release-candidate pins relaxed so they share russh's stable versions (their own suites, 402 tests, pass against them), and ironrdp-session with two unreleased upstream bitmap fixes (#1486 stride, #1524 RDP6 orientation) without which xrdp desktops render sheared. Verified live against xrdp (scottyhardy/docker-remote-desktop): logon, full desktop rendering, keyboard and mouse input.
575 lines
19 KiB
Rust
575 lines
19 KiB
Rust
use crate::key::ec::{EcdsaKeypair, EcdsaPublicKey};
|
|
use crate::key::ed::{EdKeypair, EdPublicKey};
|
|
use crate::ssh::certificate::{
|
|
SshCertType, SshCertTypeError, SshCertificate, SshCertificateError, SshCriticalOption, SshCriticalOptionError,
|
|
SshExtension, SshExtensionError, SshSignature, SshSignatureError, Timestamp,
|
|
};
|
|
use crate::ssh::private_key::{
|
|
AES256_CTR, AUTH_MAGIC, Aes256Ctr, BCRYPT, KdfOption, NONE, SshBasePrivateKey, SshPrivateKey, SshPrivateKeyError,
|
|
};
|
|
use crate::ssh::public_key::{SshBasePublicKey, SshPublicKey, SshPublicKeyError};
|
|
use crate::ssh::{Base64Writer, EcCurveSshExt as _, EdAlgorithmSshExt as _, SSH_COMBO_ED25519_KEY_LENGTH, key_type};
|
|
|
|
use super::certificate::SshSignatureBlob;
|
|
use super::key_identifier;
|
|
use aes::cipher::{KeyIvInit, StreamCipher};
|
|
use base64::engine::general_purpose;
|
|
use byteorder::{BigEndian, WriteBytesExt};
|
|
use crypto_bigint::NonZero;
|
|
use rsa::traits::{PrivateKeyParts as _, PublicKeyParts as _};
|
|
use rsa::{BoxedUint, RsaPrivateKey, RsaPublicKey};
|
|
use std::io::{self, Write};
|
|
|
|
pub trait SshWriteExt {
|
|
type Error;
|
|
|
|
fn write_ssh_string(&mut self, data: &str) -> Result<(), Self::Error>;
|
|
fn write_ssh_bytes(&mut self, data: &[u8]) -> Result<(), Self::Error>;
|
|
fn write_ssh_mpint(&mut self, data: &BoxedUint) -> Result<(), Self::Error>;
|
|
}
|
|
|
|
impl<T> SshWriteExt for T
|
|
where
|
|
T: Write,
|
|
{
|
|
type Error = io::Error;
|
|
|
|
fn write_ssh_string(&mut self, data: &str) -> Result<(), Self::Error> {
|
|
self.write_u32::<BigEndian>(data.len() as u32)?;
|
|
self.write_all(data.as_bytes())
|
|
}
|
|
|
|
fn write_ssh_bytes(&mut self, data: &[u8]) -> Result<(), Self::Error> {
|
|
self.write_u32::<BigEndian>(data.len() as u32)?;
|
|
self.write_all(data)
|
|
}
|
|
|
|
fn write_ssh_mpint(&mut self, data: &BoxedUint) -> Result<(), Self::Error> {
|
|
let data = data.to_be_bytes_trimmed_vartime();
|
|
let size = data.len() as u32;
|
|
// If the most significant bit would be set for
|
|
// a positive number, the number MUST be preceded by a zero byte.
|
|
if size > 0 && data[0] & 0b10000000 != 0 {
|
|
self.write_u32::<BigEndian>(size + 1)?;
|
|
self.write_u8(0)?;
|
|
} else {
|
|
self.write_u32::<BigEndian>(size)?;
|
|
}
|
|
self.write_all(&data)
|
|
}
|
|
}
|
|
|
|
pub trait SshComplexTypeEncode {
|
|
type Error;
|
|
|
|
fn encode(&self, stream: impl Write) -> Result<(), Self::Error>;
|
|
}
|
|
|
|
impl SshComplexTypeEncode for SshCertType {
|
|
type Error = SshCertTypeError;
|
|
|
|
fn encode(&self, mut stream: impl Write) -> Result<(), Self::Error> {
|
|
stream.write_u32::<BigEndian>((*self).into())?;
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl SshComplexTypeEncode for SshCriticalOption {
|
|
type Error = SshCriticalOptionError;
|
|
|
|
fn encode(&self, mut stream: impl Write) -> Result<(), Self::Error> {
|
|
stream.write_ssh_string(self.option_type.as_str())?;
|
|
stream.write_ssh_string(self.data.as_str())?;
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl<T> SshComplexTypeEncode for Vec<T>
|
|
where
|
|
T: SshComplexTypeEncode,
|
|
T::Error: From<std::io::Error>,
|
|
{
|
|
type Error = T::Error;
|
|
|
|
fn encode(&self, mut stream: impl Write) -> Result<(), Self::Error> {
|
|
let mut data = Vec::new();
|
|
for elem in self.iter() {
|
|
elem.encode(&mut data)?;
|
|
}
|
|
stream.write_ssh_bytes(&data)?;
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl SshComplexTypeEncode for SshExtension {
|
|
type Error = SshExtensionError;
|
|
|
|
fn encode(&self, mut stream: impl Write) -> Result<(), Self::Error> {
|
|
stream.write_ssh_string(self.extension_type.as_str())?;
|
|
stream.write_ssh_string(self.data.as_str())?;
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl SshComplexTypeEncode for Vec<String> {
|
|
type Error = io::Error;
|
|
|
|
fn encode(&self, mut stream: impl Write) -> Result<(), Self::Error> {
|
|
let mut data = Vec::new();
|
|
for s in self.iter() {
|
|
data.write_ssh_string(s)?;
|
|
}
|
|
stream.write_ssh_bytes(&data)?;
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl SshComplexTypeEncode for SshSignature {
|
|
type Error = SshSignatureError;
|
|
|
|
fn encode(&self, mut stream: impl Write) -> Result<(), Self::Error> {
|
|
let overall_size = self.format.as_str().len() + self.blob.size() + 8;
|
|
stream.write_u32::<BigEndian>(overall_size as u32)?;
|
|
stream.write_ssh_string(self.format.as_str())?;
|
|
|
|
match &self.blob {
|
|
SshSignatureBlob::Standard(data) => {
|
|
stream.write_ssh_bytes(data)?;
|
|
}
|
|
SshSignatureBlob::Sk { data, flags, counter } => {
|
|
stream.write_ssh_bytes(data)?;
|
|
stream.write_u8(*flags)?;
|
|
stream.write_u32::<BigEndian>(*counter)?;
|
|
}
|
|
};
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl SshComplexTypeEncode for KdfOption {
|
|
type Error = io::Error;
|
|
|
|
fn encode(&self, mut stream: impl Write) -> Result<(), Self::Error> {
|
|
if self.salt.is_empty() {
|
|
stream.write_u32::<BigEndian>(0)?;
|
|
return Ok(());
|
|
}
|
|
let mut data = Vec::new();
|
|
data.write_ssh_bytes(&self.salt)?;
|
|
data.write_u32::<BigEndian>(self.rounds)?;
|
|
stream.write_ssh_bytes(&data)?;
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl SshComplexTypeEncode for Timestamp {
|
|
type Error = io::Error;
|
|
|
|
fn encode(&self, mut stream: impl Write) -> Result<(), Self::Error> {
|
|
stream.write_u64::<BigEndian>(self.0)?;
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl SshComplexTypeEncode for SshBasePublicKey {
|
|
type Error = SshPublicKeyError;
|
|
|
|
fn encode(&self, mut stream: impl Write) -> Result<(), Self::Error> {
|
|
match self {
|
|
SshBasePublicKey::Rsa(rsa) => {
|
|
let rsa = RsaPublicKey::try_from(rsa)?;
|
|
stream.write_ssh_string(key_type::RSA)?;
|
|
stream.write_ssh_mpint(rsa.e())?;
|
|
stream.write_ssh_mpint(rsa.n())?;
|
|
Ok(())
|
|
}
|
|
SshBasePublicKey::Ec(ec) => {
|
|
let key = EcdsaPublicKey::try_from(ec)?;
|
|
encode_ecdsa_public_key_body(&mut stream, &key)?;
|
|
Ok(())
|
|
}
|
|
SshBasePublicKey::Ed(ed) => {
|
|
let key = EdPublicKey::try_from(ed)?;
|
|
encode_ed_public_key_body(&mut stream, &key)
|
|
}
|
|
SshBasePublicKey::SkEcdsaSha2NistP256 { base_key, application } => {
|
|
let key = EcdsaPublicKey::try_from(base_key)?;
|
|
|
|
stream.write_ssh_string(key_type::SK_ECDSA_SHA2_NIST_P256)?;
|
|
stream.write_ssh_string(key_identifier::ECDSA_SHA2_NIST_P256)?;
|
|
stream.write_ssh_bytes(key.encoded_point())?;
|
|
|
|
stream.write_ssh_string(application.as_str())?;
|
|
|
|
Ok(())
|
|
}
|
|
SshBasePublicKey::SkEd25519 { base_key, application } => {
|
|
let key = EdPublicKey::try_from(base_key)?;
|
|
|
|
stream.write_ssh_string(key_type::SK_ED25519)?;
|
|
stream.write_ssh_bytes(key.data())?;
|
|
|
|
stream.write_ssh_string(application.as_str())?;
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
impl SshComplexTypeEncode for SshPublicKey {
|
|
type Error = SshPublicKeyError;
|
|
|
|
fn encode(&self, mut stream: impl Write) -> Result<(), Self::Error> {
|
|
// Write key type
|
|
match &self.inner_key {
|
|
SshBasePublicKey::Rsa(_) => {
|
|
stream.write_all(key_type::RSA.as_bytes())?;
|
|
}
|
|
SshBasePublicKey::Ec(key) => {
|
|
let key = EcdsaPublicKey::try_from(key)?;
|
|
stream.write_all(key.curve().to_ecdsa_ssh_key_type()?.as_bytes())?;
|
|
}
|
|
SshBasePublicKey::Ed(key) => {
|
|
let key = EdPublicKey::try_from(key)?;
|
|
stream.write_all(key.algorithm().to_ed_ssh_key_type()?.as_bytes())?;
|
|
}
|
|
SshBasePublicKey::SkEcdsaSha2NistP256 { .. } => {
|
|
stream.write_all(key_type::SK_ECDSA_SHA2_NIST_P256.as_bytes())?;
|
|
}
|
|
SshBasePublicKey::SkEd25519 { .. } => {
|
|
stream.write_all(key_type::SK_ED25519.as_bytes())?;
|
|
}
|
|
};
|
|
|
|
stream.write_u8(b' ')?;
|
|
|
|
{
|
|
let mut base64_write = Base64Writer::new(&mut stream, &general_purpose::STANDARD);
|
|
self.inner_key.encode(&mut base64_write)?;
|
|
base64_write.finish()?;
|
|
}
|
|
|
|
stream.write_u8(b' ')?;
|
|
stream.write_all(self.comment.as_bytes())?;
|
|
stream.write_all("\r\n".as_bytes())?;
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl SshComplexTypeEncode for SshBasePrivateKey {
|
|
type Error = SshPrivateKeyError;
|
|
|
|
fn encode(&self, mut stream: impl Write) -> Result<(), Self::Error> {
|
|
match self {
|
|
SshBasePrivateKey::Rsa(rsa) => {
|
|
let rsa = RsaPrivateKey::try_from(rsa)?;
|
|
stream.write_ssh_string(key_type::RSA)?;
|
|
stream.write_ssh_mpint(rsa.n())?;
|
|
stream.write_ssh_mpint(rsa.e())?;
|
|
stream.write_ssh_mpint(rsa.d())?;
|
|
|
|
let prime = NonZero::new(rsa.primes()[0].clone())
|
|
.into_option()
|
|
.ok_or(SshPrivateKeyError::RsaPrimeIsZero)?;
|
|
let iqmp = rsa.primes()[1]
|
|
.invert_mod(&prime)
|
|
.into_option()
|
|
.ok_or(SshPrivateKeyError::RsaSecondPrimeInvertModFirstPrimeFailed)?;
|
|
stream.write_ssh_mpint(&iqmp)?;
|
|
|
|
for prime in rsa.primes().iter() {
|
|
stream.write_ssh_mpint(prime)?;
|
|
}
|
|
}
|
|
SshBasePrivateKey::Ec(key) => {
|
|
let keypair = EcdsaKeypair::try_from(key)?;
|
|
|
|
let public_key = EcdsaPublicKey::try_from(&keypair)?;
|
|
|
|
// Encode the public key part
|
|
encode_ecdsa_public_key_body(&mut stream, &public_key)?;
|
|
|
|
// Ecnode encoded secret
|
|
let secret = BoxedUint::from_be_slice_vartime(keypair.secret());
|
|
stream.write_ssh_mpint(&secret)?;
|
|
}
|
|
SshBasePrivateKey::Ed(key) => {
|
|
let keypair = EdKeypair::try_from(key)?;
|
|
let public_key = EdPublicKey::try_from(&keypair)?;
|
|
encode_ed_public_key_body(&mut stream, &public_key)?;
|
|
|
|
// SSH Ed25519 key private kye field contains secret in first 32 bytes and the
|
|
// public key copy in the last 32 bytes.
|
|
let mut secret = Vec::with_capacity(SSH_COMBO_ED25519_KEY_LENGTH);
|
|
secret.extend_from_slice(keypair.secret());
|
|
secret.extend_from_slice(public_key.data());
|
|
|
|
stream.write_ssh_bytes(&secret)?;
|
|
}
|
|
SshBasePrivateKey::SkEcdsaSha2NistP256 {
|
|
public_key,
|
|
application,
|
|
flags,
|
|
handle,
|
|
} => {
|
|
let ec_key = EcdsaPublicKey::try_from(public_key)?;
|
|
|
|
// Encode the public key part
|
|
stream.write_ssh_string(key_type::SK_ECDSA_SHA2_NIST_P256)?;
|
|
stream.write_ssh_string(key_identifier::ECDSA_SHA2_NIST_P256)?;
|
|
stream.write_ssh_bytes(ec_key.encoded_point())?;
|
|
|
|
stream.write_ssh_string(application.as_str())?;
|
|
stream.write_u8(*flags)?;
|
|
stream.write_ssh_bytes(handle)?;
|
|
// Reserved
|
|
stream.write_ssh_bytes(&[])?;
|
|
}
|
|
SshBasePrivateKey::SkEd25519 {
|
|
public_key,
|
|
application,
|
|
flags,
|
|
handle,
|
|
} => {
|
|
let ed_key = EdPublicKey::try_from(public_key)?;
|
|
|
|
stream.write_ssh_string(key_type::SK_ED25519)?;
|
|
stream.write_ssh_bytes(ed_key.data())?;
|
|
|
|
stream.write_ssh_string(application.as_str())?;
|
|
stream.write_u8(*flags)?;
|
|
stream.write_ssh_bytes(handle)?;
|
|
// Reserved
|
|
stream.write_ssh_bytes(&[])?;
|
|
}
|
|
};
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
fn encode_ed_public_key_body(mut stream: impl Write, key: &EdPublicKey<'_>) -> Result<(), SshPublicKeyError> {
|
|
stream.write_ssh_string(key.algorithm().to_ed_ssh_key_type()?)?;
|
|
stream.write_ssh_bytes(key.data())?;
|
|
Ok(())
|
|
}
|
|
|
|
fn encode_ecdsa_public_key_body(mut stream: impl Write, key: &EcdsaPublicKey<'_>) -> Result<(), SshPublicKeyError> {
|
|
stream.write_ssh_string(key.curve().to_ecdsa_ssh_key_type()?)?;
|
|
stream.write_ssh_string(key.curve().to_ecdsa_ssh_key_identifier()?)?;
|
|
|
|
// So called "Q" value from RFC5656. In fact - standard SEC1 encoded public key representation
|
|
stream.write_ssh_bytes(key.encoded_point())?;
|
|
Ok(())
|
|
}
|
|
|
|
impl SshComplexTypeEncode for SshPrivateKey {
|
|
type Error = SshPrivateKeyError;
|
|
|
|
fn encode(&self, mut stream: impl Write) -> Result<(), Self::Error> {
|
|
const AES256_CTR_BLOCK_SIZE: usize = 16;
|
|
const UNENCRYPTED_PADDING_SIZE: usize = 8;
|
|
|
|
stream.write_all(AUTH_MAGIC.as_bytes())?;
|
|
stream.write_u8(b'\0')?;
|
|
|
|
if self.passphrase.is_some() {
|
|
stream.write_ssh_string(AES256_CTR)?;
|
|
stream.write_ssh_string(BCRYPT)?;
|
|
|
|
let salt = &self.kdf.option.salt;
|
|
let rounds = self.kdf.option.rounds;
|
|
|
|
let mut kdf_options = Vec::new();
|
|
kdf_options.write_ssh_bytes(salt)?;
|
|
kdf_options.write_u32::<BigEndian>(rounds)?;
|
|
|
|
stream.write_ssh_bytes(&kdf_options)?;
|
|
} else {
|
|
stream.write_ssh_string(NONE)?;
|
|
stream.write_ssh_string(NONE)?;
|
|
stream.write_ssh_string("")?;
|
|
}
|
|
|
|
stream.write_u32::<BigEndian>(1)?; // keys amount
|
|
|
|
let mut public_key = Vec::new();
|
|
self.public_key().inner_key.encode(&mut public_key)?;
|
|
stream.write_ssh_bytes(&public_key)?;
|
|
|
|
public_key.clear();
|
|
let mut private_key = public_key;
|
|
|
|
private_key.write_u32::<BigEndian>(self.check)?;
|
|
private_key.write_u32::<BigEndian>(self.check)?;
|
|
self.base_key.encode(&mut private_key)?;
|
|
|
|
private_key.write_ssh_string(&self.comment)?;
|
|
|
|
let padding_size = if self.passphrase.is_some() {
|
|
AES256_CTR_BLOCK_SIZE
|
|
} else {
|
|
UNENCRYPTED_PADDING_SIZE
|
|
};
|
|
|
|
// add padding
|
|
for i in 1..=(padding_size - (private_key.len() % padding_size)) {
|
|
private_key.push(i as u8);
|
|
}
|
|
|
|
if let Some(passphrase) = &self.passphrase {
|
|
// encrypt private_key
|
|
let n = 48;
|
|
let mut hash = [0; 48];
|
|
|
|
let salt = &self.kdf.option.salt;
|
|
let rounds = self.kdf.option.rounds;
|
|
|
|
bcrypt_pbkdf::bcrypt_pbkdf(passphrase, salt, rounds, &mut hash)?;
|
|
|
|
let (key, iv) = hash.split_at(n - 16);
|
|
let mut cipher = Aes256Ctr::new_from_slices(key, iv).unwrap();
|
|
|
|
let private_key_len = private_key.len();
|
|
private_key.resize(private_key_len + 32, 0u8);
|
|
cipher.apply_keystream(&mut private_key);
|
|
private_key.truncate(private_key_len);
|
|
}
|
|
|
|
stream.write_ssh_bytes(&private_key)?;
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl SshComplexTypeEncode for SshCertificate {
|
|
type Error = SshCertificateError;
|
|
|
|
fn encode(&self, mut stream: impl Write) -> Result<(), Self::Error> {
|
|
stream.write_all(self.cert_key_type.as_str().as_bytes())?;
|
|
stream.write_u8(b' ')?;
|
|
|
|
let mut cert_data = Base64Writer::new(stream, &general_purpose::STANDARD);
|
|
|
|
cert_data.write_ssh_string(self.cert_key_type.as_str())?;
|
|
cert_data.write_ssh_bytes(&self.nonce)?;
|
|
match &self.public_key.inner_key {
|
|
SshBasePublicKey::Rsa(rsa) => {
|
|
let rsa = RsaPublicKey::try_from(rsa)?;
|
|
cert_data.write_ssh_mpint(rsa.e())?;
|
|
cert_data.write_ssh_mpint(rsa.n())?;
|
|
}
|
|
SshBasePublicKey::Ec(ec) => {
|
|
let ec = EcdsaPublicKey::try_from(ec)?;
|
|
cert_data.write_ssh_string(ec.curve().to_ecdsa_ssh_key_identifier()?)?;
|
|
cert_data.write_ssh_bytes(ec.encoded_point())?;
|
|
}
|
|
SshBasePublicKey::Ed(ed) => {
|
|
let ed = EdPublicKey::try_from(ed)?;
|
|
cert_data.write_ssh_bytes(ed.data())?;
|
|
}
|
|
SshBasePublicKey::SkEcdsaSha2NistP256 { base_key, application } => {
|
|
let ec = EcdsaPublicKey::try_from(base_key)?;
|
|
cert_data.write_ssh_string(key_identifier::ECDSA_SHA2_NIST_P256)?;
|
|
cert_data.write_ssh_bytes(ec.encoded_point())?;
|
|
cert_data.write_ssh_string(application.as_str())?;
|
|
}
|
|
SshBasePublicKey::SkEd25519 { base_key, application } => {
|
|
let ed = EdPublicKey::try_from(base_key)?;
|
|
cert_data.write_ssh_bytes(ed.data())?;
|
|
cert_data.write_ssh_string(application.as_str())?;
|
|
}
|
|
};
|
|
|
|
cert_data.write_u64::<BigEndian>(self.serial)?;
|
|
|
|
self.cert_type.encode(&mut cert_data)?;
|
|
|
|
cert_data.write_ssh_string(self.key_id.as_str())?;
|
|
|
|
self.valid_principals.encode(&mut cert_data)?;
|
|
self.valid_after.encode(&mut cert_data)?;
|
|
self.valid_before.encode(&mut cert_data)?;
|
|
self.critical_options.encode(&mut cert_data)?;
|
|
self.extensions.encode(&mut cert_data)?;
|
|
|
|
cert_data.write_ssh_bytes(&[])?; // reserved
|
|
|
|
let mut rsa_key = Vec::new();
|
|
self.signature_key.inner_key.encode(&mut rsa_key)?;
|
|
|
|
cert_data.write_ssh_bytes(&rsa_key)?;
|
|
self.signature.encode(&mut cert_data)?;
|
|
|
|
// stream.write_all(cert_data.finish()?.as_slice())?;
|
|
let mut stream = cert_data.finish().unwrap();
|
|
stream.write_u8(b' ')?;
|
|
|
|
stream.write_all(self.comment.as_bytes())?;
|
|
stream.write_all("\r\n".as_bytes())?;
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod test {
|
|
use super::SshWriteExt;
|
|
use rsa::BoxedUint;
|
|
|
|
#[test]
|
|
fn ssh_string_encode() {
|
|
let mut res = Vec::new();
|
|
let ssh_string = "picky";
|
|
|
|
res.write_ssh_string(ssh_string).unwrap();
|
|
|
|
assert_eq!(vec![0, 0, 0, 5, 112, 105, 99, 107, 121], res);
|
|
|
|
res.clear();
|
|
let ssh_string = "";
|
|
|
|
res.write_ssh_string(ssh_string).unwrap();
|
|
|
|
assert_eq!(vec![0, 0, 0, 0], res);
|
|
}
|
|
|
|
#[test]
|
|
fn byte_array_encode() {
|
|
let mut res = Vec::new();
|
|
let byte_array = [1, 2, 3, 4, 5, 6];
|
|
|
|
res.write_ssh_bytes(&byte_array).unwrap();
|
|
|
|
assert_eq!(vec![0, 0, 0, 6, 1, 2, 3, 4, 5, 6], res);
|
|
|
|
res.clear();
|
|
let byte_array = [];
|
|
|
|
res.write_ssh_bytes(&byte_array).unwrap();
|
|
|
|
assert_eq!(vec![0, 0, 0, 0], res);
|
|
}
|
|
|
|
#[test]
|
|
fn mpint_encoding() {
|
|
let mpint = BoxedUint::from_be_slice_vartime(&[0x09, 0xa3, 0x78, 0xf9, 0xb2, 0xe3, 0x32, 0xa7]);
|
|
let mut res = Vec::new();
|
|
res.write_ssh_mpint(&mpint).unwrap();
|
|
|
|
assert_eq!(
|
|
res,
|
|
vec![0x00, 0x00, 0x00, 0x08, 0x09, 0xa3, 0x78, 0xf9, 0xb2, 0xe3, 0x32, 0xa7],
|
|
);
|
|
|
|
let mpint = BoxedUint::from_be_slice_vartime(&[0x80]);
|
|
let mut res = Vec::new();
|
|
res.write_ssh_mpint(&mpint).unwrap();
|
|
|
|
assert_eq!(res, vec![0x00, 0x00, 0x00, 0x02, 0x00, 0x80]);
|
|
}
|
|
}
|