Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
pack_signing_ui.hpp
Go to the documentation of this file.
1#pragma once
2
3// Human-facing helpers for the swap-pack signing flow: consistent key names (so a
4// key is easy to find, import, rotate, and revoke), a summary of exactly what a
5// signature will cover, and a loud provenance banner for a freshly generated key.
6// These are pure string builders — the CLI and any tool surface reuse them so the
7// developer sees identical guidance everywhere. No I/O, no crypto here.
8
10
11#include <algorithm>
12#include <cctype>
13#include <string>
14#include <string_view>
15#include <vector>
16
17namespace pulp::format::reload {
18
19enum class KeyRole { Signing, Revocation };
20
21inline std::string_view key_role_name(KeyRole r) {
22 return r == KeyRole::Signing ? "signing" : "revocation";
23}
24
27inline std::string reload_keychain_service(KeyRole role, std::string_view plugin_id) {
28 return "pulp.reload." + std::string(key_role_name(role)) + "." + std::string(plugin_id);
29}
30
34inline std::string reload_key_account(int generation, std::string_view fpr_hex) {
35 std::string fpr8(fpr_hex.substr(0, std::min<std::size_t>(8, fpr_hex.size())));
36 return "gen" + std::to_string(generation) + (fpr8.empty() ? "" : "-" + fpr8);
37}
38
40inline std::string to_secret_token(std::string_view s) {
41 std::string out;
42 out.reserve(s.size());
43 for (char c : s) {
44 if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9'))
45 out.push_back(static_cast<char>(std::toupper(static_cast<unsigned char>(c))));
46 else
47 out.push_back('_');
48 }
49 return out;
50}
51
54inline std::string reload_github_secret_name(KeyRole role, std::string_view plugin_id,
55 int generation) {
56 return "PULP_RELOAD_" + to_secret_token(key_role_name(role)) + "_KEY_" +
57 to_secret_token(plugin_id) + "_GEN" + std::to_string(generation);
58}
59
62inline std::string normalize_github_repo(std::string_view repo) {
63 if (repo.empty()) return {};
64 std::string r;
65 for (char c : repo) r.push_back(static_cast<char>(std::tolower(static_cast<unsigned char>(c))));
66 if (r.size() > 4 && r.substr(r.size() - 4) == ".git") r.resize(r.size() - 4);
67 if (auto at = r.find('@'); at != std::string::npos) r = r.substr(at + 1); // scp-style
68 for (const char* scheme : {"https://", "http://", "ssh://", "git://"}) {
69 const std::size_t n = std::string_view(scheme).size();
70 if (r.rfind(scheme, 0) == 0) { r = r.substr(n); break; }
71 }
72 if (auto host = r.find("github.com"); host != std::string::npos) {
73 // Host/path separator is '/' for URLs and ':' for scp-style remotes.
74 const std::size_t sep = r.find_first_of(":/", host);
75 if (sep != std::string::npos) r = r.substr(sep + 1);
76 }
77 while (!r.empty() && (r.front() == '/' || r.front() == ':')) r.erase(r.begin());
78 const std::size_t slash = r.find('/');
79 if (slash == std::string::npos || slash == 0 || slash + 1 >= r.size()) return {};
80 if (r.find('/', slash + 1) != std::string::npos) return {}; // extra path segments
81 return r;
82}
83
88inline bool github_backup_repo_allowed(std::string_view repo) {
89 const std::string r = normalize_github_repo(repo);
90 // Block BOTH core-repo names. The framework moved danielraffel/pulp ->
91 // Generous-Corp/pulp; GitHub still redirects the old name to the new repo,
92 // so a key targeting EITHER would land in the core repo. Blocking only one
93 // leaves a hole — the old name still resolves to core, and the new name IS
94 // core. `normalize_github_repo` lowercases, so compare lowercased.
95 return !r.empty() && r != "danielraffel/pulp" && r != "generous-corp/pulp";
96}
97
100inline std::string swap_pack_signing_summary(const SwapPackManifest& m) {
101 std::string s;
102 s += "About to sign swap pack:\n";
103 s += " id: " + m.id + "\n";
104 s += " plugin: " + m.plugin_id + "\n";
105 s += " pack version: " + std::to_string(m.pack_version) + "\n";
106 s += " pack type: " + std::string(swap_pack_kind_to_string(m.pack_type)) + "\n";
107 s += " update channel:" + (m.update_channel.empty() ? std::string(" (none)")
108 : " " + m.update_channel) + "\n";
109 s += " min host: " + std::to_string(m.min_host_version) + "\n";
110 s += " capabilities: ";
111 if (m.declared_capabilities.empty()) {
112 s += "(none — pure UI)";
113 } else {
114 for (std::size_t i = 0; i < m.declared_capabilities.size(); ++i)
115 s += (i ? ", " : "") + m.declared_capabilities[i];
116 }
117 s += "\n files:\n";
118 for (const auto& f : m.files) {
119 const std::string h8 = f.sha256_hex.substr(0, std::min<std::size_t>(8, f.sha256_hex.size()));
120 s += " " + f.path + " [" + std::string(swap_pack_kind_to_string(f.kind)) +
121 "] " + h8 + "…\n";
122 }
123 return s;
124}
125
129inline std::string key_generation_banner(KeyRole role, std::string_view keychain_service,
130 std::string_view plugin_id) {
131 const bool signing = role == KeyRole::Signing;
132 std::string s;
133 s += "════════════════════════════════════════════════════════════════\n";
134 s += std::string(" Pulp generated a new ") + std::string(key_role_name(role)) +
135 " key for " + std::string(plugin_id) + ".\n";
136 s += " Stored in your keychain as: " + std::string(keychain_service) + "\n";
137 s += " THIS KEY IS YOUR PLUGIN'S IDENTITY — back it up now.\n";
138 if (signing)
139 s += " • Lose it and you cannot ship signed updates for this plugin.\n";
140 else
141 s += " • Keep it OFFLINE. Lose it and you cannot revoke a leaked signing key.\n";
142 s += " • Leak it and an attacker can sign code as you.\n";
143 s += " Pulp will reuse this key; it never silently generates a second one.\n";
144 s += "════════════════════════════════════════════════════════════════\n";
145 return s;
146}
147
148} // namespace pulp::format::reload
Definition build_fingerprint.hpp:23
std::string reload_github_secret_name(KeyRole role, std::string_view plugin_id, int generation)
GitHub Actions secret name, same shape as the keychain id but secret-safe: PULP_RELOAD_<ROLE>_KEY_<PL...
Definition pack_signing_ui.hpp:54
std::string normalize_github_repo(std::string_view repo)
Normalize a repo string (owner/name, URL, or scp-style; strips .git + host) to lowercase owner/name,...
Definition pack_signing_ui.hpp:62
std::string reload_keychain_service(KeyRole role, std::string_view plugin_id)
Keychain service id: pulp.reload.
Definition pack_signing_ui.hpp:27
const char * swap_pack_kind_to_string(SwapPackKind k)
Stable canonical spelling of a kind, for the signed message (never localized / reformatted).
Definition swap_pack.hpp:49
bool github_backup_repo_allowed(std::string_view repo)
A signing key may be published as a GitHub Actions secret ONLY into a plugin's own repository — never...
Definition pack_signing_ui.hpp:88
KeyRole
Definition pack_signing_ui.hpp:19
@ Signing
Definition pack_signing_ui.hpp:19
@ Revocation
Definition pack_signing_ui.hpp:19
std::string swap_pack_signing_summary(const SwapPackManifest &m)
A readable summary of everything the signature will cover, so the signer can confirm exactly what the...
Definition pack_signing_ui.hpp:100
std::string key_generation_banner(KeyRole role, std::string_view keychain_service, std::string_view plugin_id)
The loud, no-surprise banner shown when Pulp generates a key the developer did not already have.
Definition pack_signing_ui.hpp:129
std::string reload_key_account(int generation, std::string_view fpr_hex)
Keychain account/label carrying the generation and a short fingerprint prefix, e.g.
Definition pack_signing_ui.hpp:34
std::string to_secret_token(std::string_view s)
Upper-snake, secret-name-safe form of an identifier (non-alnum → _).
Definition pack_signing_ui.hpp:40
std::string_view key_role_name(KeyRole r)
Definition pack_signing_ui.hpp:21
Bounded event contracts shared by host ingress and graph-authored nodes.
Definition anticipation_eligibility.hpp:9
Definition swap_pack.hpp:66
Sealed swap-pack manifest + per-file integrity.