aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authortv <tv@krebsco.de>2026-11-09 15:38:41 +0000
committertv <tv@krebsco.de>2026-11-09 18:20:59 +0100
commite59be7214b1fc1998f76a34f7e4429ee4259494c (patch)
tree9754fa4d80f5afce3cbf6d8225b986f99bf402c3
parent2b7aaf12fc0fa288560072766ad6d8d39e5e37ed (diff)
access: a key's comment 256 bytes at most
Review 4, 1250's note: a key's comment was kept as given, unbounded but by the request limit (2 MiB), in protocol data every node holds and the dashboard shows. It says whose and where the key is: at most 256 bytes now (`sshsig::MAX_COMMENT`), a longer one refused with the key.
-rw-r--r--docs/access.md2
-rw-r--r--src/access/sshsig.rs19
2 files changed, 18 insertions, 3 deletions
diff --git a/docs/access.md b/docs/access.md
index 402f1ab..94ca195 100644
--- a/docs/access.md
+++ b/docs/access.md
@@ -33,7 +33,7 @@ password hashes: every member reads the document).
doesn't read is left out, and anything but `false` disables.
* **`keys`**: the user's SSH keys, each by a name of its choosing, as an
`authorized_keys` line has it (`"laptop": "ssh-ed25519 AAAA… alice@laptop"`; Ed25519
- only; the comment is kept and checks nothing): what it signs values with (Values signed by their users, below) and logs in with. A user
+ only; the comment, 256 bytes at most, is kept and checks nothing): what it signs values with (Values signed by their users, below) and logs in with. A user
manages its own, whatever else it may do: `GET /auth/keys`, `PUT /auth/keys/<name>`
with the line, `DELETE /auth/keys/<name>`; under a strict policy, by signing its
entry with only its keys changed (`continuum-signed key-add laptop
diff --git a/src/access/sshsig.rs b/src/access/sshsig.rs
index 799cb87..e25d8a7 100644
--- a/src/access/sshsig.rs
+++ b/src/access/sshsig.rs
@@ -22,6 +22,10 @@ const END: &str = "-----END SSH SIGNATURE-----";
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct PublicKey(pub(crate) [u8; 32]);
+/// A key's comment kept at most, in bytes (`PublicKey::kept_line`): what says whose and
+/// where the key is, not a place for text.
+pub(crate) const MAX_COMMENT: usize = 256;
+
impl PublicKey {
/// From an `authorized_keys` line: `ssh-ed25519 <base64> [comment]`.
pub(crate) fn parse(line: &str) -> Result<Self, String> {
@@ -70,11 +74,20 @@ impl PublicKey {
/// made one). The comment is for whoever reads it: the key alone checks a signature.
pub(crate) fn kept_line(line: &str) -> Result<String, String> {
let key = Self::parse(line)?;
- let comment: Vec<&str> = line.split_whitespace().skip(2).collect();
+ let comment = line
+ .split_whitespace()
+ .skip(2)
+ .collect::<Vec<&str>>()
+ .join(" ");
+ if comment.len() > MAX_COMMENT {
+ return Err(format!(
+ "the key's comment is longer than {MAX_COMMENT} bytes"
+ ));
+ }
Ok(if comment.is_empty() {
key.line()
} else {
- format!("{} {}", key.line(), comment.join(" "))
+ format!("{} {comment}", key.line())
})
}
@@ -263,6 +276,8 @@ DNg4vBWp1j1gLRiBMOF+gwYNegDg==
assert_eq!(commented, format!("{} alice@laptop 2026", key.line()));
assert_eq!(PublicKey::parse(&commented), Ok(key));
assert_eq!(PublicKey::kept_line(&key.line()), Ok(key.line()));
+ let long = format!("{} {}", key.line(), "x".repeat(MAX_COMMENT + 1));
+ assert!(PublicKey::kept_line(&long).is_err());
}
/// What `sign` makes checks, and reads as `ssh-keygen` writes it.