gitee.com/mysnapcore/mysnapd@v0.1.0/secboot/luks2/cryptsetup.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2022 Canonical Ltd 5 * 6 * This program is free software: you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License version 3 as 8 * published by the Free Software Foundation. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program. If not, see <http://www.gnu.org/licenses/>. 17 * 18 */ 19 20 package luks2 21 22 import ( 23 "bytes" 24 "fmt" 25 "io" 26 "os" 27 "os/exec" 28 "strconv" 29 "time" 30 31 "gitee.com/mysnapcore/mysnapd/osutil" 32 33 "golang.org/x/xerrors" 34 ) 35 36 const ( 37 // AnySlot tells a command to automatically choose an appropriate slot 38 // as opposed to hard coding one. 39 AnySlot = -1 40 ) 41 42 // cryptsetupCmd is a helper for running the cryptsetup command. If stdin is supplied, data read 43 // from it is supplied to cryptsetup via its stdin. If callback is supplied, it will be invoked 44 // after cryptsetup has started. 45 func cryptsetupCmd(stdin io.Reader, callback func(cmd *exec.Cmd) error, args ...string) error { 46 cmd := exec.Command("cryptsetup", args...) 47 cmd.Stdin = stdin 48 49 var b bytes.Buffer 50 cmd.Stdout = &b 51 cmd.Stderr = &b 52 53 if err := cmd.Start(); err != nil { 54 return xerrors.Errorf("cannot start cryptsetup: %w", err) 55 } 56 57 var cbErr error 58 if callback != nil { 59 cbErr = callback(cmd) 60 } 61 62 err := cmd.Wait() 63 64 switch { 65 case cbErr != nil: 66 return cbErr 67 case err != nil: 68 return fmt.Errorf("cryptsetup failed with: %v", osutil.OutputErr(b.Bytes(), err)) 69 } 70 71 return nil 72 } 73 74 // KDFOptions specifies parameters for the Argon2 KDF. 75 type KDFOptions struct { 76 // TargetDuration specifies the target time for benchmarking of the 77 // time and memory cost parameters. If it is zero then the cryptsetup 78 // default is used. If ForceIterations is not zero then this is ignored. 79 TargetDuration time.Duration 80 81 // MemoryKiB specifies the maximum memory cost in KiB when ForceIterations 82 // is zero, or the actual memory cost in KiB when ForceIterations is not zero. 83 // If this is set to zero, then the cryptsetup default is used. 84 MemoryKiB int 85 86 // ForceIterations specifies the time cost. If set to zero, the time 87 // and memory cost are determined by benchmarking the algorithm based on 88 // the specified TargetDuration. Set to a non-zero number to force the 89 // time cost to the value of this field, and the memory cost to the value 90 // of MemoryKiB, disabling benchmarking. 91 ForceIterations int 92 93 // Parallel sets the maximum number of parallel threads. Cryptsetup may 94 // choose a lower value based on its own maximum and the number of available 95 // CPU cores. 96 Parallel int 97 } 98 99 func (options *KDFOptions) appendArguments(args []string) []string { 100 // use argon2i as the KDF 101 args = append(args, "--pbkdf", "argon2i") 102 103 switch { 104 case options.ForceIterations != 0: 105 // Disable benchmarking by forcing the time cost 106 args = append(args, 107 "--pbkdf-force-iterations", strconv.Itoa(options.ForceIterations)) 108 case options.TargetDuration != 0: 109 args = append(args, 110 "--iter-time", strconv.FormatInt(int64(options.TargetDuration/time.Millisecond), 10)) 111 } 112 113 if options.MemoryKiB != 0 { 114 args = append(args, "--pbkdf-memory", strconv.Itoa(options.MemoryKiB)) 115 } 116 117 if options.Parallel != 0 { 118 args = append(args, "--pbkdf-parallel", strconv.Itoa(options.Parallel)) 119 } 120 121 return args 122 } 123 124 // AddKeyOptions provides the options for adding a key to a LUKS2 volume 125 type AddKeyOptions struct { 126 // KDFOptions describes the KDF options for the new key slot. 127 KDFOptions KDFOptions 128 129 // Slot is the keyslot to use. Note that the default value is slot 0. In 130 // order to automatically choose a slot, use AnySlot. 131 Slot int 132 } 133 134 // AddKey adds the supplied key in to a new keyslot for specified LUKS2 container. In order to do this, 135 // an existing key must be provided. The KDF for the new keyslot will be configured to use argon2i with 136 // the supplied benchmark time. The key will be added to the supplied slot. 137 // 138 // If options is not supplied, the default KDF benchmark time is used and the command will 139 // automatically choose an appropriate slot. 140 func AddKey(devicePath string, existingKey, key []byte, options *AddKeyOptions) error { 141 if options == nil { 142 options = &AddKeyOptions{Slot: AnySlot} 143 } 144 145 fifoPath, cleanupFifo, err := mkFifo() 146 if err != nil { 147 return xerrors.Errorf("cannot create FIFO for passing existing key to cryptsetup: %w", err) 148 } 149 defer cleanupFifo() 150 151 args := []string{ 152 // add a new key 153 "luksAddKey", 154 // LUKS2 only 155 "--type", "luks2", 156 // read existing key from named pipe 157 "--key-file", fifoPath} 158 159 // apply KDF options 160 args = options.KDFOptions.appendArguments(args) 161 162 if options.Slot != AnySlot { 163 args = append(args, "--key-slot", strconv.Itoa(options.Slot)) 164 } 165 166 args = append(args, 167 // container to add key to 168 devicePath, 169 // read new key from stdin. 170 // Note that we can't supply the new key and existing key via the same channel 171 // because pipes and FIFOs aren't seekable - we would need to use an actual file 172 // in order to be able to do this. 173 "-") 174 175 writeExistingKeyToFifo := func(cmd *exec.Cmd) error { 176 f, err := os.OpenFile(fifoPath, os.O_WRONLY, 0) 177 if err != nil { 178 // If we fail to open the write end, the read end will be blocked in open(), so 179 // kill the process. 180 cmd.Process.Kill() 181 return xerrors.Errorf("cannot open FIFO for passing existing key to cryptsetup: %w", err) 182 } 183 184 if _, err := f.Write(existingKey); err != nil { 185 // The read end is open and blocked inside read(). Closing our write end will result in the 186 // read end returning 0 bytes (EOF) and continuing cleanly. 187 if err := f.Close(); err != nil { 188 // If we can't close the write end, the read end will remain blocked inside read(), 189 // so kill the process. 190 cmd.Process.Kill() 191 } 192 return xerrors.Errorf("cannot pass existing key to cryptsetup: %w", err) 193 } 194 195 if err := f.Close(); err != nil { 196 // If we can't close the write end, the read end will remain blocked inside read(), 197 // so kill the process. 198 cmd.Process.Kill() 199 return xerrors.Errorf("cannot close write end of FIFO: %w", err) 200 } 201 202 return nil 203 } 204 205 return cryptsetupCmd(bytes.NewReader(key), writeExistingKeyToFifo, args...) 206 } 207 208 // KillSlot erases the keyslot with the supplied slot number from the specified LUKS2 container. 209 // Note that a valid key for a remaining keyslot must be supplied, in order to prevent the last 210 // keyslot from being erased. 211 func KillSlot(devicePath string, slot int, key []byte) error { 212 return cryptsetupCmd(bytes.NewReader(key), nil, "luksKillSlot", "--type", "luks2", "--key-file", "-", devicePath, strconv.Itoa(slot)) 213 } 214 215 // SetSlotPriority sets the priority of the keyslot with the supplied slot number on 216 // the specified LUKS2 container. 217 func SetSlotPriority(devicePath string, slot int, priority SlotPriority) error { 218 return cryptsetupCmd(nil, nil, "config", "--priority", priority.String(), "--key-slot", strconv.Itoa(slot), devicePath) 219 }