1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
//! Principals: who a request acts as (PRD §5.4 stage 1, identity mapping).
/// The identity a request was mapped to. Non-exhaustive: M2 may add a
/// caller-view class.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum Principal {
/// No credentials.
Anonymous,
/// An auth v2 signer.
Signer {
/// The signer's raw Ed25519 public key.
ed25519: [u8; 32],
},
/// Holder of a deployment-wide shared bearer token (`mkit-server
/// serve --auth bearer`, formerly `mkit serve --http`).
BearerHolder,
/// The authenticated peer of an encrypted (`enc`) listener.
TransportPeer {
/// The peer's raw Ed25519 static key.
ed25519: [u8; 32],
},
/// `mkit serve` over stdio. The identity is the ssh forced command
/// (SSH-SECURITY §5). `key` is always `None` in M0; WP-1.15 sets it from
/// `mkit serve --principal <ed25519-hex>`.
SshForcedCommand {
/// The Ed25519 key the forced command names, if any.
key: Option<[u8; 32]>,
},
}
impl Principal {
/// A stable, key-free label for tracing spans and metrics:
/// `anonymous`, `signer`, `bearer`, `transport_peer` or
/// `ssh_forced_command`.
#[must_use]
pub const fn kind(&self) -> &'static str {
match self {
Self::Anonymous => "anonymous",
Self::Signer { .. } => "signer",
Self::BearerHolder => "bearer",
Self::TransportPeer { .. } => "transport_peer",
Self::SshForcedCommand { .. } => "ssh_forced_command",
}
}
/// The principal's Ed25519 public key, when it has one.
#[must_use]
pub fn ed25519(&self) -> Option<&[u8; 32]> {
match self {
Self::Signer { ed25519 } | Self::TransportPeer { ed25519 } => Some(ed25519),
Self::SshForcedCommand { key } => key.as_ref(),
Self::Anonymous | Self::BearerHolder => None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn ed25519_accessor_covers_ssh_key_some_and_none() {
let key = [7u8; 32];
assert_eq!(Principal::Signer { ed25519: key }.ed25519(), Some(&key));
assert_eq!(
Principal::TransportPeer { ed25519: key }.ed25519(),
Some(&key)
);
assert_eq!(
Principal::SshForcedCommand { key: Some(key) }.ed25519(),
Some(&key)
);
assert_eq!(Principal::SshForcedCommand { key: None }.ed25519(), None);
assert_eq!(Principal::Anonymous.ed25519(), None);
assert_eq!(Principal::BearerHolder.ed25519(), None);
}
#[test]
fn kind_labels_carry_no_key_material() {
let key = [7u8; 32];
let cases = [
(Principal::Anonymous, "anonymous"),
(Principal::Signer { ed25519: key }, "signer"),
(Principal::BearerHolder, "bearer"),
(Principal::TransportPeer { ed25519: key }, "transport_peer"),
(
Principal::SshForcedCommand { key: Some(key) },
"ssh_forced_command",
),
];
for (principal, kind) in cases {
assert_eq!(principal.kind(), kind);
}
}
}