Files
asepharyana 1cdb82a76f Sync config from arch
- hypr/apps.lua
- hypr/autostart.lua
- hypr/envs.lua
- hypr/hyprland.lua
- hypr/hyprsunset.conf
- hypr/input.lua
- hypr/looknfeel.lua
- hypr/omasettings.lua
- hypr/xdph.conf
- omarchy/branding/about.txt
- omarchy/branding/screensaver.txt
- omarchy/extensions/omarchy-menu.jsonc
- omarchy/hooks/battery-low.d/play-warning-sound.sample
- omarchy/hooks/font-set.d/show-font-notification.sample
- omarchy/hooks/post-boot.d/weather.sample
- omarchy/hooks/post-update.d/install-voxtype.hook
- omarchy/hooks/post-update.d/setup-agent.hook
- omarchy/hooks/post-update.d/setup-fingerprint.hook
- omarchy/hooks/post-update.d/show-update-notification.sample
- omarchy/hooks/pre-refresh-pacman.d/add-custom-repo.sample
- omarchy/hooks/theme-set.d/show-theme-notification.sample
- omarchy/shell.json
- omarchy/shell.toml
- omarchy/theme.name
- omarchy/themes/azure-glow/README.md
- omarchy/themes/azure-glow/alacritty.toml
- omarchy/themes/azure-glow/btop.theme
- omarchy/themes/azure-glow/hyprland.conf
- omarchy/themes/azure-glow/hyprlock.conf
- omarchy/themes/azure-glow/icons.theme
- … 269 more
2026-09-23 15:19:12 +07:00

178 lines
5.6 KiB
Rust

