1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856
|
// -*- Mode: Go; indent-tabs-mode: t -*-
/*
* Copyright (C) 2015-2022 Canonical Ltd
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
// Package asserts implements snappy assertions and a database
// abstraction for managing and holding them.
package asserts
import (
"errors"
"fmt"
"regexp"
"time"
)
// NotFoundError is returned when an assertion can not be found.
type NotFoundError struct {
Type *AssertionType
Headers map[string]string
}
func (e *NotFoundError) Error() string {
pk, err := PrimaryKeyFromHeaders(e.Type, e.Headers)
if err != nil || len(e.Headers) != len(pk) {
// TODO: worth conveying more information?
return fmt.Sprintf("%s assertion not found", e.Type.Name)
}
return fmt.Sprintf("%v not found", &Ref{Type: e.Type, PrimaryKey: pk})
}
func (e *NotFoundError) Is(err error) bool {
_, ok := err.(*NotFoundError)
return ok
}
// A Backstore stores assertions. It can store and retrieve assertions
// by type under unique primary key headers (whose names are available
// from assertType.PrimaryKey). Plus it supports searching by headers.
// Lookups can be limited to a maximum allowed format.
type Backstore interface {
// Put stores an assertion.
// It is responsible for checking that assert is newer than a
// previously stored revision with the same primary key headers.
Put(assertType *AssertionType, assert Assertion) error
// Get returns the assertion with the given unique key for its
// primary key headers.
// A suffix of optional primary keys can be left out from key
// in which case their default values are implied.
// If the assertion is not present it returns a
// NotFoundError, usually with omitted Headers.
Get(assertType *AssertionType, key []string, maxFormat int) (Assertion, error)
// Search returns assertions matching the given headers.
// It invokes foundCb for each found assertion.
Search(assertType *AssertionType, headers map[string]string, foundCb func(Assertion), maxFormat int) error
// SequenceMemberAfter returns for a sequence-forming assertType the
// first assertion in the sequence under the given sequenceKey
// with sequence number larger than after. If after==-1 it
// returns the assertion with largest sequence number. If none
// exists it returns a NotFoundError, usually with omitted
// Headers. If assertType is not sequence-forming it can
// panic.
SequenceMemberAfter(assertType *AssertionType, sequenceKey []string, after, maxFormat int) (SequenceMember, error)
}
type nullBackstore struct{}
func (nbs nullBackstore) Put(t *AssertionType, a Assertion) error {
return fmt.Errorf("cannot store assertions without setting a proper assertion backstore implementation")
}
func (nbs nullBackstore) Get(t *AssertionType, k []string, maxFormat int) (Assertion, error) {
return nil, &NotFoundError{Type: t}
}
func (nbs nullBackstore) Search(t *AssertionType, h map[string]string, f func(Assertion), maxFormat int) error {
return nil
}
func (nbs nullBackstore) SequenceMemberAfter(t *AssertionType, kp []string, after, maxFormat int) (SequenceMember, error) {
return nil, &NotFoundError{Type: t}
}
// keyNotFoundError is returned when the key with a given ID cannot be found.
type keyNotFoundError struct {
msg string
}
func (e *keyNotFoundError) Error() string { return e.msg }
func (e *keyNotFoundError) Is(target error) bool {
_, ok := target.(*keyNotFoundError)
return ok
}
// IsKeyNotFound returns true when the error indicates that a given key was not
// found.
func IsKeyNotFound(err error) bool {
return errors.Is(err, &keyNotFoundError{})
}
// A KeypairManager is a manager and backstore for private/public key pairs.
type KeypairManager interface {
// Put stores the given private/public key pair,
// making sure it can be later retrieved by its unique key id with Get.
// Trying to store a key with an already present key id should
// result in an error.
Put(privKey PrivateKey) error
// Get returns the private/public key pair with the given key id. The
// error can be tested with IsKeyNotFound to check whether the given key
// was not found, or other error occurred.
Get(keyID string) (PrivateKey, error)
// Delete deletes the private/public key pair with the given key id.
Delete(keyID string) error
}
// DatabaseConfig for an assertion database.
type DatabaseConfig struct {
// trusted set of assertions (account and account-key supported),
// used to establish root keys and trusted authorities
Trusted []Assertion
// predefined assertions but that do not establish foundational trust
OtherPredefined []Assertion
// backstore for assertions, left unset storing assertions will error
Backstore Backstore
// manager/backstore for keypairs, defaults to in-memory implementation
KeypairManager KeypairManager
// assertion checkers used by Database.Check, left unset DefaultCheckers will be used which is recommended
Checkers []Checker
}
// RevisionError indicates a revision improperly used for an operation.
type RevisionError struct {
Used, Current int
}
func (e *RevisionError) Error() string {
if e.Used < 0 || e.Current < 0 {
// TODO: message may need tweaking once there's a use.
return "assertion revision is unknown"
}
if e.Used == e.Current {
return fmt.Sprintf("revision %d is already the current revision", e.Used)
}
if e.Used < e.Current {
return fmt.Sprintf("revision %d is older than current revision %d", e.Used, e.Current)
}
return fmt.Sprintf("revision %d is more recent than current revision %d", e.Used, e.Current)
}
// UnsupportedFormatError indicates an assertion with a format iteration not yet supported by the present version of asserts.
type UnsupportedFormatError struct {
Ref *Ref
Format int
// Update marks there was already a current revision of the assertion and it has been kept.
Update bool
}
func (e *UnsupportedFormatError) Error() string {
postfx := ""
if e.Update {
postfx = " (current not updated)"
}
return fmt.Sprintf("proposed %q assertion has format %d but %d is latest supported%s", e.Ref.Type.Name, e.Format, e.Ref.Type.MaxSupportedFormat(), postfx)
}
// IsUnaccceptedUpdate returns whether the error indicates that an
// assertion revision was already present and has been kept because
// the update was not accepted.
func IsUnaccceptedUpdate(err error) bool {
switch x := err.(type) {
case *UnsupportedFormatError:
return x.Update
case *RevisionError:
return x.Used <= x.Current
}
return false
}
// A RODatabase exposes read-only access to an assertion database.
type RODatabase interface {
// IsTrustedAccount returns whether the account is part of the trusted set.
IsTrustedAccount(accountID string) bool
// Find an assertion based on arbitrary headers.
// Provided headers must contain the primary key for the assertion type.
// Optional primary key headers can be omitted in which case
// their default values will be used.
// It returns a NotFoundError if the assertion cannot be found.
Find(assertionType *AssertionType, headers map[string]string) (Assertion, error)
// FindPredefined finds an assertion in the predefined sets
// (trusted or not) based on arbitrary headers. Provided
// headers must contain the primary key for the assertion
// type.
// Optional primary key headers can be omitted in which case
// their default values will be used.
// It returns a NotFoundError if the assertion cannot
// be found.
FindPredefined(assertionType *AssertionType, headers map[string]string) (Assertion, error)
// FindTrusted finds an assertion in the trusted set based on
// arbitrary headers. Provided headers must contain the
// primary key for the assertion type.
// Optional primary key headers can be omitted in which case
// their default values will be used.
// It returns a NotFoundError if the assertion cannot be
// found.
FindTrusted(assertionType *AssertionType, headers map[string]string) (Assertion, error)
// FindMany finds assertions based on arbitrary headers.
// It returns a NotFoundError if no assertion can be found.
FindMany(assertionType *AssertionType, headers map[string]string) ([]Assertion, error)
// FindManyPredefined finds assertions in the predefined sets
// (trusted or not) based on arbitrary headers. It returns a
// NotFoundError if no assertion can be found.
FindManyPredefined(assertionType *AssertionType, headers map[string]string) ([]Assertion, error)
// FindSequence finds an assertion for the given headers and after for
// a sequence-forming type.
// The provided headers must contain a sequence key, i.e. a prefix of
// the primary key for the assertion type except for the sequence
// number header.
// The assertion is the first in the sequence under the sequence key
// with sequence number > after.
// If after is -1 it returns instead the assertion with the largest
// sequence number.
// It will constraint itself to assertions with format <= maxFormat
// unless maxFormat is -1.
// It returns a NotFoundError if the assertion cannot be found.
FindSequence(assertType *AssertionType, sequenceHeaders map[string]string, after, maxFormat int) (SequenceMember, error)
// Check tests whether the assertion is properly signed and consistent with all the stored knowledge.
Check(assert Assertion) error
}
// A Checker defines a check on an assertion considering aspects such as
// the signing key, and consistency with other
// assertions in the database.
type Checker func(assert Assertion, signingKey *AccountKey, roDB RODatabase, checkTimeEarliest, checkTimeLatest time.Time) error
// Database holds assertions and can be used to sign or check
// further assertions.
type Database struct {
bs Backstore
keypairMgr KeypairManager
trusted Backstore
predefined Backstore
// all backstores to consider for find
backstores []Backstore
// backstores of dbs this was built on by stacking
stackedOn []Backstore
checkers []Checker
earliestTime time.Time
}
// OpenDatabase opens the assertion database based on the configuration.
func OpenDatabase(cfg *DatabaseConfig) (*Database, error) {
bs := cfg.Backstore
keypairMgr := cfg.KeypairManager
if bs == nil {
bs = nullBackstore{}
}
if keypairMgr == nil {
keypairMgr = NewMemoryKeypairManager()
}
trustedBackstore := NewMemoryBackstore()
for _, a := range cfg.Trusted {
switch accepted := a.(type) {
case *AccountKey:
accKey := accepted
err := trustedBackstore.Put(AccountKeyType, accKey)
if err != nil {
return nil, fmt.Errorf("cannot predefine trusted account key %q for %q: %v", accKey.PublicKeyID(), accKey.AccountID(), err)
}
case *Account:
acct := accepted
err := trustedBackstore.Put(AccountType, acct)
if err != nil {
return nil, fmt.Errorf("cannot predefine trusted account %q: %v", acct.DisplayName(), err)
}
default:
return nil, fmt.Errorf("cannot predefine trusted assertions that are not account-key or account: %s", a.Type().Name)
}
}
otherPredefinedBackstore := NewMemoryBackstore()
for _, a := range cfg.OtherPredefined {
err := otherPredefinedBackstore.Put(a.Type(), a)
if err != nil {
return nil, fmt.Errorf("cannot predefine assertion %v: %v", a.Ref(), err)
}
}
checkers := cfg.Checkers
if len(checkers) == 0 {
checkers = DefaultCheckers
}
dbCheckers := make([]Checker, len(checkers))
copy(dbCheckers, checkers)
return &Database{
bs: bs,
keypairMgr: keypairMgr,
trusted: trustedBackstore,
predefined: otherPredefinedBackstore,
// order here is relevant, Find* precedence and
// findAccountKey depend on it, trusted should win over the
// general backstore!
backstores: []Backstore{trustedBackstore, otherPredefinedBackstore, bs},
checkers: dbCheckers,
}, nil
}
// WithStackedBackstore returns a new database that adds to the given backstore
// only but finds in backstore and the base database backstores and
// cross-checks against all of them.
// This is useful to cross-check a set of assertions without adding
// them to the database.
func (db *Database) WithStackedBackstore(backstore Backstore) *Database {
// original bs goes in front of stacked-on ones
stackedOn := []Backstore{db.bs}
stackedOn = append(stackedOn, db.stackedOn...)
// find order: trusted, predefined, new backstore, stacked-on ones
backstores := []Backstore{db.trusted, db.predefined}
backstores = append(backstores, backstore)
backstores = append(backstores, stackedOn...)
return &Database{
bs: backstore,
keypairMgr: db.keypairMgr,
trusted: db.trusted,
predefined: db.predefined,
backstores: backstores,
stackedOn: stackedOn,
checkers: db.checkers,
}
}
// ImportKey stores the given private/public key pair.
func (db *Database) ImportKey(privKey PrivateKey) error {
return db.keypairMgr.Put(privKey)
}
var (
// for validity checking of base64 hash strings
base64HashLike = regexp.MustCompile("^[[:alnum:]_-]*$")
)
func (db *Database) safeGetPrivateKey(keyID string) (PrivateKey, error) {
if keyID == "" {
return nil, fmt.Errorf("key id is empty")
}
if !base64HashLike.MatchString(keyID) {
return nil, fmt.Errorf("key id contains unexpected chars: %q", keyID)
}
return db.keypairMgr.Get(keyID)
}
// PublicKey returns the public key part of the key pair that has the given key id.
func (db *Database) PublicKey(keyID string) (PublicKey, error) {
privKey, err := db.safeGetPrivateKey(keyID)
if err != nil {
return nil, err
}
return privKey.PublicKey(), nil
}
// Sign assembles an assertion with the provided information and signs it
// with the private key from `headers["authority-id"]` that has the provided key id.
func (db *Database) Sign(assertType *AssertionType, headers map[string]any, body []byte, keyID string) (Assertion, error) {
privKey, err := db.safeGetPrivateKey(keyID)
if err != nil {
return nil, err
}
return assembleAndSign(assertType, headers, body, privKey)
}
// findAccountKey finds an AccountKey exactly with account id and key id.
func (db *Database) findAccountKey(authorityID, keyID string) (*AccountKey, error) {
key := []string{keyID}
// consider trusted account keys then disk stored account keys
for _, bs := range db.backstores {
a, err := bs.Get(AccountKeyType, key, AccountKeyType.MaxSupportedFormat())
if err == nil {
hit := a.(*AccountKey)
if hit.AccountID() != authorityID {
return nil, fmt.Errorf("found public key %q from %q but expected it from: %s", keyID, hit.AccountID(), authorityID)
}
return hit, nil
}
if !errors.Is(err, &NotFoundError{}) {
return nil, err
}
}
return nil, &NotFoundError{Type: AccountKeyType}
}
// IsTrustedAccount returns whether the account is part of the trusted set.
func (db *Database) IsTrustedAccount(accountID string) bool {
if accountID == "" {
return false
}
_, err := db.trusted.Get(AccountType, []string{accountID}, AccountType.MaxSupportedFormat())
return err == nil
}
var timeNow = time.Now
// SetEarliestTime affects how key expiration is checked.
// Instead of considering current system time, only assume that current time
// is >= earliest. If earliest is zero reset to considering current system time.
func (db *Database) SetEarliestTime(earliest time.Time) {
db.earliestTime = earliest
}
// Check tests whether the assertion is properly signed and consistent with all the stored knowledge.
func (db *Database) Check(assert Assertion) error {
if !assert.SupportedFormat() {
return &UnsupportedFormatError{Ref: assert.Ref(), Format: assert.Format()}
}
typ := assert.Type()
// assume current time is >= earliestTime and <= latestTime
earliestTime := db.earliestTime
var latestTime time.Time
if earliestTime.IsZero() {
// use the current system time by setting both to it
earliestTime = timeNow()
latestTime = earliestTime
}
var accKey *AccountKey
var err error
if typ.flags&noAuthority == 0 {
// TODO: later may need to consider type of assert to find candidate keys
accKey, err = db.findAccountKey(assert.AuthorityID(), assert.SignKeyID())
if errors.Is(err, &NotFoundError{}) {
return fmt.Errorf("no matching public key %q for signature by %q", assert.SignKeyID(), assert.AuthorityID())
}
if err != nil {
return fmt.Errorf("error finding matching public key for signature: %v", err)
}
} else {
if assert.AuthorityID() != "" {
return fmt.Errorf("internal error: %q assertion cannot have authority-id set", typ.Name)
}
}
for _, checker := range db.checkers {
err := checker(assert, accKey, db, earliestTime, latestTime)
if err != nil {
return err
}
}
return nil
}
// Add persists the assertion after ensuring it is properly signed and consistent with all the stored knowledge.
// It will return an error when trying to add an older revision of the assertion than the one currently stored.
func (db *Database) Add(assert Assertion) error {
ref := assert.Ref()
if len(ref.PrimaryKey) == 0 {
return fmt.Errorf("internal error: assertion type %q has no primary key", ref.Type.Name)
}
err := db.Check(assert)
if err != nil {
if ufe, ok := err.(*UnsupportedFormatError); ok {
_, err := ref.Resolve(db.Find)
if err != nil && !errors.Is(err, &NotFoundError{}) {
return err
}
return &UnsupportedFormatError{Ref: ufe.Ref, Format: ufe.Format, Update: err == nil}
}
return err
}
for i, keyVal := range ref.PrimaryKey {
if keyVal == "" {
return fmt.Errorf("missing or non-string primary key header: %v", ref.Type.PrimaryKey[i])
}
}
// assuming trusted account keys/assertions will be managed
// through the os snap this seems the safest policy until we
// know more/better
_, err = db.trusted.Get(ref.Type, ref.PrimaryKey, ref.Type.MaxSupportedFormat())
if !errors.Is(err, &NotFoundError{}) {
return fmt.Errorf("cannot add %q assertion with primary key clashing with a trusted assertion: %v", ref.Type.Name, ref.PrimaryKey)
}
_, err = db.predefined.Get(ref.Type, ref.PrimaryKey, ref.Type.MaxSupportedFormat())
if !errors.Is(err, &NotFoundError{}) {
return fmt.Errorf("cannot add %q assertion with primary key clashing with a predefined assertion: %v", ref.Type.Name, ref.PrimaryKey)
}
// this is non empty only in the stacked case
if len(db.stackedOn) != 0 {
headers, err := HeadersFromPrimaryKey(ref.Type, ref.PrimaryKey)
if err != nil {
return fmt.Errorf("internal error: HeadersFromPrimaryKey for %q failed on prechecked data: %s", ref.Type.Name, ref.PrimaryKey)
}
cur, err := find(db.stackedOn, ref.Type, headers, -1)
if err == nil {
curRev := cur.Revision()
rev := assert.Revision()
if curRev >= rev {
return &RevisionError{Current: curRev, Used: rev}
}
} else if !errors.Is(err, &NotFoundError{}) {
return err
}
}
return db.bs.Put(ref.Type, assert)
}
func searchMatch(assert Assertion, expectedHeaders map[string]string) bool {
// check non-primary-key headers as well
for expectedKey, expectedValue := range expectedHeaders {
if assert.Header(expectedKey) != expectedValue {
return false
}
}
return true
}
func find(backstores []Backstore, assertionType *AssertionType, headers map[string]string, maxFormat int) (Assertion, error) {
err := checkAssertType(assertionType)
if err != nil {
return nil, err
}
maxSupp := assertionType.MaxSupportedFormat()
if maxFormat == -1 {
maxFormat = maxSupp
} else {
if maxFormat > maxSupp {
return nil, fmt.Errorf("cannot find %q assertions for format %d higher than supported format %d", assertionType.Name, maxFormat, maxSupp)
}
}
keyValues, err := PrimaryKeyFromHeaders(assertionType, headers)
if err != nil {
return nil, err
}
var assert Assertion
for _, bs := range backstores {
a, err := bs.Get(assertionType, keyValues, maxFormat)
if err == nil {
assert = a
break
}
if !errors.Is(err, &NotFoundError{}) {
return nil, err
}
}
if assert == nil || !searchMatch(assert, headers) {
return nil, &NotFoundError{Type: assertionType, Headers: headers}
}
return assert, nil
}
// Find an assertion based on arbitrary headers.
// Provided headers must contain the primary key for the assertion type.
// Optional primary key headers can be omitted in which case
// their default values will be used.
// It returns a NotFoundError if the assertion cannot be found.
func (db *Database) Find(assertionType *AssertionType, headers map[string]string) (Assertion, error) {
return find(db.backstores, assertionType, headers, -1)
}
// FindMaxFormat finds an assertion like Find but such that its
// format is <= maxFormat by passing maxFormat along to the backend.
// It returns a NotFoundError if such an assertion cannot be found.
func (db *Database) FindMaxFormat(assertionType *AssertionType, headers map[string]string, maxFormat int) (Assertion, error) {
return find(db.backstores, assertionType, headers, maxFormat)
}
// FindPredefined finds an assertion in the predefined sets (trusted
// or not) based on arbitrary headers. Provided headers must contain
// the primary key for the assertion type.
// Optional primary key headers can be omitted in which case
// their default values will be used.
// It returns a NotFoundError if the assertion cannot be found.
func (db *Database) FindPredefined(assertionType *AssertionType, headers map[string]string) (Assertion, error) {
return find([]Backstore{db.trusted, db.predefined}, assertionType, headers, -1)
}
// FindTrusted finds an assertion in the trusted set based on arbitrary headers.
// Provided headers must contain the primary key for the assertion type.
// Optional primary key headers can be omitted in which case
// their default values will be used.
// It returns a NotFoundError if the assertion cannot be found.
func (db *Database) FindTrusted(assertionType *AssertionType, headers map[string]string) (Assertion, error) {
return find([]Backstore{db.trusted}, assertionType, headers, -1)
}
func (db *Database) findMany(backstores []Backstore, assertionType *AssertionType, headers map[string]string) ([]Assertion, error) {
err := checkAssertType(assertionType)
if err != nil {
return nil, err
}
res := []Assertion{}
foundCb := func(assert Assertion) {
res = append(res, assert)
}
// TODO: Find variant taking this
maxFormat := assertionType.MaxSupportedFormat()
for _, bs := range backstores {
err = bs.Search(assertionType, headers, foundCb, maxFormat)
if err != nil {
return nil, err
}
}
if len(res) == 0 {
return nil, &NotFoundError{Type: assertionType, Headers: headers}
}
return res, nil
}
// FindMany finds assertions based on arbitrary headers.
// It returns a NotFoundError if no assertion can be found.
func (db *Database) FindMany(assertionType *AssertionType, headers map[string]string) ([]Assertion, error) {
return db.findMany(db.backstores, assertionType, headers)
}
// FindManyPrefined finds assertions in the predefined sets (trusted
// or not) based on arbitrary headers. It returns a NotFoundError if
// no assertion can be found.
func (db *Database) FindManyPredefined(assertionType *AssertionType, headers map[string]string) ([]Assertion, error) {
return db.findMany([]Backstore{db.trusted, db.predefined}, assertionType, headers)
}
// FindSequence finds an assertion for the given headers and after for
// a sequence-forming type.
// The provided headers must contain a sequence key, i.e. a prefix of
// the primary key for the assertion type except for the sequence
// number header.
// The assertion is the first in the sequence under the sequence key
// with sequence number > after.
// If after is -1 it returns instead the assertion with the largest
// sequence number.
// It will constraint itself to assertions with format <= maxFormat
// unless maxFormat is -1.
// It returns a NotFoundError if the assertion cannot be found.
func (db *Database) FindSequence(assertType *AssertionType, sequenceHeaders map[string]string, after, maxFormat int) (SequenceMember, error) {
err := checkAssertType(assertType)
if err != nil {
return nil, err
}
if !assertType.SequenceForming() {
return nil, fmt.Errorf("cannot use FindSequence with non sequence-forming assertion type %q", assertType.Name)
}
maxSupp := assertType.MaxSupportedFormat()
if maxFormat == -1 {
maxFormat = maxSupp
} else {
if maxFormat > maxSupp {
return nil, fmt.Errorf("cannot find %q assertions for format %d higher than supported format %d", assertType.Name, maxFormat, maxSupp)
}
}
// form the sequence key using all keys but the last one which
// is the sequence number
seqKey, err := keysFromHeaders(assertType.PrimaryKey[:len(assertType.PrimaryKey)-1], sequenceHeaders, nil)
if err != nil {
return nil, err
}
// find the better result across backstores' results
better := func(cur, a SequenceMember) SequenceMember {
if cur == nil {
return a
}
curSeq := cur.Sequence()
aSeq := a.Sequence()
if after == -1 {
if aSeq > curSeq {
return a
}
} else {
if aSeq < curSeq {
return a
}
}
return cur
}
var assert SequenceMember
for _, bs := range db.backstores {
a, err := bs.SequenceMemberAfter(assertType, seqKey, after, maxFormat)
if err == nil {
assert = better(assert, a)
continue
}
if !errors.Is(err, &NotFoundError{}) {
return nil, err
}
}
if assert != nil {
return assert, nil
}
return nil, &NotFoundError{Type: assertType, Headers: sequenceHeaders}
}
// assertion checkers
// CheckSigningKeyIsNotExpired checks that the signing key is not expired.
func CheckSigningKeyIsNotExpired(assert Assertion, signingKey *AccountKey, roDB RODatabase, checkTimeEarliest, checkTimeLatest time.Time) error {
if signingKey == nil {
// assert isn't signed with an account-key key, CheckSignature
// will fail anyway unless we teach it more stuff,
// Also this check isn't so relevant for self-signed asserts
// (e.g. account-key-request)
return nil
}
if !signingKey.isValidAssumingCurTimeWithin(checkTimeEarliest, checkTimeLatest) {
mismatchReason := timeMismatchMsg(checkTimeEarliest, checkTimeLatest, signingKey.since, signingKey.until)
return fmt.Errorf("assertion is signed with expired public key %q from %q: %s", assert.SignKeyID(), assert.AuthorityID(), mismatchReason)
}
return nil
}
func timeMismatchMsg(earliest, latest, keySince, keyUntil time.Time) string {
var msg string
validFrom := earliest.Format(time.RFC3339)
if !latest.IsZero() && !latest.Equal(earliest) {
validTo := latest.Format(time.RFC3339)
msg = fmt.Sprintf("current time range is [%s, %s]", validFrom, validTo)
} else {
msg = fmt.Sprintf("current time is %s", validFrom)
}
keyFrom := keySince.Format(time.RFC3339)
if !keyUntil.IsZero() {
keyTo := keyUntil.Format(time.RFC3339)
return msg + fmt.Sprintf(" but key is valid during [%s, %s)", keyFrom, keyTo)
}
return msg + fmt.Sprintf(" but key is valid from %s", keyFrom)
}
// CheckSignature checks that the signature is valid.
func CheckSignature(assert Assertion, signingKey *AccountKey, roDB RODatabase, checkTimeEarliest, checkTimeLatest time.Time) (err error) {
var pubKey PublicKey
if signingKey != nil {
pubKey = signingKey.publicKey()
if assert.AuthorityID() != signingKey.AccountID() {
return fmt.Errorf("assertion authority %q does not match public key from %q", assert.AuthorityID(), signingKey.AccountID())
}
if !signingKey.canSign(assert) {
return fmt.Errorf("assertion does not match signing constraints for public key %q from %q", assert.SignKeyID(), assert.AuthorityID())
}
} else {
custom, ok := assert.(customSigner)
if !ok {
return fmt.Errorf("cannot check no-authority assertion type %q", assert.Type().Name)
}
pubKey, err = custom.signKey(roDB)
if err != nil {
return fmt.Errorf("cannot check no-authority assertion type %q: %w", assert.Type().Name, err)
}
}
content, encSig := assert.Signature()
signature, err := decodeSignature(encSig)
if err != nil {
return err
}
err = pubKey.verify(content, signature)
if err != nil {
return fmt.Errorf("failed signature verification: %v", err)
}
return nil
}
type timestamped interface {
Timestamp() time.Time
}
// CheckTimestampVsSigningKeyValidity verifies that the timestamp of
// the assertion is within the signing key validity.
func CheckTimestampVsSigningKeyValidity(assert Assertion, signingKey *AccountKey, roDB RODatabase, checkTimeEarliest, checkTimeLatest time.Time) error {
if signingKey == nil {
// assert isn't signed with an account-key key, CheckSignature
// will fail anyway unless we teach it more stuff.
// Also this check isn't so relevant for self-signed asserts
// (e.g. account-key-request)
return nil
}
if tstamped, ok := assert.(timestamped); ok {
checkTime := tstamped.Timestamp()
if !signingKey.isValidAt(checkTime) {
until := ""
if !signingKey.Until().IsZero() {
until = fmt.Sprintf(" until %q", signingKey.Until())
}
return fmt.Errorf("%s assertion timestamp %q outside of signing key validity (key valid since %q%s)",
assert.Type().Name, checkTime, signingKey.Since(), until)
}
}
return nil
}
// A consistencyChecker performs further checks based on the full
// assertion database knowledge and its own signing key.
type consistencyChecker interface {
checkConsistency(roDB RODatabase, signingKey *AccountKey) error
}
// CheckCrossConsistency verifies that the assertion is consistent with the other statements in the database.
func CheckCrossConsistency(assert Assertion, signingKey *AccountKey, roDB RODatabase, checkTimeEarliest, checkTimeLatest time.Time) error {
// see if the assertion requires further checks
if checker, ok := assert.(consistencyChecker); ok {
return checker.checkConsistency(roDB, signingKey)
}
return nil
}
// DefaultCheckers lists the default and recommended assertion
// checkers used by Database if none are specified in the
// DatabaseConfig.Checkers.
var DefaultCheckers = []Checker{
CheckSigningKeyIsNotExpired,
CheckSignature,
CheckTimestampVsSigningKeyValidity,
CheckCrossConsistency,
}
|