github.com/anonymouse64/snapd@v0.0.0-20210824153203-04c4c42d842d/asserts/store_asserts_test.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2017 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_test
    21  
    22  import (
    23  	"fmt"
    24  	"strings"
    25  	"time"
    26  
    27  	. "gopkg.in/check.v1"
    28  
    29  	"github.com/snapcore/snapd/asserts"
    30  	"github.com/snapcore/snapd/asserts/assertstest"
    31  )
    32  
    33  var _ = Suite(&storeSuite{})
    34  
    35  type storeSuite struct {
    36  	ts           time.Time
    37  	tsLine       string
    38  	validExample string
    39  }
    40  
    41  func (s *storeSuite) SetUpSuite(c *C) {
    42  	s.ts = time.Now().Truncate(time.Second).UTC()
    43  	s.tsLine = "timestamp: " + s.ts.Format(time.RFC3339) + "\n"
    44  	s.validExample = "type: store\n" +
    45  		"authority-id: canonical\n" +
    46  		"store: store1\n" +
    47  		"operator-id: op-id1\n" +
    48  		"url: https://store.example.com\n" +
    49  		"location: upstairs\n" +
    50  		s.tsLine +
    51  		"sign-key-sha3-384: Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij\n" +
    52  		"\n" +
    53  		"AXNpZw=="
    54  }
    55  
    56  func (s *storeSuite) TestDecodeOK(c *C) {
    57  	a, err := asserts.Decode([]byte(s.validExample))
    58  	c.Assert(err, IsNil)
    59  	c.Check(a.Type(), Equals, asserts.StoreType)
    60  	store := a.(*asserts.Store)
    61  
    62  	c.Check(store.OperatorID(), Equals, "op-id1")
    63  	c.Check(store.Store(), Equals, "store1")
    64  	c.Check(store.URL().String(), Equals, "https://store.example.com")
    65  	c.Check(store.Location(), Equals, "upstairs")
    66  	c.Check(store.Timestamp().Equal(s.ts), Equals, true)
    67  	c.Check(store.FriendlyStores(), HasLen, 0)
    68  }
    69  
    70  var storeErrPrefix = "assertion store: "
    71  
    72  func (s *storeSuite) TestDecodeInvalidHeaders(c *C) {
    73  	tests := []struct{ original, invalid, expectedErr string }{
    74  		{"store: store1\n", "", `"store" header is mandatory`},
    75  		{"store: store1\n", "store: \n", `"store" header should not be empty`},
    76  		{"operator-id: op-id1\n", "", `"operator-id" header is mandatory`},
    77  		{"operator-id: op-id1\n", "operator-id: \n", `"operator-id" header should not be empty`},
    78  		{"url: https://store.example.com\n", "url:\n  - foo\n", `"url" header must be a string`},
    79  		{"location: upstairs\n", "location:\n  - foo\n", `"location" header must be a string`},
    80  		{s.tsLine, "", `"timestamp" header is mandatory`},
    81  		{s.tsLine, "timestamp: \n", `"timestamp" header should not be empty`},
    82  		{s.tsLine, "timestamp: 12:30\n", `"timestamp" header is not a RFC3339 date: .*`},
    83  		{"url: https://store.example.com\n", "friendly-stores: foo\n", `"friendly-stores" header must be a list of strings`},
    84  	}
    85  
    86  	for _, test := range tests {
    87  		invalid := strings.Replace(s.validExample, test.original, test.invalid, 1)
    88  		_, err := asserts.Decode([]byte(invalid))
    89  		c.Check(err, ErrorMatches, storeErrPrefix+test.expectedErr)
    90  	}
    91  }
    92  
    93  func (s *storeSuite) TestURLOptional(c *C) {
    94  	tests := []string{"", "url: \n"}
    95  	for _, test := range tests {
    96  		encoded := strings.Replace(s.validExample, "url: https://store.example.com\n", test, 1)
    97  		assert, err := asserts.Decode([]byte(encoded))
    98  		c.Assert(err, IsNil)
    99  		store := assert.(*asserts.Store)
   100  		c.Check(store.URL(), IsNil)
   101  	}
   102  }
   103  
   104  func (s *storeSuite) TestURL(c *C) {
   105  	tests := []struct {
   106  		url string
   107  		err string
   108  	}{
   109  		// Valid URLs.
   110  		{"http://example.com/", ""},
   111  		{"https://example.com/", ""},
   112  		{"https://example.com/some/path/", ""},
   113  		{"https://example.com:443/", ""},
   114  		{"https://example.com:1234/", ""},
   115  		{"https://user:pass@example.com/", ""},
   116  		{"https://token@example.com/", ""},
   117  
   118  		// Invalid URLs.
   119  		{"://example.com", `"url" header must be a valid URL`},
   120  		{"example.com", `"url" header scheme must be "https" or "http"`},
   121  		{"//example.com", `"url" header scheme must be "https" or "http"`},
   122  		{"ftp://example.com", `"url" header scheme must be "https" or "http"`},
   123  		{"mailto:someone@example.com", `"url" header scheme must be "https" or "http"`},
   124  		{"https://", `"url" header must have a host`},
   125  		{"https:///", `"url" header must have a host`},
   126  		{"https:///some/path", `"url" header must have a host`},
   127  		{"https://example.com/?foo=bar", `"url" header must not have a query`},
   128  		{"https://example.com/#fragment", `"url" header must not have a fragment`},
   129  	}
   130  
   131  	for _, test := range tests {
   132  		encoded := strings.Replace(
   133  			s.validExample, "url: https://store.example.com\n",
   134  			fmt.Sprintf("url: %s\n", test.url), 1)
   135  		assert, err := asserts.Decode([]byte(encoded))
   136  		if test.err != "" {
   137  			c.Assert(err, NotNil)
   138  			c.Check(err.Error(), Equals, storeErrPrefix+test.err+": "+test.url)
   139  		} else {
   140  			c.Assert(err, IsNil)
   141  			c.Check(assert.(*asserts.Store).URL().String(), Equals, test.url)
   142  		}
   143  	}
   144  }
   145  
   146  func (s *storeSuite) TestLocationOptional(c *C) {
   147  	encoded := strings.Replace(s.validExample, "location: upstairs\n", "", 1)
   148  	_, err := asserts.Decode([]byte(encoded))
   149  	c.Check(err, IsNil)
   150  }
   151  
   152  func (s *storeSuite) TestLocation(c *C) {
   153  	for _, test := range []string{"foo", "bar", ""} {
   154  		encoded := strings.Replace(
   155  			s.validExample, "location: upstairs\n",
   156  			fmt.Sprintf("location: %s\n", test), 1)
   157  		assert, err := asserts.Decode([]byte(encoded))
   158  		c.Assert(err, IsNil)
   159  		store := assert.(*asserts.Store)
   160  		c.Check(store.Location(), Equals, test)
   161  	}
   162  }
   163  
   164  func (s *storeSuite) TestCheckAuthority(c *C) {
   165  	storeDB, db := makeStoreAndCheckDB(c)
   166  
   167  	// Add account for operator.
   168  	operator := assertstest.NewAccount(storeDB, "op-id1", nil, "")
   169  	err := db.Add(operator)
   170  	c.Assert(err, IsNil)
   171  
   172  	otherDB := setup3rdPartySigning(c, "other", storeDB, db)
   173  
   174  	storeHeaders := map[string]interface{}{
   175  		"store":       "store1",
   176  		"operator-id": operator.HeaderString("account-id"),
   177  		"timestamp":   time.Now().Format(time.RFC3339),
   178  	}
   179  
   180  	// store signed by some other account fails.
   181  	store, err := otherDB.Sign(asserts.StoreType, storeHeaders, nil, "")
   182  	c.Assert(err, IsNil)
   183  	err = db.Check(store)
   184  	c.Assert(err, ErrorMatches, `store assertion "store1" is not signed by a directly trusted authority: other`)
   185  
   186  	// but succeeds when signed by a trusted authority.
   187  	store, err = storeDB.Sign(asserts.StoreType, storeHeaders, nil, "")
   188  	c.Assert(err, IsNil)
   189  	err = db.Check(store)
   190  	c.Assert(err, IsNil)
   191  }
   192  
   193  func (s *storeSuite) TestFriendlyStores(c *C) {
   194  	encoded := strings.Replace(s.validExample, "url: https://store.example.com\n", `friendly-stores:
   195    - store1
   196    - store2
   197    - store3
   198  `, 1)
   199  	assert, err := asserts.Decode([]byte(encoded))
   200  	c.Assert(err, IsNil)
   201  	store := assert.(*asserts.Store)
   202  	c.Check(store.URL(), IsNil)
   203  	c.Check(store.FriendlyStores(), DeepEquals, []string{"store1", "store2", "store3"})
   204  }
   205  
   206  func (s *storeSuite) TestCheckOperatorAccount(c *C) {
   207  	storeDB, db := makeStoreAndCheckDB(c)
   208  
   209  	store, err := storeDB.Sign(asserts.StoreType, map[string]interface{}{
   210  		"store":       "store1",
   211  		"operator-id": "op-id1",
   212  		"timestamp":   time.Now().Format(time.RFC3339),
   213  	}, nil, "")
   214  	c.Assert(err, IsNil)
   215  
   216  	// No account for operator op-id1 yet, so Check fails.
   217  	err = db.Check(store)
   218  	c.Assert(err, ErrorMatches, `store assertion "store1" does not have a matching account assertion for the operator "op-id1"`)
   219  
   220  	// Add the op-id1 account.
   221  	operator := assertstest.NewAccount(storeDB, "op-id1", map[string]interface{}{"account-id": "op-id1"}, "")
   222  	err = db.Add(operator)
   223  	c.Assert(err, IsNil)
   224  
   225  	// Now the operator exists so Check succeeds.
   226  	err = db.Check(store)
   227  	c.Assert(err, IsNil)
   228  }
   229  
   230  func (s *storeSuite) TestPrerequisites(c *C) {
   231  	assert, err := asserts.Decode([]byte(s.validExample))
   232  	c.Assert(err, IsNil)
   233  	c.Assert(assert.Prerequisites(), DeepEquals, []*asserts.Ref{
   234  		{Type: asserts.AccountType, PrimaryKey: []string{"op-id1"}},
   235  	})
   236  }