gitee.com/mysnapcore/mysnapd@v0.1.0/secboot/keymgr/keymgr_luks2.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 keymgr 21 22 import ( 23 "fmt" 24 "regexp" 25 "time" 26 27 sb "gitee.com/mysnapcore/mysecboot" 28 29 "gitee.com/mysnapcore/mysnapd/osutil" 30 "gitee.com/mysnapcore/mysnapd/secboot/keys" 31 "gitee.com/mysnapcore/mysnapd/secboot/luks2" 32 ) 33 34 const ( 35 // key slot used by the encryption key 36 encryptionKeySlot = 0 37 // key slot used by the recovery key 38 recoveryKeySlot = 1 39 // temporary key slot used when changing the encryption key 40 tempKeySlot = recoveryKeySlot + 1 41 ) 42 43 var ( 44 sbGetDiskUnlockKeyFromKernel = sb.GetDiskUnlockKeyFromKernel 45 ) 46 47 func getEncryptionKeyFromUserKeyring(dev string) ([]byte, error) { 48 const remove = false 49 const defaultPrefix = "ubuntu-fde" 50 // note this is the unlock key, which can be either the main key which 51 // was unsealed, or the recovery key, in which case some operations may 52 // not make sense 53 currKey, err := sbGetDiskUnlockKeyFromKernel(defaultPrefix, dev, remove) 54 if err != nil { 55 return nil, fmt.Errorf("cannot obtain current unlock key for %v: %v", dev, err) 56 } 57 return currKey, err 58 } 59 60 // TODO rather than inspecting the error messages, parse the LUKS2 headers 61 62 var keyslotFull = regexp.MustCompile(`^.*cryptsetup failed with: Key slot [0-9]+ is full, please select another one\.$`) 63 64 // IsKeyslotAlreadyUsed returns true if the error indicates that the keyslot 65 // attempted for a given key is already used 66 func IsKeyslotAlreadyUsed(err error) bool { 67 if err == nil { 68 return false 69 } 70 return keyslotFull.MatchString(err.Error()) 71 } 72 73 func isKeyslotNotActive(err error) bool { 74 match, _ := regexp.MatchString(`.*: Keyslot [0-9]+ is not active`, err.Error()) 75 return match 76 } 77 78 func recoveryKDF() (*luks2.KDFOptions, error) { 79 usableMem, err := osutil.TotalUsableMemory() 80 if err != nil { 81 return nil, fmt.Errorf("cannot get usable memory for KDF parameters when adding the recovery key: %v", err) 82 } 83 // The KDF memory is heuristically calculated by taking the 84 // usable memory and subtracting hardcoded 384MB that is 85 // needed to keep the system working. Half of that is the mem 86 // we want to use for the KDF. Doing it this way avoids the expensive 87 // benchmark from cryptsetup. The recovery key is already 128bit 88 // strong so we don't need to be super precise here. 89 kdfMem := (int(usableMem) - 384*1024*1024) / 2 90 // at most 1 GB, but at least 32 kB 91 if kdfMem > 1024*1024*1024 { 92 kdfMem = (1024 * 1024 * 1024) 93 } else if kdfMem < 32*1024 { 94 kdfMem = 32 * 1024 95 } 96 return &luks2.KDFOptions{ 97 MemoryKiB: kdfMem / 1024, 98 ForceIterations: 4, 99 }, nil 100 } 101 102 // AddRecoveryKeyToLUKSDevice adds a recovery key to a LUKS2 device. It the 103 // devuce unlock key from the user keyring to authorize the change. The 104 // recoveyry key is added to keyslot 1. 105 func AddRecoveryKeyToLUKSDevice(recoveryKey keys.RecoveryKey, dev string) error { 106 currKey, err := getEncryptionKeyFromUserKeyring(dev) 107 if err != nil { 108 return err 109 } 110 111 return AddRecoveryKeyToLUKSDeviceUsingKey(recoveryKey, currKey, dev) 112 } 113 114 // AddRecoveryKeyToLUKSDeviceUsingKey adds a recovery key rkey to the existing 115 // LUKS encrypted volume on the block device given by node. The existing key to 116 // the encrypted volume is provided in the key argument and used to authorize 117 // the operation. 118 // 119 // A heuristic memory cost is used. 120 func AddRecoveryKeyToLUKSDeviceUsingKey(recoveryKey keys.RecoveryKey, currKey keys.EncryptionKey, dev string) error { 121 opts, err := recoveryKDF() 122 if err != nil { 123 return err 124 } 125 126 options := luks2.AddKeyOptions{ 127 KDFOptions: *opts, 128 Slot: recoveryKeySlot, 129 } 130 if err := luks2.AddKey(dev, currKey, recoveryKey[:], &options); err != nil { 131 return fmt.Errorf("cannot add key: %v", err) 132 } 133 134 if err := luks2.SetSlotPriority(dev, encryptionKeySlot, luks2.SlotPriorityHigh); err != nil { 135 return fmt.Errorf("cannot change keyslot priority: %v", err) 136 } 137 138 return nil 139 } 140 141 // RemoveRecoveryKeyFromLUKSDevice removes an existing recovery key a LUKS2 142 // device. 143 func RemoveRecoveryKeyFromLUKSDevice(dev string) error { 144 currKey, err := getEncryptionKeyFromUserKeyring(dev) 145 if err != nil { 146 return err 147 } 148 return RemoveRecoveryKeyFromLUKSDeviceUsingKey(currKey, dev) 149 } 150 151 // RemoveRecoveryKeyFromLUKSDeviceUsingKey removes an existing recovery key a 152 // LUKS2 using the provided key to authorize the operation. 153 func RemoveRecoveryKeyFromLUKSDeviceUsingKey(currKey keys.EncryptionKey, dev string) error { 154 // just remove the key we think is a recovery key (luks keyslot 1) 155 if err := luks2.KillSlot(dev, recoveryKeySlot, currKey); err != nil { 156 if !isKeyslotNotActive(err) { 157 return fmt.Errorf("cannot kill recovery key slot: %v", err) 158 } 159 } 160 return nil 161 } 162 163 // StageLUKSDeviceEncryptionKeyChange stages a new encryption key with the goal 164 // of changing the main encryption key referenced in keyslot 0. The operation is 165 // authorized using the key that unlocked the device and is stored in the 166 // keyring (as it happens during factory reset). 167 func StageLUKSDeviceEncryptionKeyChange(newKey keys.EncryptionKey, dev string) error { 168 if len(newKey) != keys.EncryptionKeySize { 169 return fmt.Errorf("cannot use a key of size different than %v", keys.EncryptionKeySize) 170 } 171 172 // the key to authorize the device is in the keyring 173 currKey, err := getEncryptionKeyFromUserKeyring(dev) 174 if err != nil { 175 return err 176 } 177 178 // TODO rather than inspecting the errors, parse the LUKS2 headers 179 180 // free up the temp slot 181 if err := luks2.KillSlot(dev, tempKeySlot, currKey); err != nil { 182 if !isKeyslotNotActive(err) { 183 return fmt.Errorf("cannot kill the temporary keyslot: %v", err) 184 } 185 } 186 187 options := luks2.AddKeyOptions{ 188 KDFOptions: luks2.KDFOptions{TargetDuration: 100 * time.Millisecond}, 189 Slot: tempKeySlot, 190 } 191 if err := luks2.AddKey(dev, currKey[:], newKey, &options); err != nil { 192 return fmt.Errorf("cannot add temporary key: %v", err) 193 } 194 return nil 195 } 196 197 // TransitionLUKSDeviceEncryptionKeyChange completes the main encryption key 198 // change to the new key provided in the parameters. The new key must have been 199 // staged before, thus it can authorize LUKS operations. Lastly, the unlock key 200 // in the keyring is updated to the new key. 201 func TransitionLUKSDeviceEncryptionKeyChange(newKey keys.EncryptionKey, dev string) error { 202 if len(newKey) != keys.EncryptionKeySize { 203 return fmt.Errorf("cannot use a key of size different than %v", keys.EncryptionKeySize) 204 } 205 206 // the expected state is as follows: 207 // key slot 0 - the old encryption key 208 // key slot 2 - the new encryption key (added during --stage) 209 // the desired state is: 210 // key slot 0 - the new encryption key 211 // key slot 2 - empty 212 // it is possible that the system was rebooted right after key slot 0 was 213 // populated with the new key and key slot 2 was emptied 214 215 // there is no state information on disk which would tell if the 216 // scenario 1 above occurred and to which stage it was executed, but we 217 // need to find out if key slot 2 is in use (as the caller believes that 218 // a key was staged earlier); do this indirectly by trying to add a key 219 // to key slot 2 220 221 // TODO rather than inspecting the errors, parse the LUKS2 headers 222 223 tempKeyslotAlreadyUsed := true 224 225 options := luks2.AddKeyOptions{ 226 KDFOptions: luks2.KDFOptions{TargetDuration: 100 * time.Millisecond}, 227 Slot: tempKeySlot, 228 } 229 err := luks2.AddKey(dev, newKey, newKey, &options) 230 if err == nil { 231 // key slot is not in use, so we are dealing with unexpected reboot scenario 232 tempKeyslotAlreadyUsed = false 233 } else if err != nil && !IsKeyslotAlreadyUsed(err) { 234 return fmt.Errorf("cannot add new encryption key: %v", err) 235 } 236 237 if !tempKeyslotAlreadyUsed { 238 // since the key slot was not used, it means that the transition 239 // was already carried out (since it got authorized by the new 240 // key), so now all is needed is to remove the added key 241 if err := luks2.KillSlot(dev, tempKeySlot, newKey); err != nil { 242 return fmt.Errorf("cannot kill temporary key slot: %v", err) 243 } 244 245 return nil 246 } 247 248 // first kill the main encryption key slot, authorize the operation 249 // using the new key which must have been added to the temp keyslot in 250 // the stage operation 251 if err := luks2.KillSlot(dev, encryptionKeySlot, newKey); err != nil { 252 if !isKeyslotNotActive(err) { 253 return fmt.Errorf("cannot kill the encryption key slot: %v", err) 254 } 255 } 256 257 options = luks2.AddKeyOptions{ 258 KDFOptions: luks2.KDFOptions{TargetDuration: 100 * time.Millisecond}, 259 Slot: encryptionKeySlot, 260 } 261 if err := luks2.AddKey(dev, newKey, newKey, &options); err != nil { 262 return fmt.Errorf("cannot add new encryption key: %v", err) 263 } 264 265 // now it should be possible to kill the temporary keyslot by using the 266 // new key for authorization 267 if err := luks2.KillSlot(dev, tempKeySlot, newKey); err != nil { 268 if !isKeyslotNotActive(err) { 269 return fmt.Errorf("cannot kill temporary key slot: %v", err) 270 } 271 } 272 // TODO needed? 273 if err := luks2.SetSlotPriority(dev, encryptionKeySlot, luks2.SlotPriorityHigh); err != nil { 274 return fmt.Errorf("cannot change key slot priority: %v", err) 275 } 276 return nil 277 }