gitee.com/mysnapcore/mysnapd@v0.1.0/cmd/snap-fde-keymgr/main.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 main 21 22 import ( 23 "encoding/json" 24 "fmt" 25 "io" 26 "io/ioutil" 27 "os" 28 "strings" 29 30 "github.com/jessevdk/go-flags" 31 32 "gitee.com/mysnapcore/mysnapd/osutil" 33 "gitee.com/mysnapcore/mysnapd/secboot/keymgr" 34 "gitee.com/mysnapcore/mysnapd/secboot/keys" 35 ) 36 37 var osStdin io.Reader = os.Stdin 38 39 type commonMultiDeviceMixin struct { 40 Devices []string `long:"devices" description:"encrypted devices (can be more than one)" required:"yes"` 41 Authorizations []string `long:"authorizations" description:"authorization sources (one for each device, either 'keyring' or 'file:<key-file>')" required:"yes"` 42 } 43 44 type cmdAddRecoveryKey struct { 45 commonMultiDeviceMixin 46 KeyFile string `long:"key-file" description:"path for generated recovery key file" required:"yes"` 47 } 48 49 type cmdRemoveRecoveryKey struct { 50 commonMultiDeviceMixin 51 KeyFiles []string `long:"key-files" description:"path to recovery key files to be removed" required:"yes"` 52 } 53 54 type cmdChangeEncryptionKey struct { 55 Device string `long:"device" description:"encrypted device" required:"yes"` 56 Stage bool `long:"stage" description:"stage the new key"` 57 Transition bool `long:"transition" description:"replace the old key, unstage the new"` 58 } 59 60 type options struct { 61 CmdAddRecoveryKey cmdAddRecoveryKey `command:"add-recovery-key"` 62 CmdRemoveRecoveryKey cmdRemoveRecoveryKey `command:"remove-recovery-key"` 63 CmdChangeEncryptionKey cmdChangeEncryptionKey `command:"change-encryption-key"` 64 } 65 66 var ( 67 keymgrAddRecoveryKeyToLUKSDevice = keymgr.AddRecoveryKeyToLUKSDevice 68 keymgrAddRecoveryKeyToLUKSDeviceUsingKey = keymgr.AddRecoveryKeyToLUKSDeviceUsingKey 69 keymgrRemoveRecoveryKeyFromLUKSDevice = keymgr.RemoveRecoveryKeyFromLUKSDevice 70 keymgrRemoveRecoveryKeyFromLUKSDeviceUsingKey = keymgr.RemoveRecoveryKeyFromLUKSDeviceUsingKey 71 keymgrStageLUKSDeviceEncryptionKeyChange = keymgr.StageLUKSDeviceEncryptionKeyChange 72 keymgrTransitionLUKSDeviceEncryptionKeyChange = keymgr.TransitionLUKSDeviceEncryptionKeyChange 73 ) 74 75 func validateAuthorizations(authorizations []string) error { 76 for _, authz := range authorizations { 77 switch { 78 case authz == "keyring": 79 // happy 80 case strings.HasPrefix(authz, "file:"): 81 // file must exist 82 kf := authz[len("file:"):] 83 if !osutil.FileExists(kf) { 84 return fmt.Errorf("authorization file %v does not exist", kf) 85 } 86 default: 87 return fmt.Errorf("unknown authorization method %q", authz) 88 } 89 } 90 return nil 91 } 92 93 func writeIfNotExists(p string, data []byte) (alreadyExists bool, err error) { 94 f, err := os.OpenFile(p, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0600) 95 if err != nil { 96 if os.IsExist(err) { 97 return true, nil 98 } 99 return false, err 100 } 101 if _, err := f.Write(data); err != nil { 102 f.Close() 103 return false, err 104 } 105 return false, f.Close() 106 } 107 108 func (c *cmdAddRecoveryKey) Execute(args []string) error { 109 recoveryKey, err := keys.NewRecoveryKey() 110 if err != nil { 111 return fmt.Errorf("cannot create recovery key: %v", err) 112 } 113 if len(c.Authorizations) != len(c.Devices) { 114 return fmt.Errorf("cannot add recovery keys: mismatch in the number of devices and authorizations") 115 } 116 if err := validateAuthorizations(c.Authorizations); err != nil { 117 return fmt.Errorf("cannot add recovery keys with invalid authorizations: %v", err) 118 } 119 // write the key to the file, if the file already exists it is possible 120 // that we are being called again after an unexpected reboot or a 121 // similar event 122 alreadyExists, err := writeIfNotExists(c.KeyFile, recoveryKey[:]) 123 if err != nil { 124 return fmt.Errorf("cannot write recovery key to file: %v", err) 125 } 126 if alreadyExists { 127 // we already have the recovery key, read it back 128 maybeKey, err := ioutil.ReadFile(c.KeyFile) 129 if err != nil { 130 return fmt.Errorf("cannot read existing recovery key file: %v", err) 131 } 132 // TODO: verify that the size if non 0 and try again otherwise? 133 if len(maybeKey) != len(recoveryKey) { 134 return fmt.Errorf("cannot use existing recovery key of size %v", len(maybeKey)) 135 } 136 copy(recoveryKey[:], maybeKey[:]) 137 } 138 // add the recovery key to each device; keys are always added to the 139 // same keyslot, so when the key existed on disk, assume that the key 140 // was already added to the device in case we hit an error with keyslot 141 // being already used 142 for i, dev := range c.Devices { 143 authz := c.Authorizations[i] 144 switch { 145 case authz == "keyring": 146 if err := keymgrAddRecoveryKeyToLUKSDevice(recoveryKey, dev); err != nil { 147 if !alreadyExists || !keymgr.IsKeyslotAlreadyUsed(err) { 148 return fmt.Errorf("cannot add recovery key to LUKS device: %v", err) 149 } 150 } 151 case strings.HasPrefix(authz, "file:"): 152 authzKey, err := ioutil.ReadFile(authz[len("file:"):]) 153 if err != nil { 154 return fmt.Errorf("cannot load authorization key: %v", err) 155 } 156 if err := keymgrAddRecoveryKeyToLUKSDeviceUsingKey(recoveryKey, authzKey, dev); err != nil { 157 if !alreadyExists || !keymgr.IsKeyslotAlreadyUsed(err) { 158 return fmt.Errorf("cannot add recovery key to LUKS device using authorization key: %v", err) 159 } 160 } 161 } 162 } 163 return nil 164 } 165 166 func (c *cmdRemoveRecoveryKey) Execute(args []string) error { 167 if len(c.Authorizations) != len(c.Devices) { 168 return fmt.Errorf("cannot remove recovery keys: mismatch in the number of devices and authorizations") 169 } 170 if err := validateAuthorizations(c.Authorizations); err != nil { 171 return fmt.Errorf("cannot remove recovery keys with invalid authorizations: %v", err) 172 } 173 for i, dev := range c.Devices { 174 authz := c.Authorizations[i] 175 switch { 176 case authz == "keyring": 177 if err := keymgrRemoveRecoveryKeyFromLUKSDevice(dev); err != nil { 178 return fmt.Errorf("cannot remove recovery key from LUKS device: %v", err) 179 } 180 case strings.HasPrefix(authz, "file:"): 181 authzKey, err := ioutil.ReadFile(authz[len("file:"):]) 182 if err != nil { 183 return fmt.Errorf("cannot load authorization key: %v", err) 184 } 185 if err := keymgrRemoveRecoveryKeyFromLUKSDeviceUsingKey(authzKey, dev); err != nil { 186 return fmt.Errorf("cannot remove recovery key from device using authorization key: %v", err) 187 } 188 } 189 } 190 var rmErrors []string 191 for _, kf := range c.KeyFiles { 192 if err := os.Remove(kf); err != nil && !os.IsNotExist(err) { 193 rmErrors = append(rmErrors, err.Error()) 194 } 195 } 196 if len(rmErrors) != 0 { 197 return fmt.Errorf("cannot remove key files:\n%s", strings.Join(rmErrors, "\n")) 198 } 199 return nil 200 } 201 202 type newKey struct { 203 Key []byte `json:"key"` 204 } 205 206 func (c *cmdChangeEncryptionKey) Execute(args []string) error { 207 if c.Stage && c.Transition { 208 return fmt.Errorf("cannot both stage and transition the encryption key change") 209 } 210 if !c.Stage && !c.Transition { 211 return fmt.Errorf("cannot change encryption key without stage or transition request") 212 } 213 214 var newEncryptionKeyData newKey 215 dec := json.NewDecoder(osStdin) 216 if err := dec.Decode(&newEncryptionKeyData); err != nil { 217 return fmt.Errorf("cannot obtain new encryption key: %v", err) 218 } 219 switch { 220 case c.Stage: 221 // staging the key change authorizes the operation using a key 222 // from the keyring 223 if err := keymgrStageLUKSDeviceEncryptionKeyChange(newEncryptionKeyData.Key, c.Device); err != nil { 224 return fmt.Errorf("cannot stage LUKS device encryption key change: %v", err) 225 } 226 case c.Transition: 227 // transitioning the key change authorizes the operation using 228 // the currently provided key (which must have been staged 229 // before hence the op will be authorized successfully) 230 if err := keymgrTransitionLUKSDeviceEncryptionKeyChange(newEncryptionKeyData.Key, c.Device); err != nil { 231 return fmt.Errorf("cannot transition LUKS device encryption key change: %v", err) 232 } 233 } 234 return nil 235 } 236 237 func run(osArgs1 []string) error { 238 var opts options 239 p := flags.NewParser(&opts, flags.HelpFlag|flags.PassDoubleDash) 240 if _, err := p.ParseArgs(osArgs1); err != nil { 241 return err 242 } 243 return nil 244 } 245 246 func main() { 247 if err := run(os.Args[1:]); err != nil { 248 fmt.Fprintf(os.Stderr, "error: %v\n", err) 249 os.Exit(1) 250 } 251 }