gitee.com/mysnapcore/mysnapd@v0.1.0/asserts/snapasserts/snapasserts.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 snapasserts offers helpers to handle snap related assertions and their checking for installation.
    21  package snapasserts
    22  
    23  import (
    24  	"fmt"
    25  
    26  	"gitee.com/mysnapcore/mysnapd/asserts"
    27  	"gitee.com/mysnapcore/mysnapd/release"
    28  	"gitee.com/mysnapcore/mysnapd/snap"
    29  	"gitee.com/mysnapcore/mysnapd/snap/snapfile"
    30  )
    31  
    32  type Finder interface {
    33  	// Find an assertion based on arbitrary headers.  Provided
    34  	// headers must contain the primary key for the assertion
    35  	// type.  It returns a asserts.NotFoundError if the assertion
    36  	// cannot be found.
    37  	Find(assertionType *asserts.AssertionType, headers map[string]string) (asserts.Assertion, error)
    38  	// FindMany finds assertions based on arbitrary headers.
    39  	// It returns a NotFoundError if no assertion can be found.
    40  	FindMany(assertionType *asserts.AssertionType, headers map[string]string) ([]asserts.Assertion, error)
    41  }
    42  
    43  func findSnapDeclaration(snapID, name string, db Finder) (*asserts.SnapDeclaration, error) {
    44  	a, err := db.Find(asserts.SnapDeclarationType, map[string]string{
    45  		"series":  release.Series,
    46  		"snap-id": snapID,
    47  	})
    48  	if err != nil {
    49  		return nil, fmt.Errorf("internal error: cannot find snap declaration for %q: %s", name, snapID)
    50  	}
    51  	snapDecl := a.(*asserts.SnapDeclaration)
    52  
    53  	if snapDecl.SnapName() == "" {
    54  		return nil, fmt.Errorf("cannot install snap %q with a revoked snap declaration", name)
    55  	}
    56  
    57  	return snapDecl, nil
    58  }
    59  
    60  // CrossCheck tries to cross check the instance name, hash digest, provenance
    61  // and size of a snap plus its metadata in a SideInfo with the relevant
    62  // snap assertions in a database that should have been populated with
    63  // them.
    64  // The optional model assertion must be passed to have full cross
    65  // checks in the case of delegated authority snap-revisions before
    66  // installing a snap.
    67  // It returns the corresponding cross-checked snap-revision.
    68  // Ultimately the provided provenance (if not default) must be checked
    69  // with the provenance in the snap metadata by the caller as well, if
    70  // the provided provenance was not read safely from there already.
    71  func CrossCheck(instanceName, snapSHA3_384, provenance string, snapSize uint64, si *snap.SideInfo, model *asserts.Model, db Finder) (snapRev *asserts.SnapRevision, err error) {
    72  	// get relevant assertions and do cross checks
    73  	headers := map[string]string{
    74  		"snap-sha3-384": snapSHA3_384,
    75  	}
    76  	if provenance != "" {
    77  		headers["provenance"] = provenance
    78  	}
    79  	a, err := db.Find(asserts.SnapRevisionType, headers)
    80  	if err != nil {
    81  		provInf := ""
    82  		if provenance != "" {
    83  			provInf = fmt.Sprintf(" provenance: %s", provenance)
    84  		}
    85  		return nil, fmt.Errorf("internal error: cannot find pre-populated snap-revision assertion for %q: %s%s", instanceName, snapSHA3_384, provInf)
    86  	}
    87  	snapRev = a.(*asserts.SnapRevision)
    88  
    89  	if snapRev.SnapSize() != snapSize {
    90  		return nil, fmt.Errorf("snap %q file does not have expected size according to signatures (download is broken or tampered): %d != %d", instanceName, snapSize, snapRev.SnapSize())
    91  	}
    92  
    93  	snapID := si.SnapID
    94  
    95  	if snapRev.SnapID() != snapID || snapRev.SnapRevision() != si.Revision.N {
    96  		return nil, fmt.Errorf("snap %q does not have expected ID or revision according to assertions (metadata is broken or tampered): %s / %s != %d / %s", instanceName, si.Revision, snapID, snapRev.SnapRevision(), snapRev.SnapID())
    97  	}
    98  
    99  	snapDecl, err := findSnapDeclaration(snapID, instanceName, db)
   100  	if err != nil {
   101  		return nil, err
   102  	}
   103  
   104  	if snapDecl.SnapName() != snap.InstanceSnap(instanceName) {
   105  		return nil, fmt.Errorf("cannot install %q, snap %q is undergoing a rename to %q", instanceName, snap.InstanceSnap(instanceName), snapDecl.SnapName())
   106  	}
   107  
   108  	if _, err := CrossCheckProvenance(instanceName, snapRev, snapDecl, model, db); err != nil {
   109  		return nil, err
   110  	}
   111  
   112  	return snapRev, nil
   113  }
   114  
   115  // CrossCheckProvenance tries to cross check the given snap-revision
   116  // if it has a non default provenance with the revision-authority
   117  // constraints of the given snap-declaration including any device
   118  // scope constraints using model (and implied store).
   119  // It also returns the provenance if it is different from the default.
   120  // Ultimately if not default the provenance must also be checked
   121  // with the provenance in the snap metadata by the caller.
   122  func CrossCheckProvenance(instanceName string, snapRev *asserts.SnapRevision, snapDecl *asserts.SnapDeclaration, model *asserts.Model, db Finder) (signedProvenance string, err error) {
   123  	if snapRev.Provenance() == "global-upload" {
   124  		// nothing to check
   125  		return "", nil
   126  	}
   127  	var store *asserts.Store
   128  	if model != nil && model.Store() != "" {
   129  		a, err := db.Find(asserts.StoreType, map[string]string{
   130  			"store": model.Store(),
   131  		})
   132  		if err != nil && !asserts.IsNotFound(err) {
   133  			return "", err
   134  		}
   135  		if a != nil {
   136  			store = a.(*asserts.Store)
   137  		}
   138  	}
   139  	ras := snapDecl.RevisionAuthority(snapRev.Provenance())
   140  	matchingRevAuthority := false
   141  	for _, ra := range ras {
   142  		if err := ra.Check(snapRev, model, store); err == nil {
   143  			matchingRevAuthority = true
   144  			break
   145  		}
   146  	}
   147  	if !matchingRevAuthority {
   148  		return "", fmt.Errorf("snap %q revision assertion with provenance %q is not signed by an authority authorized on this device: %s", instanceName, snapRev.Provenance(), snapRev.AuthorityID())
   149  	}
   150  	return snapRev.Provenance(), nil
   151  }
   152  
   153  // CheckProvenanceWithVerifiedRevision checks that the given snap has
   154  // the same provenance as of the provided snap-revision.
   155  // It is intended to be called safely on snaps for which a matching
   156  // and authorized snap-revision has been already found and cross-checked.
   157  // Its purpose is to check that a blob has not been re-signed under an
   158  // inappropriate provenance.
   159  func CheckProvenanceWithVerifiedRevision(snapPath string, verifiedRev *asserts.SnapRevision) error {
   160  	snapf, err := snapfile.Open(snapPath)
   161  	if err != nil {
   162  		return err
   163  	}
   164  	info, err := snap.ReadInfoFromSnapFile(snapf, nil)
   165  	if err != nil {
   166  		return err
   167  	}
   168  	if verifiedRev.Provenance() != info.Provenance() {
   169  		return fmt.Errorf("snap %q has been signed under provenance %q different from the metadata one: %q", snapPath, verifiedRev.Provenance(), info.Provenance())
   170  	}
   171  	return nil
   172  }
   173  
   174  // DeriveSideInfo tries to construct a SideInfo for the given snap
   175  // using its digest to find the relevant snap assertions with the
   176  // information in the given database. It will fail with an
   177  // asserts.NotFoundError if it cannot find them.
   178  // model is used to cross check that the found snap-revision is applicable
   179  // on the device.
   180  func DeriveSideInfo(snapPath string, model *asserts.Model, db Finder) (*snap.SideInfo, error) {
   181  	snapSHA3_384, snapSize, err := asserts.SnapFileSHA3_384(snapPath)
   182  	if err != nil {
   183  		return nil, err
   184  	}
   185  
   186  	return DeriveSideInfoFromDigestAndSize(snapPath, snapSHA3_384, snapSize, model, db)
   187  }
   188  
   189  // DeriveSideInfoFromDigestAndSize tries to construct a SideInfo
   190  // using digest and size as provided for the snap to find the relevant
   191  // snap assertions with the information in the given database. It will
   192  // fail with an asserts.NotFoundError if it cannot find them.
   193  // model is used to cross check that the found snap-revision is applicable
   194  // on the device.
   195  func DeriveSideInfoFromDigestAndSize(snapPath string, snapSHA3_384 string, snapSize uint64, model *asserts.Model, db Finder) (*snap.SideInfo, error) {
   196  	// get relevant assertions and reconstruct metadata
   197  	headers := map[string]string{
   198  		"snap-sha3-384": snapSHA3_384,
   199  	}
   200  	a, err := db.Find(asserts.SnapRevisionType, headers)
   201  	if err != nil && !asserts.IsNotFound(err) {
   202  		return nil, err
   203  	}
   204  	if a == nil {
   205  		// non-default provenance?
   206  		cands, err := db.FindMany(asserts.SnapRevisionType, headers)
   207  		if err != nil {
   208  			return nil, err
   209  		}
   210  		if len(cands) != 1 {
   211  			return nil, fmt.Errorf("safely handling snaps with different provenance but same hash not yet supported")
   212  		}
   213  		a = cands[0]
   214  	}
   215  
   216  	snapRev := a.(*asserts.SnapRevision)
   217  
   218  	if snapRev.SnapSize() != snapSize {
   219  		return nil, fmt.Errorf("snap %q does not have expected size according to signatures (broken or tampered): %d != %d", snapPath, snapSize, snapRev.SnapSize())
   220  	}
   221  
   222  	snapID := snapRev.SnapID()
   223  
   224  	snapDecl, err := findSnapDeclaration(snapID, snapPath, db)
   225  	if err != nil {
   226  		return nil, err
   227  	}
   228  
   229  	if _, err = CrossCheckProvenance(snapDecl.SnapName(), snapRev, snapDecl, model, db); err != nil {
   230  		return nil, err
   231  	}
   232  
   233  	if err := CheckProvenanceWithVerifiedRevision(snapPath, snapRev); err != nil {
   234  		return nil, err
   235  	}
   236  
   237  	return SideInfoFromSnapAssertions(snapDecl, snapRev), nil
   238  }
   239  
   240  // SideInfoFromSnapAssertions returns a *snap.SideInfo reflecting the given snap assertions.
   241  func SideInfoFromSnapAssertions(snapDecl *asserts.SnapDeclaration, snapRev *asserts.SnapRevision) *snap.SideInfo {
   242  	return &snap.SideInfo{
   243  		RealName: snapDecl.SnapName(),
   244  		SnapID:   snapDecl.SnapID(),
   245  		Revision: snap.R(snapRev.SnapRevision()),
   246  	}
   247  }
   248  
   249  // FetchSnapAssertions fetches the assertions matching the snap file digest and optional provenance using the given fetcher.
   250  func FetchSnapAssertions(f asserts.Fetcher, snapSHA3_384, provenance string) error {
   251  	// for now starting from the snap-revision will get us all other relevant assertions
   252  	ref := &asserts.Ref{
   253  		Type:       asserts.SnapRevisionType,
   254  		PrimaryKey: []string{snapSHA3_384},
   255  	}
   256  	if provenance != "" {
   257  		ref.PrimaryKey = append(ref.PrimaryKey, provenance)
   258  	}
   259  
   260  	return f.Fetch(ref)
   261  }
   262  
   263  // FetchSnapDeclaration fetches the snap declaration and its prerequisites for the given snap id using the given fetcher.
   264  func FetchSnapDeclaration(f asserts.Fetcher, snapID string) error {
   265  	ref := &asserts.Ref{
   266  		Type:       asserts.SnapDeclarationType,
   267  		PrimaryKey: []string{release.Series, snapID},
   268  	}
   269  
   270  	return f.Fetch(ref)
   271  }
   272  
   273  // FetchStore fetches the store assertion and its prerequisites for the given store id using the given fetcher.
   274  func FetchStore(f asserts.Fetcher, storeID string) error {
   275  	ref := &asserts.Ref{
   276  		Type:       asserts.StoreType,
   277  		PrimaryKey: []string{storeID},
   278  	}
   279  
   280  	return f.Fetch(ref)
   281  }