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  }