Skip to content

Commit 864a197

Browse files
committed
ssh-key: fix ssh-dss signature encoding on 64-bit targets
Since switching from `BigUint::to_bytes_be()` to `BoxedUint::to_be_bytes()` in 07f295e, `Signer<Signature> for DsaKeypair::try_sign` produces a 48-byte blob instead of the 40 bytes required by RFC 4253 §6.6, panicking the debug-assert at `signature.rs:345` and breaking ssh-dss publickey client auth in release builds. Root cause: `dsa::Signature::r()`/`s()` are constructed at `KeySize::n_aligned() == n.div_ceil(Limb::BITS) * Limb::BITS` precision. For ssh-dss (n = 160) on 64-bit targets that's 192 bits, so `to_be_bytes()` returns 24 bytes per component. Trim the limb-alignment leading zeros down to `DSA_SIGNATURE_SIZE / 2` before left-padding so values shorter than 160 bits still get padded. Also bump dsa to 0.7.0-rc.14 to pick up the verify-side overflow fix (`v1.concatenating_mul(&v2)` in `verifying_key.rs`); without it, `DsaPublicKey::verify` panics with "attempted to multiply with overflow" on 1024-bit p. Reworked the existing `try_sign_and_verify_dsa` test so it asserts on the encoded `signature.data.len() == DSA_SIGNATURE_SIZE` rather than on the underlying `dsa` component byte lengths -- the old assertion (`r.len() == 20`) couldn't hold on 64-bit given the alignment above. Refs Eugeny/russh#705
1 parent 07f295e commit 864a197

3 files changed

Lines changed: 19 additions & 54 deletions

File tree

Cargo.lock

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

ssh-key/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ crypto-bigint = { version = "0.7.0-rc.28", features = ["alloc"] }
2828
argon2 = { version = "0.5", optional = true, default-features = false, features = ["alloc"] }
2929
bcrypt-pbkdf = { version = "0.10", optional = true, default-features = false, features = ["alloc"] }
3030
bigint = { package = "num-bigint-dig", version = "0.8.4", optional = true, default-features = false, features = ["std"] }
31-
dsa = { version = "0.7.0-rc.13", optional = true, default-features = false, features = ["hazmat"] }
31+
dsa = { version = "0.7.0-rc.14", optional = true, default-features = false, features = ["hazmat"] }
3232
ecdsa = { version = "0.17.0-rc.16", optional = true, default-features = false } # only need this explicit dependency due to a minimal-versions fail @ https://github.com/RustCrypto/elliptic-curves/issues/1038
3333
ed25519-dalek = { version = "3.0.0-pre.6", optional = true, default-features = false }
3434
hex = { version = "0.4", optional = true }

ssh-key/src/signature.rs

