LCOV - code coverage report
Current view: top level - pkg/storaged/crypto - encryption.jsx Coverage Total Hit
Test: cockpit Lines: 73.1 % 182 133
Test Date: 2026-07-17 12:03:54

            Line data    Source code
       1              : /*
       2              :  * Copyright (C) 2023 Red Hat, Inc.
       3              :  * SPDX-License-Identifier: LGPL-2.1-or-later
       4              :  */
       5              : 
       6          113 : import cockpit from "cockpit";
       7          113 : import React from "react";
       8              : import client from "../client";
       9              : 
      10              : import { DescriptionList } from "@patternfly/react-core/dist/esm/components/DescriptionList/index.js";
      11              : import { CardBody } from "@patternfly/react-core/dist/esm/components/Card/index.js";
      12              : import { useObject, useEvent } from "hooks";
      13              : import * as python from "python.js";
      14              : import * as timeformat from "timeformat";
      15              : 
      16              : import { dialog_open, TextInput, PassInput } from "../dialog.jsx";
      17              : import { block_name, encode_filename, decode_filename, parse_options, unparse_options, extract_option, edit_crypto_config } from "../utils.js";
      18              : import { StorageCard, StorageDescription, new_card } from "../pages.jsx";
      19              : import luksmeta_monitor_hack_py from "./luksmeta-monitor-hack.py";
      20              : import { is_mounted } from "../filesystem/utils.jsx";
      21              : import { StorageLink } from "../storage-controls.jsx";
      22              : import { CryptoKeyslots } from "./keyslots.jsx";
      23              : 
      24          113 : const _ = cockpit.gettext;
      25              : 
      26           24 : export function make_encryption_card(next, block) {
      27           24 :     return new_card({
      28           24 :         title: _("Encryption"),
      29           24 :         next,
      30           24 :         type_extra: _("encrypted"),
      31           24 :         component: EncryptionCard,
      32           24 :         props: { block },
      33           24 :     });
      34           24 : }
      35              : 
      36           17 : function monitor_luks(block) {
      37           17 :     const self = {
      38           17 :         stop,
      39              : 
      40           17 :         luks_version: null,
      41           17 :         slots: null,
      42           17 :         slot_error: null,
      43           17 :         max_slots: null,
      44           17 :     };
      45              : 
      46           17 :     cockpit.event_target(self);
      47              : 
      48           17 :     const dev = decode_filename(block.Device);
      49           17 :     const channel = python.spawn(luksmeta_monitor_hack_py, [dev], { superuser: "require" });
      50           17 :     let buf = "";
      51              : 
      52           15 :     channel.stream(output => {
      53           15 :         buf += output;
      54           15 :         const lines = buf.split("\n");
      55           15 :         buf = lines[lines.length - 1];
      56           15 :         if (lines.length >= 2) {
      57           15 :             const data = JSON.parse(lines[lines.length - 2]);
      58           15 :             self.slots = data.slots;
      59           15 :             self.luks_version = data.version;
      60           15 :             self.max_slots = data.max_slots;
      61           15 :             self.dispatchEvent("changed");
      62           15 :         }
      63           15 :     });
      64              : 
      65            0 :     channel.catch(err => {
      66            0 :         self.slots = [];
      67            0 :         self.slot_error = err;
      68            0 :         self.dispatchEvent("changed");
      69            0 :     });
      70              : 
      71            5 :     function stop() {
      72            5 :         channel.close();
      73            5 :     }
      74              : 
      75           17 :     return self;
      76           17 : }
      77              : 
      78            0 : function parse_tag_mtime(tag) {
      79            0 :     if (tag && tag.indexOf("1:") == 0) {
      80            0 :         try {
      81            0 :             const parts = tag.split("-")[1].split(".");
      82              :             // s:ns → ms
      83            0 :             const mtime = parseInt(parts[0]) * 1000 + parseInt(parts[1]) * 1e-6;
      84            0 :             return cockpit.format(_("Last modified: $0"), timeformat.dateTime(mtime));
      85            0 :         } catch {
      86            0 :             return null;
      87            0 :         }
      88            0 :     } else
      89            0 :         return null;
      90            0 : }
      91              : 
      92           17 : function monitor_mtime(path) {
      93           17 :     const self = {
      94           17 :         stop,
      95              : 
      96           17 :         mtime: 0
      97           17 :     };
      98              : 
      99           17 :     cockpit.event_target(self);
     100              : 
     101           17 :     let file = null;
     102            2 :     if (path) {
     103            2 :         file = cockpit.file(path, { superuser: "require" });
     104            0 :         file.watch((_, tag) => { self.mtime = parse_tag_mtime(tag); self.dispatchEvent("changed") },
     105            2 :                    { read: false });
     106            2 :     }
     107              : 
     108            6 :     function stop() {
     109            6 :         if (file)
     110            0 :             file.close();
     111            6 :     }
     112              : 
     113           17 :     return self;
     114           17 : }
     115              : 
     116           17 : const EncryptionCard = ({ card, block }) => {
     117           17 :     const luks_info = useObject(() => monitor_luks(block),
     118            5 :                                 m => m.stop(),
     119           17 :                                 [block]);
     120           17 :     useEvent(luks_info, "changed");
     121              : 
     122           17 :     let old_options;
     123           17 :     let passphrase_path;
     124           16 :     const old_config = block.Configuration.find(c => c[0] == "crypttab");
     125           16 :     if (old_config) {
     126           16 :         old_options = (decode_filename(old_config[1].options.v)
     127           16 :                 .split(",")
     128           16 :                 .filter(function (s) { return s.indexOf("x-parent") !== 0 })
     129           16 :                 .join(","));
     130           16 :         passphrase_path = decode_filename(old_config[1]["passphrase-path"].v);
     131           16 :     }
     132              : 
     133           17 :     const stored_passphrase_info = useObject(() => monitor_mtime(passphrase_path),
     134            6 :                                              m => m.stop(),
     135           17 :                                              [passphrase_path]);
     136           17 :     useEvent(stored_passphrase_info, "changed");
     137              : 
     138           17 :     const split_options = parse_options(old_options);
     139           17 :     let opt_noauto = extract_option(split_options, "noauto");
     140           17 :     const extra_options = unparse_options(split_options);
     141              : 
     142            0 :     function edit_stored_passphrase() {
     143            0 :         edit_crypto_config(block, function (config, commit) {
     144            0 :             dialog_open({
     145            0 :                 Title: _("Stored passphrase"),
     146            0 :                 Fields: [
     147            0 :                     PassInput("passphrase", _("Stored passphrase"),
     148            0 :                               {
     149            0 :                                   value: (config && config['passphrase-contents']
     150            0 :                                       ? decode_filename(config['passphrase-contents'].v)
     151            0 :                                       : "")
     152            0 :                               })
     153            0 :                 ],
     154            0 :                 Action: {
     155            0 :                     Title: _("Save"),
     156            0 :                     action: function (vals) {
     157            0 :                         config["passphrase-contents"] = {
     158            0 :                             t: 'ay',
     159            0 :                             v: encode_filename(vals.passphrase)
     160            0 :                         };
     161            0 :                         delete config["passphrase-path"];
     162            0 :                         return commit();
     163            0 :                     }
     164            0 :                 }
     165            0 :             });
     166            0 :         });
     167            0 :     }
     168              : 
     169            1 :     function edit_options() {
     170            0 :         const fsys_config = client.blocks_crypto[block.path]?.ChildConfiguration.find(c => c[0] == "fstab");
     171            1 :         const content_block = client.blocks_cleartext[block.path];
     172            0 :         const is_fsys = fsys_config || (content_block && content_block.IdUsage == "filesystem");
     173              : 
     174            1 :         edit_crypto_config(block, function (config, commit) {
     175            1 :             dialog_open({
     176            1 :                 Title: _("Encryption options"),
     177            1 :                 Fields: [
     178            1 :                     TextInput("options", "", { value: extra_options }),
     179            1 :                 ],
     180            1 :                 isFormHorizontal: false,
     181            1 :                 Action: {
     182            1 :                     Title: _("Save"),
     183            1 :                     action: function (vals) {
     184            1 :                         let opts = [];
     185            0 :                         if (is_fsys && content_block)
     186            0 :                             opt_noauto = !is_mounted(client, content_block);
     187            1 :                         if (opt_noauto)
     188            1 :                             opts.push("noauto");
     189            1 :                         opts = opts.concat(parse_options(vals.options));
     190            1 :                         config.options = {
     191            1 :                             t: 'ay',
     192            1 :                             v: encode_filename(unparse_options(opts))
     193            1 :                         };
     194            1 :                         return commit();
     195            1 :                     }
     196            1 :                 }
     197            1 :             });
     198            1 :         });
     199            1 :     }
     200              : 
     201           17 :     const cleartext = client.blocks_cleartext[block.path];
     202              : 
     203           17 :     const option_parts = [];
     204           17 :     if (extra_options)
     205           13 :         option_parts.push(extra_options);
     206           17 :     const options = option_parts.join(", ");
     207              : 
     208           17 :     return (
     209           17 :         <StorageCard card={card}>
     210           17 :             <CardBody>
     211           17 :                 <DescriptionList className="pf-m-horizontal-on-sm wide-label">
     212           17 :                     <StorageDescription title={_("Encryption type")}>
     213           16 :                         { luks_info.luks_version ? "LUKS" + luks_info.luks_version : "-" }
     214           17 :                     </StorageDescription>
     215           17 :                     <StorageDescription title={_("Cleartext device")}>
     216           10 :                         {cleartext ? block_name(cleartext) : "-"}
     217           17 :                     </StorageDescription>
     218           17 :                     <StorageDescription title={_("Stored passphrase")}
     219            2 :                            value={ passphrase_path ? stored_passphrase_info.mtime || _("yes") : _("none") }
     220           17 :                            action={<StorageLink onClick={edit_stored_passphrase}>{_("edit")}</StorageLink>} />
     221           17 :                     <StorageDescription title={_("Options")}
     222            9 :                            value={options || _("none")}
     223           17 :                            action={<StorageLink onClick={edit_options}>{_("edit")}</StorageLink>} />
     224           17 :                 </DescriptionList>
     225           17 :             </CardBody>
     226           17 :             <CryptoKeyslots client={client} block={block}
     227           17 :                             slots={luks_info.slots} slot_error={luks_info.slot_error}
     228           17 :                             max_slots={luks_info.max_slots} />
     229           17 :         </StorageCard>);
     230           17 : };
        

Generated by: LCOV version 2.0-1