use qs_bitwarden_ssh_agent::protocol::{handle_frame, Identity, MAX_FRAME_LEN};
use rand_core::OsRng;
use signature::Verifier;
use ssh_encoding::{Decode, Encode};
use ssh_key::private::RsaKeypair;
use ssh_key::{Algorithm, HashAlg, PrivateKey, Signature};
const FAILURE: u8 = 5;
const REQUEST_IDENTITIES: u8 = 11;
const IDENTITIES_ANSWER: u8 = 12;
const SIGN_REQUEST: u8 = 13;
const SIGN_RESPONSE: u8 = 14;
const RSA_SHA2_256: u32 = 2;
const RSA_SHA2_512: u32 = 4;
fn frame(payload: &[u8]) -> Vec<u8> {
let mut encoded = Vec::with_capacity(payload.len() + 4);
u32::try_from(payload.len())
.unwrap()
.encode(&mut encoded)
.unwrap();
encoded.extend_from_slice(payload);
encoded
}
fn string(value: &[u8], out: &mut Vec<u8>) {
value.encode(out).unwrap();
}
fn response_payload(response: &[u8]) -> &[u8] {
let declared = u32::from_be_bytes(response[..4].try_into().unwrap()) as usize;
assert_eq!(declared, response.len() - 4);
&response[4..]
}
fn sign_request(key_blob: &[u8], message: &[u8], flags: u32) -> Vec<u8> {
let mut payload = vec![SIGN_REQUEST];
string(key_blob, &mut payload);
string(message, &mut payload);
flags.encode(&mut payload).unwrap();
frame(&payload)
}
fn signature(response: &[u8]) -> Signature {
let payload = response_payload(response);
assert_eq!(payload[0], SIGN_RESPONSE);
let mut encoded = &payload[1..];
let signature_bytes = Vec::<u8>::decode(&mut encoded).unwrap();
assert!(encoded.is_empty());
Signature::try_from(signature_bytes.as_slice()).unwrap()
}
#[test]
fn lists_openssh_encoded_identities() {
let ed25519 = PrivateKey::random(&mut OsRng, Algorithm::Ed25519).unwrap();
let rsa = PrivateKey::from(RsaKeypair::random(&mut OsRng, 2048).unwrap());
let identities = [
Identity::new(ed25519, "vault ed25519").unwrap(),
Identity::new(rsa, "vault rsa").unwrap(),
];
let response = handle_frame(&frame(&[REQUEST_IDENTITIES]), &identities);
let payload = response_payload(&response);
assert_eq!(payload[0], IDENTITIES_ANSWER);
let mut fields = &payload[1..];
assert_eq!(u32::decode(&mut fields).unwrap(), 2);
for identity in identities.iter() {
assert_eq!(
Vec::<u8>::decode(&mut fields).unwrap(),
identity.public_blob()
);
assert_eq!(String::decode(&mut fields).unwrap(), identity.comment());
}
assert!(fields.is_empty());
}
#[test]
fn signs_ed25519_requests_and_rejects_nonzero_flags() {
let key = PrivateKey::random(&mut OsRng, Algorithm::Ed25519).unwrap();
let identity = Identity::new(key, "ed25519").unwrap();
let message = b"bounded agent protocol vector";
let signed = signature(&handle_frame(
&sign_request(identity.public_blob(), message, 0),
std::slice::from_ref(&identity),
));
assert_eq!(signed.algorithm(), Algorithm::Ed25519);
Verifier::verify(identity.public_key(), message, &signed).unwrap();
let rejected = handle_frame(
&sign_request(identity.public_blob(), message, RSA_SHA2_256),
&[identity],
);
assert_eq!(response_payload(&rejected), &[FAILURE]);
}
#[test]
fn signs_rsa_with_exactly_the_requested_sha2_algorithm() {
let key = PrivateKey::from(RsaKeypair::random(&mut OsRng, 2048).unwrap());
let identity = Identity::new(key, "rsa").unwrap();
let message = b"rsa protocol vector";
for (flags, hash) in [
(RSA_SHA2_256, HashAlg::Sha256),
(RSA_SHA2_512, HashAlg::Sha512),
] {
let signed = signature(&handle_frame(
&sign_request(identity.public_blob(), message, flags),
std::slice::from_ref(&identity),
));
assert_eq!(signed.algorithm(), Algorithm::Rsa { hash: Some(hash) });
Verifier::verify(identity.public_key(), message, &signed).unwrap();
}
for flags in [0, RSA_SHA2_256 | RSA_SHA2_512, 8] {
let rejected = handle_frame(
&sign_request(identity.public_blob(), message, flags),
std::slice::from_ref(&identity),
);
assert_eq!(response_payload(&rejected), &[FAILURE]);
}
}
#[test]
fn malformed_and_disallowed_requests_receive_only_bounded_failure() {
let cases = [
Vec::new(),
vec![0, 0, 0, 2, REQUEST_IDENTITIES],
frame(&[REQUEST_IDENTITIES, 0]),
frame(&[17]),
frame(&[18]),
frame(&[19]),
frame(&[20]),
frame(&[21]),
frame(&[22]),
frame(&[23]),
frame(&[25]),
frame(&[26]),
frame(&[27, 0, 0, 0, 1, 0xff]),
frame(&[255]),
];
for request in cases {
assert_eq!(response_payload(&handle_frame(&request, &[])), &[FAILURE]);
}
}
#[test]
fn lengths_are_rejected_before_body_allocation_or_parsing() {
let oversized_header = u32::try_from(MAX_FRAME_LEN + 1).unwrap().to_be_bytes();
assert_eq!(
response_payload(&handle_frame(&oversized_header, &[])),
&[FAILURE]
);
let mut invalid_string = vec![SIGN_REQUEST];
invalid_string.extend_from_slice(&u32::MAX.to_be_bytes());
assert_eq!(
response_payload(&handle_frame(&frame(&invalid_string), &[])),
&[FAILURE]
);
let mut unknown_key = vec![SIGN_REQUEST];
string(b"not an advertised public key", &mut unknown_key);
string(b"message", &mut unknown_key);
0_u32.encode(&mut unknown_key).unwrap();
assert_eq!(
response_payload(&handle_frame(&frame(&unknown_key), &[])),
&[FAILURE]
);
unknown_key.push(0);
assert_eq!(
response_payload(&handle_frame(&frame(&unknown_key), &[])),
&[FAILURE]
);
}