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