gopkg.in/ubuntu-core/snappy.v0@v0.0.0-20210902073436-25a8614f10a6/asserts/snapasserts/snapasserts_test.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2016 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_test
    21  
    22  import (
    23  	"crypto"
    24  	"fmt"
    25  	"io/ioutil"
    26  	"path/filepath"
    27  	"testing"
    28  	"time"
    29  
    30  	"golang.org/x/crypto/sha3"
    31  	. "gopkg.in/check.v1"
    32  
    33  	"github.com/snapcore/snapd/asserts"
    34  	"github.com/snapcore/snapd/asserts/assertstest"
    35  	"github.com/snapcore/snapd/asserts/snapasserts"
    36  	"github.com/snapcore/snapd/snap"
    37  )
    38  
    39  func TestSnapasserts(t *testing.T) { TestingT(t) }
    40  
    41  type snapassertsSuite struct {
    42  	storeSigning *assertstest.StoreStack
    43  	dev1Acct     *asserts.Account
    44  
    45  	localDB *asserts.Database
    46  }
    47  
    48  var _ = Suite(&snapassertsSuite{})
    49  
    50  func (s *snapassertsSuite) SetUpTest(c *C) {
    51  	s.storeSigning = assertstest.NewStoreStack("can0nical", nil)
    52  
    53  	s.dev1Acct = assertstest.NewAccount(s.storeSigning, "developer1", nil, "")
    54  
    55  	localDB, err := asserts.OpenDatabase(&asserts.DatabaseConfig{
    56  		Backstore: asserts.NewMemoryBackstore(),
    57  		Trusted:   s.storeSigning.Trusted,
    58  	})
    59  	c.Assert(err, IsNil)
    60  
    61  	s.localDB = localDB
    62  
    63  	// add in prereqs assertions
    64  	err = s.localDB.Add(s.storeSigning.StoreAccountKey(""))
    65  	c.Assert(err, IsNil)
    66  	err = s.localDB.Add(s.dev1Acct)
    67  	c.Assert(err, IsNil)
    68  
    69  	headers := map[string]interface{}{
    70  		"series":       "16",
    71  		"snap-id":      "snap-id-1",
    72  		"snap-name":    "foo",
    73  		"publisher-id": s.dev1Acct.AccountID(),
    74  		"timestamp":    time.Now().Format(time.RFC3339),
    75  	}
    76  	snapDecl, err := s.storeSigning.Sign(asserts.SnapDeclarationType, headers, nil, "")
    77  	c.Assert(err, IsNil)
    78  	err = s.localDB.Add(snapDecl)
    79  	c.Assert(err, IsNil)
    80  }
    81  
    82  func fakeSnap(rev int) []byte {
    83  	fake := fmt.Sprintf("hsqs________________%d", rev)
    84  	return []byte(fake)
    85  }
    86  
    87  func fakeHash(rev int) []byte {
    88  	h := sha3.Sum384(fakeSnap(rev))
    89  	return h[:]
    90  }
    91  
    92  func makeDigest(rev int) string {
    93  	d, err := asserts.EncodeDigest(crypto.SHA3_384, fakeHash(rev))
    94  	if err != nil {
    95  		panic(err)
    96  	}
    97  	return string(d)
    98  }
    99  
   100  func (s *snapassertsSuite) TestCrossCheckHappy(c *C) {
   101  	digest := makeDigest(12)
   102  	size := uint64(len(fakeSnap(12)))
   103  	headers := map[string]interface{}{
   104  		"snap-id":       "snap-id-1",
   105  		"snap-sha3-384": digest,
   106  		"snap-size":     fmt.Sprintf("%d", size),
   107  		"snap-revision": "12",
   108  		"developer-id":  s.dev1Acct.AccountID(),
   109  		"timestamp":     time.Now().Format(time.RFC3339),
   110  	}
   111  	snapRev, err := s.storeSigning.Sign(asserts.SnapRevisionType, headers, nil, "")
   112  	c.Assert(err, IsNil)
   113  	err = s.localDB.Add(snapRev)
   114  	c.Assert(err, IsNil)
   115  
   116  	si := &snap.SideInfo{
   117  		SnapID:   "snap-id-1",
   118  		Revision: snap.R(12),
   119  	}
   120  
   121  	// everything cross checks, with the regular snap name
   122  	err = snapasserts.CrossCheck("foo", digest, size, si, s.localDB)
   123  	c.Check(err, IsNil)
   124  	// and a snap instance name
   125  	err = snapasserts.CrossCheck("foo_instance", digest, size, si, s.localDB)
   126  	c.Check(err, IsNil)
   127  }
   128  
   129  func (s *snapassertsSuite) TestCrossCheckErrors(c *C) {
   130  	digest := makeDigest(12)
   131  	size := uint64(len(fakeSnap(12)))
   132  	headers := map[string]interface{}{
   133  		"snap-id":       "snap-id-1",
   134  		"snap-sha3-384": digest,
   135  		"snap-size":     fmt.Sprintf("%d", size),
   136  		"snap-revision": "12",
   137  		"developer-id":  s.dev1Acct.AccountID(),
   138  		"timestamp":     time.Now().Format(time.RFC3339),
   139  	}
   140  	snapRev, err := s.storeSigning.Sign(asserts.SnapRevisionType, headers, nil, "")
   141  	c.Assert(err, IsNil)
   142  	err = s.localDB.Add(snapRev)
   143  	c.Assert(err, IsNil)
   144  
   145  	si := &snap.SideInfo{
   146  		SnapID:   "snap-id-1",
   147  		Revision: snap.R(12),
   148  	}
   149  
   150  	// different size
   151  	err = snapasserts.CrossCheck("foo", digest, size+1, si, s.localDB)
   152  	c.Check(err, ErrorMatches, fmt.Sprintf(`snap "foo" file does not have expected size according to signatures \(download is broken or tampered\): %d != %d`, size+1, size))
   153  	err = snapasserts.CrossCheck("foo_instance", digest, size+1, si, s.localDB)
   154  	c.Check(err, ErrorMatches, fmt.Sprintf(`snap "foo_instance" file does not have expected size according to signatures \(download is broken or tampered\): %d != %d`, size+1, size))
   155  
   156  	// mismatched revision vs what we got from store original info
   157  	err = snapasserts.CrossCheck("foo", digest, size, &snap.SideInfo{
   158  		SnapID:   "snap-id-1",
   159  		Revision: snap.R(21),
   160  	}, s.localDB)
   161  	c.Check(err, ErrorMatches, `snap "foo" does not have expected ID or revision according to assertions \(metadata is broken or tampered\): 21 / snap-id-1 != 12 / snap-id-1`)
   162  	err = snapasserts.CrossCheck("foo_instance", digest, size, &snap.SideInfo{
   163  		SnapID:   "snap-id-1",
   164  		Revision: snap.R(21),
   165  	}, s.localDB)
   166  	c.Check(err, ErrorMatches, `snap "foo_instance" does not have expected ID or revision according to assertions \(metadata is broken or tampered\): 21 / snap-id-1 != 12 / snap-id-1`)
   167  
   168  	// mismatched snap id vs what we got from store original info
   169  	err = snapasserts.CrossCheck("foo", digest, size, &snap.SideInfo{
   170  		SnapID:   "snap-id-other",
   171  		Revision: snap.R(12),
   172  	}, s.localDB)
   173  	c.Check(err, ErrorMatches, `snap "foo" does not have expected ID or revision according to assertions \(metadata is broken or tampered\): 12 / snap-id-other != 12 / snap-id-1`)
   174  	err = snapasserts.CrossCheck("foo_instance", digest, size, &snap.SideInfo{
   175  		SnapID:   "snap-id-other",
   176  		Revision: snap.R(12),
   177  	}, s.localDB)
   178  	c.Check(err, ErrorMatches, `snap "foo_instance" does not have expected ID or revision according to assertions \(metadata is broken or tampered\): 12 / snap-id-other != 12 / snap-id-1`)
   179  
   180  	// changed name
   181  	err = snapasserts.CrossCheck("baz", digest, size, si, s.localDB)
   182  	c.Check(err, ErrorMatches, `cannot install "baz", snap "baz" is undergoing a rename to "foo"`)
   183  	err = snapasserts.CrossCheck("baz_instance", digest, size, si, s.localDB)
   184  	c.Check(err, ErrorMatches, `cannot install "baz_instance", snap "baz" is undergoing a rename to "foo"`)
   185  
   186  }
   187  
   188  func (s *snapassertsSuite) TestCrossCheckRevokedSnapDecl(c *C) {
   189  	// revoked snap declaration (snap-name=="") !
   190  	headers := map[string]interface{}{
   191  		"series":       "16",
   192  		"snap-id":      "snap-id-1",
   193  		"snap-name":    "",
   194  		"publisher-id": s.dev1Acct.AccountID(),
   195  		"revision":     "1",
   196  		"timestamp":    time.Now().Format(time.RFC3339),
   197  	}
   198  	snapDecl, err := s.storeSigning.Sign(asserts.SnapDeclarationType, headers, nil, "")
   199  	c.Assert(err, IsNil)
   200  	err = s.localDB.Add(snapDecl)
   201  	c.Assert(err, IsNil)
   202  
   203  	digest := makeDigest(12)
   204  	size := uint64(len(fakeSnap(12)))
   205  	headers = map[string]interface{}{
   206  		"snap-id":       "snap-id-1",
   207  		"snap-sha3-384": digest,
   208  		"snap-size":     fmt.Sprintf("%d", size),
   209  		"snap-revision": "12",
   210  		"developer-id":  s.dev1Acct.AccountID(),
   211  		"timestamp":     time.Now().Format(time.RFC3339),
   212  	}
   213  	snapRev, err := s.storeSigning.Sign(asserts.SnapRevisionType, headers, nil, "")
   214  	c.Assert(err, IsNil)
   215  	err = s.localDB.Add(snapRev)
   216  	c.Assert(err, IsNil)
   217  
   218  	si := &snap.SideInfo{
   219  		SnapID:   "snap-id-1",
   220  		Revision: snap.R(12),
   221  	}
   222  
   223  	err = snapasserts.CrossCheck("foo", digest, size, si, s.localDB)
   224  	c.Check(err, ErrorMatches, `cannot install snap "foo" with a revoked snap declaration`)
   225  	err = snapasserts.CrossCheck("foo_instance", digest, size, si, s.localDB)
   226  	c.Check(err, ErrorMatches, `cannot install snap "foo_instance" with a revoked snap declaration`)
   227  }
   228  
   229  func (s *snapassertsSuite) TestDeriveSideInfoHappy(c *C) {
   230  	digest := makeDigest(42)
   231  	size := uint64(len(fakeSnap(42)))
   232  	headers := map[string]interface{}{
   233  		"snap-id":       "snap-id-1",
   234  		"snap-sha3-384": digest,
   235  		"snap-size":     fmt.Sprintf("%d", size),
   236  		"snap-revision": "42",
   237  		"developer-id":  s.dev1Acct.AccountID(),
   238  		"timestamp":     time.Now().Format(time.RFC3339),
   239  	}
   240  	snapRev, err := s.storeSigning.Sign(asserts.SnapRevisionType, headers, nil, "")
   241  	c.Assert(err, IsNil)
   242  	err = s.localDB.Add(snapRev)
   243  	c.Assert(err, IsNil)
   244  
   245  	tempdir := c.MkDir()
   246  	snapPath := filepath.Join(tempdir, "anon.snap")
   247  	err = ioutil.WriteFile(snapPath, fakeSnap(42), 0644)
   248  	c.Assert(err, IsNil)
   249  
   250  	si, err := snapasserts.DeriveSideInfo(snapPath, s.localDB)
   251  	c.Assert(err, IsNil)
   252  	c.Check(si, DeepEquals, &snap.SideInfo{
   253  		RealName: "foo",
   254  		SnapID:   "snap-id-1",
   255  		Revision: snap.R(42),
   256  		Channel:  "",
   257  	})
   258  }
   259  
   260  func (s *snapassertsSuite) TestDeriveSideInfoNoSignatures(c *C) {
   261  	tempdir := c.MkDir()
   262  	snapPath := filepath.Join(tempdir, "anon.snap")
   263  	err := ioutil.WriteFile(snapPath, fakeSnap(42), 0644)
   264  	c.Assert(err, IsNil)
   265  
   266  	_, err = snapasserts.DeriveSideInfo(snapPath, s.localDB)
   267  	// cannot find signatures with metadata for snap
   268  	c.Assert(asserts.IsNotFound(err), Equals, true)
   269  }
   270  
   271  func (s *snapassertsSuite) TestDeriveSideInfoSizeMismatch(c *C) {
   272  	digest := makeDigest(42)
   273  	size := uint64(len(fakeSnap(42)))
   274  	headers := map[string]interface{}{
   275  		"snap-id":       "snap-id-1",
   276  		"snap-sha3-384": digest,
   277  		"snap-size":     fmt.Sprintf("%d", size+5), // broken
   278  		"snap-revision": "42",
   279  		"developer-id":  s.dev1Acct.AccountID(),
   280  		"timestamp":     time.Now().Format(time.RFC3339),
   281  	}
   282  	snapRev, err := s.storeSigning.Sign(asserts.SnapRevisionType, headers, nil, "")
   283  	c.Assert(err, IsNil)
   284  	err = s.localDB.Add(snapRev)
   285  	c.Assert(err, IsNil)
   286  
   287  	tempdir := c.MkDir()
   288  	snapPath := filepath.Join(tempdir, "anon.snap")
   289  	err = ioutil.WriteFile(snapPath, fakeSnap(42), 0644)
   290  	c.Assert(err, IsNil)
   291  
   292  	_, err = snapasserts.DeriveSideInfo(snapPath, s.localDB)
   293  	c.Check(err, ErrorMatches, fmt.Sprintf(`snap %q does not have expected size according to signatures \(broken or tampered\): %d != %d`, snapPath, size, size+5))
   294  }
   295  
   296  func (s *snapassertsSuite) TestDeriveSideInfoRevokedSnapDecl(c *C) {
   297  	// revoked snap declaration (snap-name=="") !
   298  	headers := map[string]interface{}{
   299  		"series":       "16",
   300  		"snap-id":      "snap-id-1",
   301  		"snap-name":    "",
   302  		"publisher-id": s.dev1Acct.AccountID(),
   303  		"revision":     "1",
   304  		"timestamp":    time.Now().Format(time.RFC3339),
   305  	}
   306  	snapDecl, err := s.storeSigning.Sign(asserts.SnapDeclarationType, headers, nil, "")
   307  	c.Assert(err, IsNil)
   308  	err = s.localDB.Add(snapDecl)
   309  	c.Assert(err, IsNil)
   310  
   311  	digest := makeDigest(42)
   312  	size := uint64(len(fakeSnap(42)))
   313  	headers = map[string]interface{}{
   314  		"snap-id":       "snap-id-1",
   315  		"snap-sha3-384": digest,
   316  		"snap-size":     fmt.Sprintf("%d", size),
   317  		"snap-revision": "42",
   318  		"developer-id":  s.dev1Acct.AccountID(),
   319  		"timestamp":     time.Now().Format(time.RFC3339),
   320  	}
   321  	snapRev, err := s.storeSigning.Sign(asserts.SnapRevisionType, headers, nil, "")
   322  	c.Assert(err, IsNil)
   323  	err = s.localDB.Add(snapRev)
   324  	c.Assert(err, IsNil)
   325  
   326  	tempdir := c.MkDir()
   327  	snapPath := filepath.Join(tempdir, "anon.snap")
   328  	err = ioutil.WriteFile(snapPath, fakeSnap(42), 0644)
   329  	c.Assert(err, IsNil)
   330  
   331  	_, err = snapasserts.DeriveSideInfo(snapPath, s.localDB)
   332  	c.Check(err, ErrorMatches, fmt.Sprintf(`cannot install snap %q with a revoked snap declaration`, snapPath))
   333  }