github.com/meulengracht/snapd@v0.0.0-20210719210640-8bde69bcc84e/asserts/account_key.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2015-2021 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 asserts 21 22 import ( 23 "fmt" 24 "regexp" 25 "time" 26 ) 27 28 var validAccountKeyName = regexp.MustCompile(`^(?:[a-z0-9]+-?)*[a-z](?:-?[a-z0-9])*$`) 29 30 // AccountKey holds an account-key assertion, asserting a public key 31 // belonging to the account. 32 type AccountKey struct { 33 assertionBase 34 since time.Time 35 until time.Time 36 pubKey PublicKey 37 } 38 39 // AccountID returns the account-id of this account-key. 40 func (ak *AccountKey) AccountID() string { 41 return ak.HeaderString("account-id") 42 } 43 44 // Name returns the name of the account key. 45 func (ak *AccountKey) Name() string { 46 return ak.HeaderString("name") 47 } 48 49 func IsValidAccountKeyName(name string) bool { 50 return validAccountKeyName.MatchString(name) 51 } 52 53 // Since returns the time when the account key starts being valid. 54 func (ak *AccountKey) Since() time.Time { 55 return ak.since 56 } 57 58 // Until returns the time when the account key stops being valid. A zero time means the key is valid forever. 59 func (ak *AccountKey) Until() time.Time { 60 return ak.until 61 } 62 63 // PublicKeyID returns the key id used for lookup of the account key. 64 func (ak *AccountKey) PublicKeyID() string { 65 return ak.pubKey.ID() 66 } 67 68 // isKeyValidAt returns whether the account key is valid at 'when' time. 69 func (ak *AccountKey) isKeyValidAt(when time.Time) bool { 70 valid := when.After(ak.since) || when.Equal(ak.since) 71 if valid && !ak.until.IsZero() { 72 valid = when.Before(ak.until) 73 } 74 return valid 75 } 76 77 // isKeyValidAssumingCurTimeWithin returns whether the account key is 78 // possibly valid if the current time is known to be within [earliest, 79 // latest]. That means the intersection of possible current times and 80 // validity is not empty. 81 // If latest is zero, then current time is assumed to be >=earliest. 82 // If earliest == latest this is equivalent to isKeyValidAt(). 83 func (ak *AccountKey) isKeyValidAssumingCurTimeWithin(earliest, latest time.Time) bool { 84 if !latest.IsZero() { 85 // impossible input => false 86 if latest.Before(earliest) { 87 return false 88 } 89 if latest.Before(ak.since) { 90 return false 91 } 92 } 93 if !ak.until.IsZero() { 94 if earliest.After(ak.until) || earliest.Equal(ak.until) { 95 return false 96 } 97 } 98 return true 99 } 100 101 // publicKey returns the underlying public key of the account key. 102 func (ak *AccountKey) publicKey() PublicKey { 103 return ak.pubKey 104 } 105 106 func checkPublicKey(ab *assertionBase, keyIDName string) (PublicKey, error) { 107 pubKey, err := DecodePublicKey(ab.Body()) 108 if err != nil { 109 return nil, err 110 } 111 keyID, err := checkNotEmptyString(ab.headers, keyIDName) 112 if err != nil { 113 return nil, err 114 } 115 if keyID != pubKey.ID() { 116 return nil, fmt.Errorf("public key does not match provided key id") 117 } 118 return pubKey, nil 119 } 120 121 // Implement further consistency checks. 122 func (ak *AccountKey) checkConsistency(db RODatabase, acck *AccountKey) error { 123 if !db.IsTrustedAccount(ak.AuthorityID()) { 124 return fmt.Errorf("account-key assertion for %q is not signed by a directly trusted authority: %s", ak.AccountID(), ak.AuthorityID()) 125 } 126 _, err := db.Find(AccountType, map[string]string{ 127 "account-id": ak.AccountID(), 128 }) 129 if IsNotFound(err) { 130 return fmt.Errorf("account-key assertion for %q does not have a matching account assertion", ak.AccountID()) 131 } 132 if err != nil { 133 return err 134 } 135 // XXX: Make this unconditional once account-key assertions are required to have a name. 136 if ak.Name() != "" { 137 // Check that we don't end up with multiple keys with 138 // different IDs but the same account-id and name. 139 // Note that this is a non-transactional check-then-add, so 140 // is not a hard guarantee. Backstores that can implement a 141 // unique constraint should do so. 142 assertions, err := db.FindMany(AccountKeyType, map[string]string{ 143 "account-id": ak.AccountID(), 144 "name": ak.Name(), 145 }) 146 if err != nil && !IsNotFound(err) { 147 return err 148 } 149 for _, assertion := range assertions { 150 existingAccKey := assertion.(*AccountKey) 151 if ak.PublicKeyID() != existingAccKey.PublicKeyID() { 152 return fmt.Errorf("account-key assertion for %q with ID %q has the same name %q as existing ID %q", ak.AccountID(), ak.PublicKeyID(), ak.Name(), existingAccKey.PublicKeyID()) 153 } 154 } 155 } 156 return nil 157 } 158 159 // sanity 160 var _ consistencyChecker = (*AccountKey)(nil) 161 162 // Prerequisites returns references to this account-key's prerequisite assertions. 163 func (ak *AccountKey) Prerequisites() []*Ref { 164 return []*Ref{ 165 {Type: AccountType, PrimaryKey: []string{ak.AccountID()}}, 166 } 167 } 168 169 func assembleAccountKey(assert assertionBase) (Assertion, error) { 170 _, err := checkNotEmptyString(assert.headers, "account-id") 171 if err != nil { 172 return nil, err 173 } 174 175 // XXX: We should require name to be present after backfilling existing assertions. 176 _, ok := assert.headers["name"] 177 if ok { 178 _, err = checkStringMatches(assert.headers, "name", validAccountKeyName) 179 if err != nil { 180 return nil, err 181 } 182 } 183 184 since, err := checkRFC3339Date(assert.headers, "since") 185 if err != nil { 186 return nil, err 187 } 188 189 until, err := checkRFC3339DateWithDefault(assert.headers, "until", time.Time{}) 190 if err != nil { 191 return nil, err 192 } 193 if !until.IsZero() && until.Before(since) { 194 return nil, fmt.Errorf("'until' time cannot be before 'since' time") 195 } 196 197 pubk, err := checkPublicKey(&assert, "public-key-sha3-384") 198 if err != nil { 199 return nil, err 200 } 201 202 // ignore extra headers for future compatibility 203 return &AccountKey{ 204 assertionBase: assert, 205 since: since, 206 until: until, 207 pubKey: pubk, 208 }, nil 209 } 210 211 // AccountKeyRequest holds an account-key-request assertion, which is a self-signed request to prove that the requester holds the private key and wishes to create an account-key assertion for it. 212 type AccountKeyRequest struct { 213 assertionBase 214 since time.Time 215 until time.Time 216 pubKey PublicKey 217 } 218 219 // AccountID returns the account-id of this account-key-request. 220 func (akr *AccountKeyRequest) AccountID() string { 221 return akr.HeaderString("account-id") 222 } 223 224 // Name returns the name of the account key. 225 func (akr *AccountKeyRequest) Name() string { 226 return akr.HeaderString("name") 227 } 228 229 // Since returns the time when the requested account key starts being valid. 230 func (akr *AccountKeyRequest) Since() time.Time { 231 return akr.since 232 } 233 234 // Until returns the time when the requested account key stops being valid. A zero time means the key is valid forever. 235 func (akr *AccountKeyRequest) Until() time.Time { 236 return akr.until 237 } 238 239 // PublicKeyID returns the underlying public key ID of the requested account key. 240 func (akr *AccountKeyRequest) PublicKeyID() string { 241 return akr.pubKey.ID() 242 } 243 244 // signKey returns the underlying public key of the requested account key. 245 func (akr *AccountKeyRequest) signKey() PublicKey { 246 return akr.pubKey 247 } 248 249 // Implement further consistency checks. 250 func (akr *AccountKeyRequest) checkConsistency(db RODatabase, acck *AccountKey) error { 251 _, err := db.Find(AccountType, map[string]string{ 252 "account-id": akr.AccountID(), 253 }) 254 if IsNotFound(err) { 255 return fmt.Errorf("account-key-request assertion for %q does not have a matching account assertion", akr.AccountID()) 256 } 257 if err != nil { 258 return err 259 } 260 return nil 261 } 262 263 // sanity 264 var ( 265 _ consistencyChecker = (*AccountKeyRequest)(nil) 266 _ customSigner = (*AccountKeyRequest)(nil) 267 ) 268 269 // Prerequisites returns references to this account-key-request's prerequisite assertions. 270 func (akr *AccountKeyRequest) Prerequisites() []*Ref { 271 return []*Ref{ 272 {Type: AccountType, PrimaryKey: []string{akr.AccountID()}}, 273 } 274 } 275 276 func assembleAccountKeyRequest(assert assertionBase) (Assertion, error) { 277 _, err := checkNotEmptyString(assert.headers, "account-id") 278 if err != nil { 279 return nil, err 280 } 281 282 _, err = checkStringMatches(assert.headers, "name", validAccountKeyName) 283 if err != nil { 284 return nil, err 285 } 286 287 since, err := checkRFC3339Date(assert.headers, "since") 288 if err != nil { 289 return nil, err 290 } 291 292 until, err := checkRFC3339DateWithDefault(assert.headers, "until", time.Time{}) 293 if err != nil { 294 return nil, err 295 } 296 if !until.IsZero() && until.Before(since) { 297 return nil, fmt.Errorf("'until' time cannot be before 'since' time") 298 } 299 300 pubk, err := checkPublicKey(&assert, "public-key-sha3-384") 301 if err != nil { 302 return nil, err 303 } 304 305 // ignore extra headers for future compatibility 306 return &AccountKeyRequest{ 307 assertionBase: assert, 308 since: since, 309 until: until, 310 pubKey: pubk, 311 }, nil 312 }