github.com/hugh712/snapd@v0.0.0-20200910133618-1a99902bd583/asserts/serial_asserts_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 asserts_test
    21  
    22  import (
    23  	"strings"
    24  	"time"
    25  
    26  	. "gopkg.in/check.v1"
    27  
    28  	"github.com/snapcore/snapd/asserts"
    29  	"github.com/snapcore/snapd/asserts/assertstest"
    30  )
    31  
    32  var (
    33  	_ = Suite(&serialSuite{})
    34  )
    35  
    36  type serialSuite struct {
    37  	ts            time.Time
    38  	tsLine        string
    39  	deviceKey     asserts.PrivateKey
    40  	encodedDevKey string
    41  }
    42  
    43  func (ss *serialSuite) SetUpSuite(c *C) {
    44  	ss.ts = time.Now().Truncate(time.Second).UTC()
    45  	ss.tsLine = "timestamp: " + ss.ts.Format(time.RFC3339) + "\n"
    46  
    47  	ss.deviceKey = testPrivKey2
    48  	encodedPubKey, err := asserts.EncodePublicKey(ss.deviceKey.PublicKey())
    49  	c.Assert(err, IsNil)
    50  	ss.encodedDevKey = string(encodedPubKey)
    51  }
    52  
    53  const serialExample = "type: serial\n" +
    54  	"authority-id: brand-id1\n" +
    55  	"brand-id: brand-id1\n" +
    56  	"model: baz-3000\n" +
    57  	"serial: 2700\n" +
    58  	"device-key:\n    DEVICEKEY\n" +
    59  	"device-key-sha3-384: KEYID\n" +
    60  	"TSLINE" +
    61  	"body-length: 2\n" +
    62  	"sign-key-sha3-384: Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij\n\n" +
    63  	"HW" +
    64  	"\n\n" +
    65  	"AXNpZw=="
    66  
    67  func (ss *serialSuite) TestDecodeOK(c *C) {
    68  	encoded := strings.Replace(serialExample, "TSLINE", ss.tsLine, 1)
    69  	encoded = strings.Replace(encoded, "DEVICEKEY", strings.Replace(ss.encodedDevKey, "\n", "\n    ", -1), 1)
    70  	encoded = strings.Replace(encoded, "KEYID", ss.deviceKey.PublicKey().ID(), 1)
    71  	a, err := asserts.Decode([]byte(encoded))
    72  	c.Assert(err, IsNil)
    73  	c.Check(a.Type(), Equals, asserts.SerialType)
    74  	serial := a.(*asserts.Serial)
    75  	c.Check(serial.AuthorityID(), Equals, "brand-id1")
    76  	c.Check(serial.Timestamp(), Equals, ss.ts)
    77  	c.Check(serial.BrandID(), Equals, "brand-id1")
    78  	c.Check(serial.Model(), Equals, "baz-3000")
    79  	c.Check(serial.Serial(), Equals, "2700")
    80  	c.Check(serial.DeviceKey().ID(), Equals, ss.deviceKey.PublicKey().ID())
    81  }
    82  
    83  const (
    84  	deviceSessReqErrPrefix = "assertion device-session-request: "
    85  	serialErrPrefix        = "assertion serial: "
    86  	serialReqErrPrefix     = "assertion serial-request: "
    87  )
    88  
    89  func (ss *serialSuite) TestDecodeInvalid(c *C) {
    90  	encoded := strings.Replace(serialExample, "TSLINE", ss.tsLine, 1)
    91  
    92  	invalidTests := []struct{ original, invalid, expectedErr string }{
    93  		{"brand-id: brand-id1\n", "", `"brand-id" header is mandatory`},
    94  		{"brand-id: brand-id1\n", "brand-id: \n", `"brand-id" header should not be empty`},
    95  		{"brand-id: brand-id1\n", "brand-id: ,1\n", `"brand-id" header contains invalid characters: ",1\"`},
    96  		{"model: baz-3000\n", "", `"model" header is mandatory`},
    97  		{"model: baz-3000\n", "model: \n", `"model" header should not be empty`},
    98  		{"model: baz-3000\n", "model: _what\n", `"model" header contains invalid characters: "_what"`},
    99  		{"serial: 2700\n", "", `"serial" header is mandatory`},
   100  		{"serial: 2700\n", "serial: \n", `"serial" header should not be empty`},
   101  		{ss.tsLine, "", `"timestamp" header is mandatory`},
   102  		{ss.tsLine, "timestamp: \n", `"timestamp" header should not be empty`},
   103  		{ss.tsLine, "timestamp: 12:30\n", `"timestamp" header is not a RFC3339 date: .*`},
   104  		{"device-key:\n    DEVICEKEY\n", "", `"device-key" header is mandatory`},
   105  		{"device-key:\n    DEVICEKEY\n", "device-key: \n", `"device-key" header should not be empty`},
   106  		{"device-key:\n    DEVICEKEY\n", "device-key: $$$\n", `cannot decode public key: .*`},
   107  		{"device-key-sha3-384: KEYID\n", "", `"device-key-sha3-384" header is mandatory`},
   108  	}
   109  
   110  	for _, test := range invalidTests {
   111  		invalid := strings.Replace(encoded, test.original, test.invalid, 1)
   112  		invalid = strings.Replace(invalid, "DEVICEKEY", strings.Replace(ss.encodedDevKey, "\n", "\n    ", -1), 1)
   113  		invalid = strings.Replace(invalid, "KEYID", ss.deviceKey.PublicKey().ID(), 1)
   114  		_, err := asserts.Decode([]byte(invalid))
   115  		c.Check(err, ErrorMatches, serialErrPrefix+test.expectedErr)
   116  	}
   117  }
   118  
   119  func (ss *serialSuite) TestDecodeKeyIDMismatch(c *C) {
   120  	invalid := strings.Replace(serialExample, "TSLINE", ss.tsLine, 1)
   121  	invalid = strings.Replace(invalid, "DEVICEKEY", strings.Replace(ss.encodedDevKey, "\n", "\n    ", -1), 1)
   122  	invalid = strings.Replace(invalid, "KEYID", "Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij", 1)
   123  
   124  	_, err := asserts.Decode([]byte(invalid))
   125  	c.Check(err, ErrorMatches, serialErrPrefix+"device key does not match provided key id")
   126  }
   127  
   128  func (ss *serialSuite) TestSerialCheck(c *C) {
   129  	encoded := strings.Replace(serialExample, "TSLINE", ss.tsLine, 1)
   130  	encoded = strings.Replace(encoded, "DEVICEKEY", strings.Replace(ss.encodedDevKey, "\n", "\n    ", -1), 1)
   131  	encoded = strings.Replace(encoded, "KEYID", ss.deviceKey.PublicKey().ID(), 1)
   132  	ex, err := asserts.Decode([]byte(encoded))
   133  	c.Assert(err, IsNil)
   134  
   135  	storeDB, db := makeStoreAndCheckDB(c)
   136  	brandDB := setup3rdPartySigning(c, "brand1", storeDB, db)
   137  
   138  	const serialMismatchErr = `serial with authority "generic" different from brand "brand1" without model assertion with serial-authority set to to allow for them`
   139  	brandID := brandDB.AuthorityID
   140  	brandKeyID := brandDB.KeyID
   141  	genericKeyID := storeDB.GenericKey.PublicKeyID()
   142  	modelNA := []interface{}(nil)
   143  	brandOnly := []interface{}{}
   144  	tests := []struct {
   145  		// serial-authority setting in model
   146  		// nil == model not available at check (modelNA)
   147  		// empty ==  just brand (brandOnly)
   148  		serialAuth  []interface{}
   149  		signDB      assertstest.SignerDB
   150  		authID      string
   151  		keyID       string
   152  		expectedErr string
   153  	}{
   154  		{modelNA, brandDB, "", brandKeyID, ""},
   155  		{brandOnly, brandDB, "", brandKeyID, ""},
   156  		{[]interface{}{"generic"}, brandDB, "", brandKeyID, ""},
   157  		{[]interface{}{"generic", brandID}, brandDB, "", brandKeyID, ""},
   158  		{[]interface{}{"generic"}, storeDB, "generic", genericKeyID, ""},
   159  		{brandOnly, storeDB, "generic", genericKeyID, serialMismatchErr},
   160  		{modelNA, storeDB, "generic", genericKeyID, serialMismatchErr},
   161  		{[]interface{}{"other"}, storeDB, "generic", genericKeyID, serialMismatchErr},
   162  	}
   163  
   164  	for _, test := range tests {
   165  		checkDB := db.WithStackedBackstore(asserts.NewMemoryBackstore())
   166  
   167  		if test.serialAuth != nil {
   168  			modHeaders := map[string]interface{}{
   169  				"series":       "16",
   170  				"brand-id":     brandID,
   171  				"architecture": "amd64",
   172  				"model":        "baz-3000",
   173  				"gadget":       "gadget",
   174  				"kernel":       "kernel",
   175  				"timestamp":    time.Now().Format(time.RFC3339),
   176  			}
   177  			if len(test.serialAuth) != 0 {
   178  				modHeaders["serial-authority"] = test.serialAuth
   179  			}
   180  			model, err := brandDB.Sign(asserts.ModelType, modHeaders, nil, "")
   181  			c.Assert(err, IsNil)
   182  			err = checkDB.Add(model)
   183  			c.Assert(err, IsNil)
   184  		}
   185  
   186  		headers := ex.Headers()
   187  		headers["brand-id"] = brandID
   188  		if test.authID != "" {
   189  			headers["authority-id"] = test.authID
   190  		} else {
   191  			headers["authority-id"] = brandID
   192  		}
   193  		headers["timestamp"] = time.Now().Format(time.RFC3339)
   194  		serial, err := test.signDB.Sign(asserts.SerialType, headers, nil, test.keyID)
   195  		c.Check(err, IsNil)
   196  		if err != nil {
   197  			continue
   198  		}
   199  
   200  		err = checkDB.Check(serial)
   201  		if test.expectedErr == "" {
   202  			c.Check(err, IsNil)
   203  		} else {
   204  			c.Check(err, ErrorMatches, test.expectedErr)
   205  		}
   206  	}
   207  }
   208  
   209  func (ss *serialSuite) TestSerialRequestHappy(c *C) {
   210  	sreq, err := asserts.SignWithoutAuthority(asserts.SerialRequestType,
   211  		map[string]interface{}{
   212  			"brand-id":   "brand-id1",
   213  			"model":      "baz-3000",
   214  			"device-key": ss.encodedDevKey,
   215  			"request-id": "REQID",
   216  		}, []byte("HW-DETAILS"), ss.deviceKey)
   217  	c.Assert(err, IsNil)
   218  
   219  	// roundtrip
   220  	a, err := asserts.Decode(asserts.Encode(sreq))
   221  	c.Assert(err, IsNil)
   222  
   223  	sreq2, ok := a.(*asserts.SerialRequest)
   224  	c.Assert(ok, Equals, true)
   225  
   226  	// standalone signature check
   227  	err = asserts.SignatureCheck(sreq2, sreq2.DeviceKey())
   228  	c.Check(err, IsNil)
   229  
   230  	c.Check(sreq2.BrandID(), Equals, "brand-id1")
   231  	c.Check(sreq2.Model(), Equals, "baz-3000")
   232  	c.Check(sreq2.RequestID(), Equals, "REQID")
   233  
   234  	c.Check(sreq2.Serial(), Equals, "")
   235  }
   236  
   237  func (ss *serialSuite) TestSerialRequestHappyOptionalSerial(c *C) {
   238  	sreq, err := asserts.SignWithoutAuthority(asserts.SerialRequestType,
   239  		map[string]interface{}{
   240  			"brand-id":   "brand-id1",
   241  			"model":      "baz-3000",
   242  			"serial":     "pserial",
   243  			"device-key": ss.encodedDevKey,
   244  			"request-id": "REQID",
   245  		}, []byte("HW-DETAILS"), ss.deviceKey)
   246  	c.Assert(err, IsNil)
   247  
   248  	// roundtrip
   249  	a, err := asserts.Decode(asserts.Encode(sreq))
   250  	c.Assert(err, IsNil)
   251  
   252  	sreq2, ok := a.(*asserts.SerialRequest)
   253  	c.Assert(ok, Equals, true)
   254  
   255  	c.Check(sreq2.Model(), Equals, "baz-3000")
   256  	c.Check(sreq2.Serial(), Equals, "pserial")
   257  }
   258  
   259  func (ss *serialSuite) TestSerialRequestDecodeInvalid(c *C) {
   260  	encoded := "type: serial-request\n" +
   261  		"brand-id: brand-id1\n" +
   262  		"model: baz-3000\n" +
   263  		"device-key:\n    DEVICEKEY\n" +
   264  		"request-id: REQID\n" +
   265  		"serial: S\n" +
   266  		"body-length: 2\n" +
   267  		"sign-key-sha3-384: " + ss.deviceKey.PublicKey().ID() + "\n\n" +
   268  		"HW" +
   269  		"\n\n" +
   270  		"AXNpZw=="
   271  
   272  	invalidTests := []struct{ original, invalid, expectedErr string }{
   273  		{"brand-id: brand-id1\n", "", `"brand-id" header is mandatory`},
   274  		{"brand-id: brand-id1\n", "brand-id: \n", `"brand-id" header should not be empty`},
   275  		{"model: baz-3000\n", "", `"model" header is mandatory`},
   276  		{"model: baz-3000\n", "model: \n", `"model" header should not be empty`},
   277  		{"request-id: REQID\n", "", `"request-id" header is mandatory`},
   278  		{"request-id: REQID\n", "request-id: \n", `"request-id" header should not be empty`},
   279  		{"device-key:\n    DEVICEKEY\n", "", `"device-key" header is mandatory`},
   280  		{"device-key:\n    DEVICEKEY\n", "device-key: \n", `"device-key" header should not be empty`},
   281  		{"device-key:\n    DEVICEKEY\n", "device-key: $$$\n", `cannot decode public key: .*`},
   282  		{"serial: S\n", "serial:\n  - xyz\n", `"serial" header must be a string`},
   283  	}
   284  
   285  	for _, test := range invalidTests {
   286  		invalid := strings.Replace(encoded, test.original, test.invalid, 1)
   287  		invalid = strings.Replace(invalid, "DEVICEKEY", strings.Replace(ss.encodedDevKey, "\n", "\n    ", -1), 1)
   288  
   289  		_, err := asserts.Decode([]byte(invalid))
   290  		c.Check(err, ErrorMatches, serialReqErrPrefix+test.expectedErr)
   291  	}
   292  }
   293  
   294  func (ss *serialSuite) TestSerialRequestDecodeKeyIDMismatch(c *C) {
   295  	invalid := "type: serial-request\n" +
   296  		"brand-id: brand-id1\n" +
   297  		"model: baz-3000\n" +
   298  		"device-key:\n    " + strings.Replace(ss.encodedDevKey, "\n", "\n    ", -1) + "\n" +
   299  		"request-id: REQID\n" +
   300  		"body-length: 2\n" +
   301  		"sign-key-sha3-384: Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij\n\n" +
   302  		"HW" +
   303  		"\n\n" +
   304  		"AXNpZw=="
   305  
   306  	_, err := asserts.Decode([]byte(invalid))
   307  	c.Check(err, ErrorMatches, "assertion serial-request: device key does not match included signing key id")
   308  }
   309  
   310  func (ss *serialSuite) TestDeviceSessionRequest(c *C) {
   311  	ts := time.Now().UTC().Round(time.Second)
   312  	sessReq, err := asserts.SignWithoutAuthority(asserts.DeviceSessionRequestType,
   313  		map[string]interface{}{
   314  			"brand-id":  "brand-id1",
   315  			"model":     "baz-3000",
   316  			"serial":    "99990",
   317  			"nonce":     "NONCE",
   318  			"timestamp": ts.Format(time.RFC3339),
   319  		}, nil, ss.deviceKey)
   320  	c.Assert(err, IsNil)
   321  
   322  	// roundtrip
   323  	a, err := asserts.Decode(asserts.Encode(sessReq))
   324  	c.Assert(err, IsNil)
   325  
   326  	sessReq2, ok := a.(*asserts.DeviceSessionRequest)
   327  	c.Assert(ok, Equals, true)
   328  
   329  	// standalone signature check
   330  	err = asserts.SignatureCheck(sessReq2, ss.deviceKey.PublicKey())
   331  	c.Check(err, IsNil)
   332  
   333  	c.Check(sessReq2.BrandID(), Equals, "brand-id1")
   334  	c.Check(sessReq2.Model(), Equals, "baz-3000")
   335  	c.Check(sessReq2.Serial(), Equals, "99990")
   336  	c.Check(sessReq2.Nonce(), Equals, "NONCE")
   337  	c.Check(sessReq2.Timestamp().Equal(ts), Equals, true)
   338  }
   339  
   340  func (ss *serialSuite) TestDeviceSessionRequestDecodeInvalid(c *C) {
   341  	tsLine := "timestamp: " + time.Now().Format(time.RFC3339) + "\n"
   342  	encoded := "type: device-session-request\n" +
   343  		"brand-id: brand-id1\n" +
   344  		"model: baz-3000\n" +
   345  		"serial: 99990\n" +
   346  		"nonce: NONCE\n" +
   347  		tsLine +
   348  		"body-length: 0\n" +
   349  		"sign-key-sha3-384: " + ss.deviceKey.PublicKey().ID() + "\n\n" +
   350  		"AXNpZw=="
   351  
   352  	invalidTests := []struct{ original, invalid, expectedErr string }{
   353  		{"brand-id: brand-id1\n", "brand-id: \n", `"brand-id" header should not be empty`},
   354  		{"model: baz-3000\n", "model: \n", `"model" header should not be empty`},
   355  		{"serial: 99990\n", "", `"serial" header is mandatory`},
   356  		{"nonce: NONCE\n", "nonce: \n", `"nonce" header should not be empty`},
   357  		{tsLine, "timestamp: 12:30\n", `"timestamp" header is not a RFC3339 date: .*`},
   358  	}
   359  
   360  	for _, test := range invalidTests {
   361  		invalid := strings.Replace(encoded, test.original, test.invalid, 1)
   362  		_, err := asserts.Decode([]byte(invalid))
   363  		c.Check(err, ErrorMatches, deviceSessReqErrPrefix+test.expectedErr)
   364  	}
   365  }