diff --git a/pkg/storaged/crypto-keyslots.jsx b/pkg/storaged/crypto-keyslots.jsx index e6a619f70061..e1af2e0de0cb 100644 --- a/pkg/storaged/crypto-keyslots.jsx +++ b/pkg/storaged/crypto-keyslots.jsx @@ -52,7 +52,7 @@ const _ = cockpit.gettext; /* Tang advertisement utilities */ -function get_tang_adv(url) { +export function get_tang_adv(url) { return cockpit.spawn(["curl", "-sSf", url + "/adv"], { err: "message" }) .then(JSON.parse) .catch(error => { @@ -60,7 +60,7 @@ function get_tang_adv(url) { }); } -function tang_adv_payload(adv) { +export function tang_adv_payload(adv) { return JSON.parse(cockpit.utf8_decoder().decode(cockpit.base64_decode(adv.payload))); } @@ -92,7 +92,7 @@ function compute_thp(jwk) { }; } -function compute_sigkey_thps(adv) { +export function compute_sigkey_thps(adv) { function is_signing_key(jwk) { if (!jwk.use && !jwk.key_ops) return true; @@ -474,7 +474,7 @@ function ensure_nbde_support_dialog(steps, client, block, url, adv, old_key, exi }); } -function parse_url(url) { +export function parse_url(url) { // clevis-encrypt-tang defaults to "http://" (via curl), so we do the same here. if (!/^[a-zA-Z]+:\/\//.test(url)) url = "http://" + url; @@ -487,7 +487,7 @@ function parse_url(url) { } } -function validate_url(url) { +export function validate_url(url) { if (url.length === 0) return _("Address cannot be empty"); if (!parse_url(url)) diff --git a/pkg/storaged/stratis-details.jsx b/pkg/storaged/stratis-details.jsx index c2c1fd42fcdb..8414f147ca61 100644 --- a/pkg/storaged/stratis-details.jsx +++ b/pkg/storaged/stratis-details.jsx @@ -21,6 +21,7 @@ import cockpit from "cockpit"; import React from "react"; import { Card, CardBody, CardHeader, CardTitle } from '@patternfly/react-core/dist/esm/components/Card/index.js'; +import { ClipboardCopy } from "@patternfly/react-core/dist/esm/components/ClipboardCopy/index.js"; import { DescriptionList, DescriptionListDescription, DescriptionListGroup, DescriptionListTerm } from "@patternfly/react-core/dist/esm/components/DescriptionList/index.js"; import { List, ListItem } from "@patternfly/react-core/dist/esm/components/List/index.js"; import { PlusIcon, ExclamationTriangleIcon } from "@patternfly/react-icons"; @@ -36,6 +37,7 @@ import { TextInput, PassInput, SelectOne, SelectSpaces, CheckBoxes, BlockingMessage, TeardownMessage, + Skip, init_active_usage_processes } from "./dialog.jsx"; @@ -48,6 +50,7 @@ import { } from "./utils.js"; import { fmt_to_fragments } from "utils.jsx"; import { mount_explanation } from "./format-dialog.jsx"; +import { validate_url, get_tang_adv, parse_url, tang_adv_payload, compute_sigkey_thps } from "./crypto-keyslots.jsx"; const _ = cockpit.gettext; @@ -263,6 +266,127 @@ export const StratisPoolDetails = ({ client, pool }) => { }); } + function add_tang() { + const key_desc = pool.KeyDescription[1][1]; + return client.stratis_list_keys() + .then(keys => { + if (keys.indexOf(key_desc) >= 0) + add_tang_with_keydesc(false); + else + add_tang_with_keydesc(key_desc); + }) + .catch(ex => { + console.warn("Failed fetch properties", ex.toString()); + }); + } + + function add_tang_with_keydesc(key_desc) { + dialog_open({ + Title: _("Add Tang keyserver"), + Fields: [ + TextInput("tang_url", _("Keyserver address"), + { + validate: validate_url + }), + Skip("medskip", + { + visible: () => !!key_desc + }), + PassInput("passphrase", _("Disk passphrase"), + { + visible: () => !!key_desc, + validate: val => !val.length && _("Passphrase cannot be empty"), + explanation: _("Adding a keyserver requires unlocking the pool. Please provide the existing pool passphrase.") + }) + ], + Action: { + Title: _("Add"), + action: function (vals, progress) { + return get_tang_adv(vals.tang_url) + .then(adv => confirm_tang_trust(vals.tang_url, adv, key_desc, vals.passphrase)); + } + } + }); + } + + function confirm_tang_trust(url, adv, key_desc, passphrase) { + const parsed = parse_url(url); + const cmd = cockpit.format("ssh $0 tang-show-keys $1", parsed.hostname, parsed.port); + + const sigkey_thps = compute_sigkey_thps(tang_adv_payload(adv)); + + dialog_open({ + Title: _("Verify key"), + Body: ( + <> +
{_("Make sure the key hash from the Tang server matches one of the following:")}
+ +{s.sha256}
) } + +{s.sha1}
) } + +
+ {_("Manually check with SSH: ")}
+
{ fmt_to_fragments(_("Remove $0?"), {tang_url}) }
+{ fmt_to_fragments(_("Keyserver removal may prevent unlocking $0."), {pool.Name}) }
+