imsd: a resumed registration echoes the 401's Security-Server list from the state file
A warm resume rebuilt its Security-Verify from the kernel's SA: one mechanism with hmac-sha-1-96 and aes-cbc written in, where RFC 3329 §2.3.1 has the client mirror the server's whole list. The fresh registration kept the 401's list in memory, so a registration this process made fresh echoed it; after a restart, the resumed registration's re-REGISTER, its keepalives, its reg-event SUBSCRIBE and its INVITEs carried the rebuilt entry until the next fresh registration. KPN's one-entry answer to the default offer is the rebuilt entry byte for byte; against a six-entry list (O2's) the mirror is broken, which the RFC has the server answer with a 494, and a refused resume registers fresh. The fresh registration now stores the joined Security-Server value in the state file (security_server, next to the challenge answer) and the resume takes it from there, logging the mechanism count, when the entry carrying its SPIs names the SA pair in the kernel: a file an attempt the process did not live to record left behind (its SAs installed, its 200 not written) names another. A file from before this change holds none. In both cases the resume falls back to the rebuilt entry, says which, and the registration's next state write stores that entry, which is what it echoes; the choice is Imsd:Ipsec's, pinned in tests/Ipsec with O2's recorded list and KPN's. A value with a quote, a backslash or a newline in it is written empty, which the resume treats as absent; RFC 3329's token syntax produces none of these, a quoted-string extension parameter could carry the first two. Tested on the dev phone (KPN) with the build of this tree: a resume from a state file the 0.3.11 daemon wrote logs the fallback and succeeds, so do one from a file whose list names another SA pair and one whose list is empty; two keepalives at a 120-s interval; a fresh registration stores KPN's list byte-for-byte as the journal shows it received; a restart resumes with the list from the file; an outgoing call.
This commit is contained in:
parent
53b8c5c8e7
commit
729ccaa01d
5 changed files with 171 additions and 25 deletions
|
|
@ -13,11 +13,13 @@ alg (HMAC-SHA-1-96 or HMAC-MD5-96) keyed with IK; confidentiality is the
|
|||
negotiated ealg keyed with CK. Both come from the Security-Server mechanism
|
||||
the P-CSCF selected in its 401 (parsed here), and this module turns the
|
||||
negotiated SPIs/ports/keys into the exact argv lists the daemon shell hands to
|
||||
`ip`; for a warm resume it reconstructs the SPIs, ports, and Security-Server
|
||||
value back out of `ip xfrm state`/`policy` text — the Security-Verify and
|
||||
the P-CSCF ports for the resume's flow, and the SPIs in use, which a
|
||||
reregistration's new SPIs must differ from (and the state file falls back
|
||||
to).
|
||||
`ip`; for a warm resume it reconstructs the SPIs and ports back out of `ip
|
||||
xfrm state`/`policy` text — the P-CSCF ports for the resume's flow, and the
|
||||
SPIs in use, which a reregistration's new SPIs must differ from (and the
|
||||
state file falls back to) — and a one-mechanism Security-Server value that
|
||||
stands in for the Security-Verify when the state file holds no list for the
|
||||
SA pair in the kernel (`ChooseResumeSecurityVerify` decides; since 0.3.12
|
||||
the 401's own list is stored there).
|
||||
|
||||
Pure std C++ (the shell runs the commands and captures the text). Command
|
||||
shapes and the parser are pinned by tests/Ipsec.
|
||||
|
|
@ -308,7 +310,7 @@ export namespace imsd::ipsec {
|
|||
// What a warm SA in the kernel tells us — enough for a refresh re-REGISTER
|
||||
// to re-bind without re-authenticating.
|
||||
struct ExistingSa {
|
||||
std::string securityServer; // reconstructed Security-Server header value
|
||||
std::string securityServer; // one-mechanism Security-Server value: the resume's Security-Verify when the state file holds no list for this pair
|
||||
int portPs = 0;
|
||||
int portPc = 0;
|
||||
std::uint32_t spiUc = 0; // our inbound client SPI (a reregistration offers a different one)
|
||||
|
|
@ -403,6 +405,35 @@ export namespace imsd::ipsec {
|
|||
return sa;
|
||||
}
|
||||
|
||||
// The Security-Verify a resume sends, and where it came from: the state
|
||||
// file's list when the entry carrying its SPIs names the SA pair the
|
||||
// kernel holds (a file an attempt the process did not live to record
|
||||
// left behind names another pair), else the one-mechanism entry rebuilt
|
||||
// from the SA — for an empty or unparseable value too.
|
||||
struct ResumeSecurityVerify {
|
||||
enum class Source { Stored, NoneStored, OtherSa };
|
||||
Source source = Source::NoneStored;
|
||||
std::string value;
|
||||
std::size_t mechanisms = 0; // of the stored list, when it is used
|
||||
std::optional<std::uint32_t> storedSpiPs; // the stored list's spi-s (OtherSa); nullopt = none parsed
|
||||
};
|
||||
ResumeSecurityVerify ChooseResumeSecurityVerify(std::string_view stored, const ExistingSa& sa, std::string_view offeredEalg) {
|
||||
ResumeSecurityVerify r;
|
||||
r.value = sa.securityServer;
|
||||
if (stored.empty()) return r;
|
||||
r.source = ResumeSecurityVerify::Source::OtherSa;
|
||||
if (auto parsed = ParseSecurityServer(stored, "hmac-sha-1-96", offeredEalg)) {
|
||||
const SecurityMechanism& c = parsed->Carrier();
|
||||
r.storedSpiPs = c.spiPs;
|
||||
if (c.spiPc == sa.spiPc && c.spiPs == sa.spiPs && c.portPc == sa.portPc && c.portPs == sa.portPs) {
|
||||
r.source = ResumeSecurityVerify::Source::Stored;
|
||||
r.value = std::string(stored);
|
||||
r.mechanisms = parsed->mechanisms.size();
|
||||
}
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
// Every local address holding an SA to the P-CSCF, once each, in kernel
|
||||
// order: where a registration made on another address of the link (the
|
||||
// kernel's temporary one, before 0.3.10 — ims ledger F18) still lives.
|
||||
|
|
|
|||
|
|
@ -29,8 +29,9 @@ import :Sip;
|
|||
export namespace imsd::msg {
|
||||
|
||||
// Everything the builders need that is stable for the life of a
|
||||
// registration. Ports default to the fixed protected values; route/ppi/
|
||||
// securityServer are filled in from the REGISTER 200 OK.
|
||||
// registration. Ports default to the fixed protected values; route and
|
||||
// ppi are filled in from the REGISTER 200 OK, securityServer from the
|
||||
// 401 (at a resume, from the state file).
|
||||
struct Context {
|
||||
imsd::aka::Identity id;
|
||||
std::string local;
|
||||
|
|
@ -63,7 +64,7 @@ export namespace imsd::msg {
|
|||
// self-targeted requests like reg-event
|
||||
// SUBSCRIBE; the temporary (IMSI-derived)
|
||||
// IMPU is barred for anything but REGISTER
|
||||
std::string securityServer; // Security-Verify value (the network's ss)
|
||||
std::string securityServer; // Security-Verify value: the 401's Security-Server list (from the state file on a resume)
|
||||
// P-Access-Network-Info value (imsd::aka::ParsePani from live mmcli
|
||||
// location: real MCC/MNC/TAC/ECI, FDD). Empty omits the header —
|
||||
// never fabricate one: the network reads it to learn which access
|
||||
|
|
|
|||
Loading…
Reference in a new issue