Lines changed: 16 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -333,13 +333,19 @@ impl Signer<Signature> for DsaKeypair {
333333
.map_err(|_| signature::Error::new())?;
334334

335335
// Encode the format specified in RFC4253 section 6.6: two raw 80-bit integers concatenated
336-
let mut data = Vec::new();
336+
let mut data = Vec::with_capacity(DSA_SIGNATURE_SIZE);
337+
let half = DSA_SIGNATURE_SIZE / 2;
337338

338339
for component in [signature.r(), signature.s()] {
339-
let mut bytes = component.to_be_bytes();
340-
let pad_len = (DSA_SIGNATURE_SIZE / 2).saturating_sub(bytes.len());
341-
data.extend(iter::repeat(0).take(pad_len));
342-
data.extend(&bytes);
340+
// `BoxedUint::to_be_bytes()` is sized to `bits_precision`, which
341+
// `dsa` rounds up to a multiple of `Limb::BITS` (24 bytes on 64-bit
342+
// for n = 160). Since `dsa` enforces `r < q` and `s < q`, any bytes
343+
// above the trailing `half` are zero and can be sliced off.
344+
let bytes = component.to_be_bytes();
345+
let bytes = &bytes[bytes.len().saturating_sub(half)..];
346+
let pad_len = half.saturating_sub(bytes.len());
347+
data.resize(data.len() + pad_len, 0);
348+
data.extend_from_slice(bytes);
343349
}
344350

345351
debug_assert_eq!(data.len(), DSA_SIGNATURE_SIZE);
@@ -887,65 +893,24 @@ mod tests {
887893
use encoding::Decode as _;
888894
use signature::{Signer as _, Verifier as _};
889895

890-
fn check_signature_component_lens(
891-
keypair: &DsaKeypair,
892-
data: &[u8],
893-
r_len: usize,
894-
s_len: usize,
895-
) {
896-
use sha1::{Digest as _, Sha1};
897-
use signature::DigestSigner as _;
898-
899-
let signature = dsa::SigningKey::try_from(keypair)
900-
.expect("valid DSA signing key")
901-
.try_sign_digest(|d: &mut Sha1| Ok(d.update(data)))
902-
.expect("valid DSA signature");
903-
904-
let r = signature.r().to_be_bytes();
905-
assert_eq!(
906-
r.len(),
907-
r_len,
908-
"dsa signature component `r` has len {} != {}",
909-
r.len(),
910-
r_len
911-
);
912-
let s = signature.s().to_be_bytes();
913-
assert_eq!(
914-
s.len(),
915-
s_len,
916-
"dsa signature component `s` has len {} != {}",
917-
s.len(),
918-
s_len
919-
);
920-
}
921-
922896
let keypair = hex!("0000008100c161fb30c9e4e3602c8510f93bbd48d813da845dfcc75f3696e440cd019d609809608cd592b8430db901d7b43740740045b547c60fb035d69f9c64d3dfbfb13bb3edd8ccfdd44705739a639eb70f4aed16b0b8355de1b21cd9d442eff250895573a8af7ce2fb71fb062e887482dab5c68139845fb8afafc5f3819dc782920d510000001500f3fb6762430332bd5950edc5cd1ae6f17b88514f0000008061ef1394d864905e8efec3b610b7288a6522893af2a475f910796e0de47c8b065d365e942e80e471d1e6d4abdee1d3d3ede7103c6996432f1a9f9a671a31388672d63555077911fc69e641a997087260d22cdbf4965aa64bb382204f88987890ec225a5a7723a977dc1ecc5e04cf678f994692b20470adbf697489f800817b920000008100a9a6f1b65fc724d65df7441908b34af66489a4a3872cbbba25ea1bcfc83f25c4af1a62e339eefc814907cfaf0cb6d2d16996212a32a27a63013f01c57d0630f0be16c8c69d16fc25438e613b904b98aeb3e7c356fa8e75ee1d474c9f82f1280c5a6c18e9e607fcf7586eefb75ea9399da893b807375ac1396fd586bf277161980000001500ced95f1c7bbb39be4987837ad1f71be31bb7b0d9");
923897
let keypair = DsaKeypair::decode(&mut &keypair[..]).expect("properly encoded DSA keypair");
924898

925899
let data = hex!("F0000040713d5f6fffe0000e6421ab0b3a69774d3da02fd72b107d6b32b6dad7c1660bbf507bf3eac3304cc5058f7e6f81b04239b8471459b1f3b387e2626f7eb8f6bcdd3200000006626c616465320000000e7373682d636f6e6e656374696f6e00000009686f73746261736564000000077373682d647373000001b2000000077373682d6473730000008100c161fb30c9e4e3602c8510f93bbd48d813da845dfcc75f3696e440cd019d609809608cd592b8430db901d7b43740740045b547c60fb035d69f9c64d3dfbfb13bb3edd8ccfdd44705739a639eb70f4aed16b0b8355de1b21cd9d442eff250895573a8af7ce2fb71fb062e887482dab5c68139845fb8afafc5f3819dc782920d510000001500f3fb6762430332bd5950edc5cd1ae6f17b88514f0000008061ef1394d864905e8efec3b610b7288a6522893af2a475f910796e0de47c8b065d365e942e80e471d1e6d4abdee1d3d3ede7103c6996432f1a9f9a671a31388672d63555077911fc69e641a997087260d22cdbf4965aa64bb382204f88987890ec225a5a7723a977dc1ecc5e04cf678f994692b20470adbf697489f800817b920000008100a9a6f1b65fc724d65df7441908b34af66489a4a3872cbbba25ea1bcfc83f25c4af1a62e339eefc814907cfaf0cb6d2d16996212a32a27a63013f01c57d0630f0be16c8c69d16fc25438e613b904b98aeb3e7c356fa8e75ee1d474c9f82f1280c5a6c18e9e607fcf7586eefb75ea9399da893b807375ac1396fd586bf2771619800000015746f6d61746f7373682e6c6f63616c646f6d61696e00000009746f6d61746f737368");
926-
check_signature_component_lens(
927-
&keypair,
928-
&data,
929-
DSA_SIGNATURE_SIZE / 2,
930-
DSA_SIGNATURE_SIZE / 2,
931-
);
932900
let signature = keypair.try_sign(&data[..]).expect("dsa try_sign is ok");
901+
assert_eq!(signature.as_bytes().len(), DSA_SIGNATURE_SIZE);
933902
keypair
934903
.public
935904
.verify(&data[..], &signature)
936905
.expect("dsa verify is ok");
937906

907+
// This input produces an `r` shorter than 160 bits, exercising the
908+
// left-pad path for naturally short signature components.
938909
let data = hex!("00000040713d5f6fffe0000e6421ab0b3a69774d3da02fd72b107d6b32b6dad7c1660bbf507bf3eac3304cc5058f7e6f81b04239b8471459b1f3b387e2626f7eb8f6bcdd3200000006626c616465320000000e7373682d636f6e6e656374696f6e00000009686f73746261736564000000077373682d647373000001b2000000077373682d6473730000008100c161fb30c9e4e3602c8510f93bbd48d813da845dfcc75f3696e440cd019d609809608cd592b8430db901d7b43740740045b547c60fb035d69f9c64d3dfbfb13bb3edd8ccfdd44705739a639eb70f4aed16b0b8355de1b21cd9d442eff250895573a8af7ce2fb71fb062e887482dab5c68139845fb8afafc5f3819dc782920d510000001500f3fb6762430332bd5950edc5cd1ae6f17b88514f0000008061ef1394d864905e8efec3b610b7288a6522893af2a475f910796e0de47c8b065d365e942e80e471d1e6d4abdee1d3d3ede7103c6996432f1a9f9a671a31388672d63555077911fc69e641a997087260d22cdbf4965aa64bb382204f88987890ec225a5a7723a977dc1ecc5e04cf678f994692b20470adbf697489f800817b920000008100a9a6f1b65fc724d65df7441908b34af66489a4a3872cbbba25ea1bcfc83f25c4af1a62e339eefc814907cfaf0cb6d2d16996212a32a27a63013f01c57d0630f0be16c8c69d16fc25438e613b904b98aeb3e7c356fa8e75ee1d474c9f82f1280c5a6c18e9e607fcf7586eefb75ea9399da893b807375ac1396fd586bf2771619800000015746f6d61746f7373682e6c6f63616c646f6d61696e00000009746f6d61746f737368");
939-
// verify that this data produces signature with `r` integer component that is less than 160 bits/20 bytes.
940-
check_signature_component_lens(
941-
&keypair,
942-
&data,
943-
DSA_SIGNATURE_SIZE / 2 - 1,
944-
DSA_SIGNATURE_SIZE / 2,
945-
);
946910
let signature = keypair
947911
.try_sign(&data[..])
948-
.expect("dsa try_sign for r.len() == 19 is ok");
912+
.expect("dsa try_sign for short `r` is ok");
913+
assert_eq!(signature.as_bytes().len(), DSA_SIGNATURE_SIZE);
949914
keypair
950915
.public
951916
.verify(&data[..], &signature)

0 commit comments

Comments
 (0)