mirror of
https://github.com/token2/snapd.git
synced 2026-03-13 11:15:47 -07:00
Allow plugs to specify the $PLUG_PUBLISHER_ID in attribute constraints such that interfaces can restrict auto-connection to when certain attributes match their publisher IDs. Signed-off-by: Miguel Pires <miguel.pires@canonical.com>
2479 lines
84 KiB
Go
2479 lines
84 KiB
Go
// -*- Mode: Go; indent-tabs-mode: t -*-
|
|
|
|
/*
|
|
* Copyright (C) 2015-2022 Canonical Ltd
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 3 as
|
|
* published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
*/
|
|
|
|
package asserts_test
|
|
|
|
import (
|
|
"encoding/base64"
|
|
"os"
|
|
"path/filepath"
|
|
"sort"
|
|
"strings"
|
|
"time"
|
|
|
|
"golang.org/x/crypto/sha3"
|
|
. "gopkg.in/check.v1"
|
|
|
|
"github.com/snapcore/snapd/asserts"
|
|
"github.com/snapcore/snapd/asserts/assertstest"
|
|
)
|
|
|
|
var (
|
|
_ = Suite(&snapDeclSuite{})
|
|
_ = Suite(&snapFileDigestSuite{})
|
|
_ = Suite(&snapBuildSuite{})
|
|
_ = Suite(&snapRevSuite{})
|
|
_ = Suite(&validationSuite{})
|
|
_ = Suite(&baseDeclSuite{})
|
|
_ = Suite(&snapDevSuite{})
|
|
)
|
|
|
|
type snapDeclSuite struct {
|
|
ts time.Time
|
|
tsLine string
|
|
}
|
|
|
|
type emptyAttrerObject struct{}
|
|
|
|
func (o emptyAttrerObject) Lookup(path string) (interface{}, bool) {
|
|
return nil, false
|
|
}
|
|
|
|
func (sds *snapDeclSuite) SetUpSuite(c *C) {
|
|
sds.ts = time.Now().Truncate(time.Second).UTC()
|
|
sds.tsLine = "timestamp: " + sds.ts.Format(time.RFC3339) + "\n"
|
|
}
|
|
|
|
func (sds *snapDeclSuite) TestDecodeOK(c *C) {
|
|
encoded := "type: snap-declaration\n" +
|
|
"authority-id: canonical\n" +
|
|
"series: 16\n" +
|
|
"snap-id: snap-id-1\n" +
|
|
"snap-name: first\n" +
|
|
"publisher-id: dev-id1\n" +
|
|
"refresh-control:\n - foo\n - bar\n" +
|
|
"auto-aliases:\n - cmd1\n - cmd_2\n - Cmd-3\n - CMD.4\n" +
|
|
sds.tsLine +
|
|
`aliases:
|
|
-
|
|
name: cmd1
|
|
target: cmd-1
|
|
-
|
|
name: cmd_2
|
|
target: cmd-2
|
|
-
|
|
name: Cmd-3
|
|
target: cmd-3
|
|
-
|
|
name: CMD.4
|
|
target: cmd-4
|
|
` +
|
|
"body-length: 0\n" +
|
|
"sign-key-sha3-384: Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij" +
|
|
"\n\n" +
|
|
"AXNpZw=="
|
|
a, err := asserts.Decode([]byte(encoded))
|
|
c.Assert(err, IsNil)
|
|
c.Check(a.Type(), Equals, asserts.SnapDeclarationType)
|
|
snapDecl := a.(*asserts.SnapDeclaration)
|
|
c.Check(snapDecl.AuthorityID(), Equals, "canonical")
|
|
c.Check(snapDecl.Timestamp(), Equals, sds.ts)
|
|
c.Check(snapDecl.Series(), Equals, "16")
|
|
c.Check(snapDecl.SnapID(), Equals, "snap-id-1")
|
|
c.Check(snapDecl.SnapName(), Equals, "first")
|
|
c.Check(snapDecl.PublisherID(), Equals, "dev-id1")
|
|
c.Check(snapDecl.RefreshControl(), DeepEquals, []string{"foo", "bar"})
|
|
c.Check(snapDecl.AutoAliases(), DeepEquals, []string{"cmd1", "cmd_2", "Cmd-3", "CMD.4"})
|
|
c.Check(snapDecl.Aliases(), DeepEquals, map[string]string{
|
|
"cmd1": "cmd-1",
|
|
"cmd_2": "cmd-2",
|
|
"Cmd-3": "cmd-3",
|
|
"CMD.4": "cmd-4",
|
|
})
|
|
c.Check(snapDecl.RevisionAuthority(""), IsNil)
|
|
}
|
|
|
|
func (sds *snapDeclSuite) TestDecodeOKWithRevisionAuthority(c *C) {
|
|
encoded := "type: snap-declaration\n" +
|
|
"authority-id: canonical\n" +
|
|
"series: 16\n" +
|
|
"snap-id: snap-id-1\n" +
|
|
"snap-name: first\n" +
|
|
"publisher-id: dev-id1\n" +
|
|
"refresh-control:\n - foo\n - bar\n" +
|
|
sds.tsLine +
|
|
`revision-authority:
|
|
-
|
|
account-id: delegated-acc-id
|
|
provenance:
|
|
- prov1
|
|
- prov2
|
|
min-revision: 100
|
|
max-revision: 1000000
|
|
on-store:
|
|
- store1
|
|
` +
|
|
"body-length: 0\n" +
|
|
"sign-key-sha3-384: Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij" +
|
|
"\n\n" +
|
|
"AXNpZw=="
|
|
a, err := asserts.Decode([]byte(encoded))
|
|
c.Assert(err, IsNil)
|
|
c.Check(a.Type(), Equals, asserts.SnapDeclarationType)
|
|
snapDecl := a.(*asserts.SnapDeclaration)
|
|
c.Check(snapDecl.AuthorityID(), Equals, "canonical")
|
|
c.Check(snapDecl.Timestamp(), Equals, sds.ts)
|
|
c.Check(snapDecl.Series(), Equals, "16")
|
|
c.Check(snapDecl.SnapID(), Equals, "snap-id-1")
|
|
c.Check(snapDecl.SnapName(), Equals, "first")
|
|
c.Check(snapDecl.PublisherID(), Equals, "dev-id1")
|
|
c.Check(snapDecl.RefreshControl(), DeepEquals, []string{"foo", "bar"})
|
|
ras := snapDecl.RevisionAuthority("prov1")
|
|
c.Check(ras, DeepEquals, []*asserts.RevisionAuthority{
|
|
{
|
|
AccountID: "delegated-acc-id",
|
|
Provenance: []string{"prov1", "prov2"},
|
|
MinRevision: 100,
|
|
MaxRevision: 1000000,
|
|
DeviceScope: &asserts.DeviceScopeConstraint{
|
|
Store: []string{"store1"},
|
|
},
|
|
},
|
|
})
|
|
}
|
|
|
|
func (sds *snapDeclSuite) TestDecodeOKWithRevisionAuthorityDefaults(c *C) {
|
|
initial := "type: snap-declaration\n" +
|
|
"authority-id: canonical\n" +
|
|
"series: 16\n" +
|
|
"snap-id: snap-id-1\n" +
|
|
"snap-name: first\n" +
|
|
"publisher-id: dev-id1\n" +
|
|
"refresh-control:\n - foo\n - bar\n" +
|
|
sds.tsLine +
|
|
`revision-authority:
|
|
-
|
|
account-id: delegated-acc-id
|
|
provenance:
|
|
- prov1
|
|
- prov2
|
|
min-revision: 100
|
|
` +
|
|
"body-length: 0\n" +
|
|
"sign-key-sha3-384: Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij" +
|
|
"\n\n" +
|
|
"AXNpZw=="
|
|
tests := []struct {
|
|
original, replaced string
|
|
revAuth asserts.RevisionAuthority
|
|
}{
|
|
{"min", "min", asserts.RevisionAuthority{
|
|
AccountID: "delegated-acc-id",
|
|
Provenance: []string{"prov1", "prov2"},
|
|
MinRevision: 100,
|
|
}},
|
|
{"min", "max", asserts.RevisionAuthority{
|
|
AccountID: "delegated-acc-id",
|
|
Provenance: []string{"prov1", "prov2"},
|
|
MinRevision: 1,
|
|
MaxRevision: 100,
|
|
}},
|
|
{" min-revision: 100\n", "", asserts.RevisionAuthority{
|
|
AccountID: "delegated-acc-id",
|
|
Provenance: []string{"prov1", "prov2"},
|
|
MinRevision: 1,
|
|
}},
|
|
}
|
|
|
|
for _, t := range tests {
|
|
encoded := strings.Replace(initial, t.original, t.replaced, 1)
|
|
a, err := asserts.Decode([]byte(encoded))
|
|
c.Assert(err, IsNil)
|
|
snapDecl := a.(*asserts.SnapDeclaration)
|
|
ras := snapDecl.RevisionAuthority("prov2")
|
|
c.Check(ras, HasLen, 1)
|
|
c.Check(*ras[0], DeepEquals, t.revAuth)
|
|
}
|
|
}
|
|
|
|
func (sds *snapDeclSuite) TestEmptySnapName(c *C) {
|
|
encoded := "type: snap-declaration\n" +
|
|
"authority-id: canonical\n" +
|
|
"series: 16\n" +
|
|
"snap-id: snap-id-1\n" +
|
|
"snap-name: \n" +
|
|
"publisher-id: dev-id1\n" +
|
|
sds.tsLine +
|
|
"body-length: 0\n" +
|
|
"sign-key-sha3-384: Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij" +
|
|
"\n\n" +
|
|
"AXNpZw=="
|
|
a, err := asserts.Decode([]byte(encoded))
|
|
c.Assert(err, IsNil)
|
|
snapDecl := a.(*asserts.SnapDeclaration)
|
|
c.Check(snapDecl.SnapName(), Equals, "")
|
|
}
|
|
|
|
func (sds *snapDeclSuite) TestMissingRefreshControlAutoAliases(c *C) {
|
|
encoded := "type: snap-declaration\n" +
|
|
"authority-id: canonical\n" +
|
|
"series: 16\n" +
|
|
"snap-id: snap-id-1\n" +
|
|
"snap-name: \n" +
|
|
"publisher-id: dev-id1\n" +
|
|
sds.tsLine +
|
|
"body-length: 0\n" +
|
|
"sign-key-sha3-384: Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij" +
|
|
"\n\n" +
|
|
"AXNpZw=="
|
|
a, err := asserts.Decode([]byte(encoded))
|
|
c.Assert(err, IsNil)
|
|
snapDecl := a.(*asserts.SnapDeclaration)
|
|
c.Check(snapDecl.RefreshControl(), HasLen, 0)
|
|
c.Check(snapDecl.AutoAliases(), HasLen, 0)
|
|
}
|
|
|
|
const (
|
|
snapDeclErrPrefix = "assertion snap-declaration: "
|
|
)
|
|
|
|
func (sds *snapDeclSuite) TestDecodeInvalid(c *C) {
|
|
aliases := `aliases:
|
|
-
|
|
name: cmd_1
|
|
target: cmd-1
|
|
`
|
|
encoded := "type: snap-declaration\n" +
|
|
"authority-id: canonical\n" +
|
|
"series: 16\n" +
|
|
"snap-id: snap-id-1\n" +
|
|
"snap-name: first\n" +
|
|
"publisher-id: dev-id1\n" +
|
|
"refresh-control:\n - foo\n - bar\n" +
|
|
"auto-aliases:\n - cmd1\n - cmd2\n" +
|
|
aliases +
|
|
"plugs:\n interface1: true\n" +
|
|
"slots:\n interface2: true\n" +
|
|
sds.tsLine +
|
|
"body-length: 0\n" +
|
|
"sign-key-sha3-384: Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij" +
|
|
"\n\n" +
|
|
"AXNpZw=="
|
|
|
|
invalidTests := []struct{ original, invalid, expectedErr string }{
|
|
{"series: 16\n", "", `"series" header is mandatory`},
|
|
{"series: 16\n", "series: \n", `"series" header should not be empty`},
|
|
{"snap-id: snap-id-1\n", "", `"snap-id" header is mandatory`},
|
|
{"snap-id: snap-id-1\n", "snap-id: \n", `"snap-id" header should not be empty`},
|
|
{"snap-name: first\n", "", `"snap-name" header is mandatory`},
|
|
{"publisher-id: dev-id1\n", "", `"publisher-id" header is mandatory`},
|
|
{"publisher-id: dev-id1\n", "publisher-id: \n", `"publisher-id" header should not be empty`},
|
|
{"refresh-control:\n - foo\n - bar\n", "refresh-control: foo\n", `"refresh-control" header must be a list of strings`},
|
|
{"refresh-control:\n - foo\n - bar\n", "refresh-control:\n -\n - nested\n", `"refresh-control" header must be a list of strings`},
|
|
{"plugs:\n interface1: true\n", "plugs: \n", `"plugs" header must be a map`},
|
|
{"plugs:\n interface1: true\n", "plugs:\n intf1:\n foo: bar\n", `plug rule for interface "intf1" must specify at least one of.*`},
|
|
{"slots:\n interface2: true\n", "slots: \n", `"slots" header must be a map`},
|
|
{"slots:\n interface2: true\n", "slots:\n intf1:\n foo: bar\n", `slot rule for interface "intf1" must specify at least one of.*`},
|
|
{"auto-aliases:\n - cmd1\n - cmd2\n", "auto-aliases: cmd0\n", `"auto-aliases" header must be a list of strings`},
|
|
{"auto-aliases:\n - cmd1\n - cmd2\n", "auto-aliases:\n -\n - nested\n", `"auto-aliases" header must be a list of strings`},
|
|
{"auto-aliases:\n - cmd1\n - cmd2\n", "auto-aliases:\n - _cmd-1\n - cmd2\n", `"auto-aliases" header contains an invalid element: "_cmd-1"`},
|
|
{aliases, "aliases: cmd0\n", `"aliases" header must be a list of alias maps`},
|
|
{aliases, "aliases:\n - cmd1\n", `"aliases" header must be a list of alias maps`},
|
|
{"name: cmd_1\n", "name: .cmd1\n", `"name" in "aliases" item 1 contains invalid characters: ".cmd1"`},
|
|
{"target: cmd-1\n", "target: -cmd-1\n", `"target" for alias "cmd_1" contains invalid characters: "-cmd-1"`},
|
|
{aliases, aliases + " -\n name: cmd_1\n target: foo\n", `duplicated definition in "aliases" for alias "cmd_1"`},
|
|
{sds.tsLine, "", `"timestamp" header is mandatory`},
|
|
{sds.tsLine, "timestamp: \n", `"timestamp" header should not be empty`},
|
|
{sds.tsLine, "timestamp: 12:30\n", `"timestamp" header is not a RFC3339 date: .*`},
|
|
}
|
|
|
|
for _, test := range invalidTests {
|
|
invalid := strings.Replace(encoded, test.original, test.invalid, 1)
|
|
_, err := asserts.Decode([]byte(invalid))
|
|
c.Check(err, ErrorMatches, snapDeclErrPrefix+test.expectedErr)
|
|
}
|
|
|
|
}
|
|
|
|
func (sds *snapDeclSuite) TestDecodeInvalidWithRevisionAuthority(c *C) {
|
|
const revAuth = `revision-authority:
|
|
-
|
|
account-id: delegated-acc-id
|
|
provenance:
|
|
- prov1
|
|
- prov2
|
|
min-revision: 100
|
|
max-revision: 1000000
|
|
on-store:
|
|
- store1
|
|
`
|
|
encoded := "type: snap-declaration\n" +
|
|
"authority-id: canonical\n" +
|
|
"series: 16\n" +
|
|
"snap-id: snap-id-1\n" +
|
|
"snap-name: first\n" +
|
|
"publisher-id: dev-id1\n" +
|
|
"refresh-control:\n - foo\n - bar\n" +
|
|
sds.tsLine +
|
|
revAuth +
|
|
"body-length: 0\n" +
|
|
"sign-key-sha3-384: Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij" +
|
|
"\n\n" +
|
|
"AXNpZw=="
|
|
|
|
invalidTests := []struct{ original, invalid, expectedErr string }{
|
|
{revAuth, "revision-authority: x\n", `revision-authority stanza must be a list of maps`},
|
|
{revAuth, "revision-authority:\n - x\n", `revision-authority stanza must be a list of maps`},
|
|
{" account-id: delegated-acc-id\n", "", `"account-id" in revision authority is mandatory`},
|
|
{"account-id: delegated-acc-id\n", "account-id: *\n", `"account-id" in revision authority contains invalid characters: "\*"`},
|
|
{" provenance:\n - prov1\n - prov2\n", " provenance: \n", `provenance in revision authority must be a list of strings`},
|
|
{"prov2\n", "*\n", `provenance in revision authority contains an invalid element: "\*"`},
|
|
{" min-revision: 100\n", " min-revision: 0\n", `"min-revision" in revision authority must be >=1: 0`},
|
|
{" max-revision: 1000000\n", " max-revision: 0\n", `"max-revision" in revision authority must be >=1: 0`},
|
|
{" max-revision: 1000000\n", " max-revision: 10\n", `optional max-revision cannot be less than min-revision in revision-authority`},
|
|
{" on-store:\n - store1\n", " on-store: foo", `on-store in revision-authority must be a list of strings`},
|
|
}
|
|
|
|
for _, test := range invalidTests {
|
|
invalid := strings.Replace(encoded, test.original, test.invalid, 1)
|
|
_, err := asserts.Decode([]byte(invalid))
|
|
c.Check(err, ErrorMatches, snapDeclErrPrefix+test.expectedErr)
|
|
}
|
|
}
|
|
|
|
func (sds *snapDeclSuite) TestDecodePlugsAndSlots(c *C) {
|
|
encoded := `type: snap-declaration
|
|
format: 1
|
|
authority-id: canonical
|
|
series: 16
|
|
snap-id: snap-id-1
|
|
snap-name: first
|
|
publisher-id: dev-id1
|
|
plugs:
|
|
interface1:
|
|
deny-installation: false
|
|
allow-auto-connection:
|
|
slot-snap-type:
|
|
- app
|
|
slot-publisher-id:
|
|
- acme
|
|
slot-attributes:
|
|
a1: /foo/.*
|
|
plug-attributes:
|
|
b1: B1
|
|
deny-auto-connection:
|
|
slot-attributes:
|
|
a1: !A1
|
|
plug-attributes:
|
|
b1: !B1
|
|
interface2:
|
|
allow-installation: true
|
|
allow-connection:
|
|
plug-attributes:
|
|
a2: A2
|
|
slot-attributes:
|
|
b2: B2
|
|
deny-connection:
|
|
slot-snap-id:
|
|
- snapidsnapidsnapidsnapidsnapid01
|
|
- snapidsnapidsnapidsnapidsnapid02
|
|
plug-attributes:
|
|
a2: !A2
|
|
slot-attributes:
|
|
b2: !B2
|
|
slots:
|
|
interface3:
|
|
deny-installation: false
|
|
allow-auto-connection:
|
|
plug-snap-type:
|
|
- app
|
|
plug-publisher-id:
|
|
- acme
|
|
slot-attributes:
|
|
c1: /foo/.*
|
|
plug-attributes:
|
|
d1: C1
|
|
deny-auto-connection:
|
|
slot-attributes:
|
|
c1: !C1
|
|
plug-attributes:
|
|
d1: !D1
|
|
interface4:
|
|
allow-connection:
|
|
plug-attributes:
|
|
c2: C2
|
|
slot-attributes:
|
|
d2: D2
|
|
deny-connection:
|
|
plug-snap-id:
|
|
- snapidsnapidsnapidsnapidsnapid01
|
|
- snapidsnapidsnapidsnapidsnapid02
|
|
plug-attributes:
|
|
c2: !D2
|
|
slot-attributes:
|
|
d2: !D2
|
|
allow-installation:
|
|
slot-snap-type:
|
|
- app
|
|
slot-attributes:
|
|
e1: E1
|
|
TSLINE
|
|
body-length: 0
|
|
sign-key-sha3-384: Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij
|
|
|
|
AXNpZw==`
|
|
encoded = strings.Replace(encoded, "TSLINE\n", sds.tsLine, 1)
|
|
a, err := asserts.Decode([]byte(encoded))
|
|
c.Assert(err, IsNil)
|
|
c.Check(a.SupportedFormat(), Equals, true)
|
|
snapDecl := a.(*asserts.SnapDeclaration)
|
|
c.Check(snapDecl.Series(), Equals, "16")
|
|
c.Check(snapDecl.SnapID(), Equals, "snap-id-1")
|
|
|
|
c.Check(snapDecl.PlugRule("interfaceX"), IsNil)
|
|
c.Check(snapDecl.SlotRule("interfaceX"), IsNil)
|
|
|
|
plugRule1 := snapDecl.PlugRule("interface1")
|
|
c.Assert(plugRule1, NotNil)
|
|
c.Assert(plugRule1.DenyInstallation, HasLen, 1)
|
|
c.Check(plugRule1.DenyInstallation[0].PlugAttributes, Equals, asserts.NeverMatchAttributes)
|
|
c.Assert(plugRule1.AllowAutoConnection, HasLen, 1)
|
|
|
|
plug := emptyAttrerObject{}
|
|
slot := emptyAttrerObject{}
|
|
|
|
c.Check(plugRule1.AllowAutoConnection[0].SlotAttributes.Check(slot, nil), ErrorMatches, `attribute "a1".*`)
|
|
c.Check(plugRule1.AllowAutoConnection[0].PlugAttributes.Check(plug, nil), ErrorMatches, `attribute "b1".*`)
|
|
c.Check(plugRule1.AllowAutoConnection[0].SlotSnapTypes, DeepEquals, []string{"app"})
|
|
c.Check(plugRule1.AllowAutoConnection[0].SlotPublisherIDs, DeepEquals, []string{"acme"})
|
|
c.Assert(plugRule1.DenyAutoConnection, HasLen, 1)
|
|
c.Check(plugRule1.DenyAutoConnection[0].SlotAttributes.Check(slot, nil), ErrorMatches, `attribute "a1".*`)
|
|
c.Check(plugRule1.DenyAutoConnection[0].PlugAttributes.Check(plug, nil), ErrorMatches, `attribute "b1".*`)
|
|
plugRule2 := snapDecl.PlugRule("interface2")
|
|
c.Assert(plugRule2, NotNil)
|
|
c.Assert(plugRule2.AllowInstallation, HasLen, 1)
|
|
c.Check(plugRule2.AllowInstallation[0].PlugAttributes, Equals, asserts.AlwaysMatchAttributes)
|
|
c.Assert(plugRule2.AllowConnection, HasLen, 1)
|
|
c.Check(plugRule2.AllowConnection[0].PlugAttributes.Check(plug, nil), ErrorMatches, `attribute "a2".*`)
|
|
c.Check(plugRule2.AllowConnection[0].SlotAttributes.Check(slot, nil), ErrorMatches, `attribute "b2".*`)
|
|
c.Assert(plugRule2.DenyConnection, HasLen, 1)
|
|
c.Check(plugRule2.DenyConnection[0].PlugAttributes.Check(plug, nil), ErrorMatches, `attribute "a2".*`)
|
|
c.Check(plugRule2.DenyConnection[0].SlotAttributes.Check(slot, nil), ErrorMatches, `attribute "b2".*`)
|
|
c.Check(plugRule2.DenyConnection[0].SlotSnapIDs, DeepEquals, []string{"snapidsnapidsnapidsnapidsnapid01", "snapidsnapidsnapidsnapidsnapid02"})
|
|
|
|
slotRule3 := snapDecl.SlotRule("interface3")
|
|
c.Assert(slotRule3, NotNil)
|
|
c.Assert(slotRule3.DenyInstallation, HasLen, 1)
|
|
c.Check(slotRule3.DenyInstallation[0].SlotAttributes, Equals, asserts.NeverMatchAttributes)
|
|
c.Assert(slotRule3.AllowAutoConnection, HasLen, 1)
|
|
c.Check(slotRule3.AllowAutoConnection[0].SlotAttributes.Check(slot, nil), ErrorMatches, `attribute "c1".*`)
|
|
c.Check(slotRule3.AllowAutoConnection[0].PlugAttributes.Check(plug, nil), ErrorMatches, `attribute "d1".*`)
|
|
c.Check(slotRule3.AllowAutoConnection[0].PlugSnapTypes, DeepEquals, []string{"app"})
|
|
c.Check(slotRule3.AllowAutoConnection[0].PlugPublisherIDs, DeepEquals, []string{"acme"})
|
|
c.Assert(slotRule3.DenyAutoConnection, HasLen, 1)
|
|
c.Check(slotRule3.DenyAutoConnection[0].SlotAttributes.Check(slot, nil), ErrorMatches, `attribute "c1".*`)
|
|
c.Check(slotRule3.DenyAutoConnection[0].PlugAttributes.Check(plug, nil), ErrorMatches, `attribute "d1".*`)
|
|
slotRule4 := snapDecl.SlotRule("interface4")
|
|
c.Assert(slotRule4, NotNil)
|
|
c.Assert(slotRule4.AllowAutoConnection, HasLen, 1)
|
|
c.Check(slotRule4.AllowConnection[0].PlugAttributes.Check(plug, nil), ErrorMatches, `attribute "c2".*`)
|
|
c.Check(slotRule4.AllowConnection[0].SlotAttributes.Check(slot, nil), ErrorMatches, `attribute "d2".*`)
|
|
c.Assert(slotRule4.DenyAutoConnection, HasLen, 1)
|
|
c.Check(slotRule4.DenyConnection[0].PlugAttributes.Check(plug, nil), ErrorMatches, `attribute "c2".*`)
|
|
c.Check(slotRule4.DenyConnection[0].SlotAttributes.Check(slot, nil), ErrorMatches, `attribute "d2".*`)
|
|
c.Check(slotRule4.DenyConnection[0].PlugSnapIDs, DeepEquals, []string{"snapidsnapidsnapidsnapidsnapid01", "snapidsnapidsnapidsnapidsnapid02"})
|
|
c.Assert(slotRule4.AllowInstallation, HasLen, 1)
|
|
c.Check(slotRule4.AllowInstallation[0].SlotAttributes.Check(slot, nil), ErrorMatches, `attribute "e1".*`)
|
|
c.Check(slotRule4.AllowInstallation[0].SlotSnapTypes, DeepEquals, []string{"app"})
|
|
}
|
|
|
|
func (sds *snapDeclSuite) TestSuggestedFormat(c *C) {
|
|
fmtnum, err := asserts.SuggestFormat(asserts.SnapDeclarationType, nil, nil)
|
|
c.Assert(err, IsNil)
|
|
c.Check(fmtnum, Equals, 0)
|
|
|
|
headers := map[string]interface{}{
|
|
"plugs": map[string]interface{}{
|
|
"interface1": "true",
|
|
},
|
|
}
|
|
fmtnum, err = asserts.SuggestFormat(asserts.SnapDeclarationType, headers, nil)
|
|
c.Assert(err, IsNil)
|
|
c.Check(fmtnum, Equals, 1)
|
|
|
|
headers = map[string]interface{}{
|
|
"slots": map[string]interface{}{
|
|
"interface2": "true",
|
|
},
|
|
}
|
|
fmtnum, err = asserts.SuggestFormat(asserts.SnapDeclarationType, headers, nil)
|
|
c.Assert(err, IsNil)
|
|
c.Check(fmtnum, Equals, 1)
|
|
|
|
headers = map[string]interface{}{
|
|
"plugs": map[string]interface{}{
|
|
"interface3": map[string]interface{}{
|
|
"allow-auto-connection": map[string]interface{}{
|
|
"plug-attributes": map[string]interface{}{
|
|
"x": "$SLOT(x)",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
fmtnum, err = asserts.SuggestFormat(asserts.SnapDeclarationType, headers, nil)
|
|
c.Assert(err, IsNil)
|
|
c.Check(fmtnum, Equals, 2)
|
|
|
|
headers = map[string]interface{}{
|
|
"slots": map[string]interface{}{
|
|
"interface3": map[string]interface{}{
|
|
"allow-auto-connection": map[string]interface{}{
|
|
"plug-attributes": map[string]interface{}{
|
|
"x": "$SLOT(x)",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
fmtnum, err = asserts.SuggestFormat(asserts.SnapDeclarationType, headers, nil)
|
|
c.Assert(err, IsNil)
|
|
c.Check(fmtnum, Equals, 2)
|
|
|
|
// combinations with on-store/on-brand/on-model => format 3
|
|
for _, side := range []string{"plugs", "slots"} {
|
|
for k, vals := range deviceScopeConstrs {
|
|
|
|
headers := map[string]interface{}{
|
|
side: map[string]interface{}{
|
|
"interface3": map[string]interface{}{
|
|
"allow-installation": map[string]interface{}{
|
|
k: vals,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
fmtnum, err = asserts.SuggestFormat(asserts.SnapDeclarationType, headers, nil)
|
|
c.Assert(err, IsNil)
|
|
c.Check(fmtnum, Equals, 3)
|
|
|
|
for _, conn := range []string{"connection", "auto-connection"} {
|
|
|
|
headers = map[string]interface{}{
|
|
side: map[string]interface{}{
|
|
"interface3": map[string]interface{}{
|
|
"allow-" + conn: map[string]interface{}{
|
|
k: vals,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
fmtnum, err = asserts.SuggestFormat(asserts.SnapDeclarationType, headers, nil)
|
|
c.Assert(err, IsNil)
|
|
c.Check(fmtnum, Equals, 3)
|
|
}
|
|
}
|
|
}
|
|
|
|
// higher format features win
|
|
|
|
headers = map[string]interface{}{
|
|
"plugs": map[string]interface{}{
|
|
"interface3": map[string]interface{}{
|
|
"allow-auto-connection": map[string]interface{}{
|
|
"on-store": []interface{}{"store"},
|
|
},
|
|
},
|
|
},
|
|
"slots": map[string]interface{}{
|
|
"interface4": map[string]interface{}{
|
|
"allow-auto-connection": map[string]interface{}{
|
|
"plug-attributes": map[string]interface{}{
|
|
"x": "$SLOT(x)",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
fmtnum, err = asserts.SuggestFormat(asserts.SnapDeclarationType, headers, nil)
|
|
c.Assert(err, IsNil)
|
|
c.Check(fmtnum, Equals, 3)
|
|
|
|
headers = map[string]interface{}{
|
|
"plugs": map[string]interface{}{
|
|
"interface4": map[string]interface{}{
|
|
"allow-auto-connection": map[string]interface{}{
|
|
"slot-attributes": map[string]interface{}{
|
|
"x": "$SLOT(x)",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
"slots": map[string]interface{}{
|
|
"interface3": map[string]interface{}{
|
|
"allow-auto-connection": map[string]interface{}{
|
|
"on-store": []interface{}{"store"},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
fmtnum, err = asserts.SuggestFormat(asserts.SnapDeclarationType, headers, nil)
|
|
c.Assert(err, IsNil)
|
|
c.Check(fmtnum, Equals, 3)
|
|
|
|
// errors
|
|
headers = map[string]interface{}{
|
|
"plugs": "what",
|
|
}
|
|
_, err = asserts.SuggestFormat(asserts.SnapDeclarationType, headers, nil)
|
|
c.Assert(err, ErrorMatches, `assertion snap-declaration: "plugs" header must be a map`)
|
|
|
|
headers = map[string]interface{}{
|
|
"slots": "what",
|
|
}
|
|
_, err = asserts.SuggestFormat(asserts.SnapDeclarationType, headers, nil)
|
|
c.Assert(err, ErrorMatches, `assertion snap-declaration: "slots" header must be a map`)
|
|
|
|
// plug-names/slot-names => format 4
|
|
for _, sidePrefix := range []string{"plug", "slot"} {
|
|
side := sidePrefix + "s"
|
|
headers := map[string]interface{}{
|
|
side: map[string]interface{}{
|
|
"interface3": map[string]interface{}{
|
|
"allow-installation": map[string]interface{}{
|
|
sidePrefix + "-names": []interface{}{"foo"},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
fmtnum, err = asserts.SuggestFormat(asserts.SnapDeclarationType, headers, nil)
|
|
c.Assert(err, IsNil)
|
|
c.Check(fmtnum, Equals, 4)
|
|
|
|
for _, conn := range []string{"connection", "auto-connection"} {
|
|
|
|
headers = map[string]interface{}{
|
|
side: map[string]interface{}{
|
|
"interface3": map[string]interface{}{
|
|
"allow-" + conn: map[string]interface{}{
|
|
sidePrefix + "-names": []interface{}{"foo"},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
fmtnum, err = asserts.SuggestFormat(asserts.SnapDeclarationType, headers, nil)
|
|
c.Assert(err, IsNil)
|
|
c.Check(fmtnum, Equals, 4)
|
|
|
|
headers = map[string]interface{}{
|
|
side: map[string]interface{}{
|
|
"interface3": map[string]interface{}{
|
|
"allow-" + conn: map[string]interface{}{
|
|
"plug-names": []interface{}{"Pfoo"},
|
|
"slot-names": []interface{}{"Sfoo"},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
fmtnum, err = asserts.SuggestFormat(asserts.SnapDeclarationType, headers, nil)
|
|
c.Assert(err, IsNil)
|
|
c.Check(fmtnum, Equals, 4)
|
|
}
|
|
}
|
|
|
|
// alt matcher (so far unused) => format 5
|
|
for _, sidePrefix := range []string{"plug", "slot"} {
|
|
headers = map[string]interface{}{
|
|
sidePrefix + "s": map[string]interface{}{
|
|
"interface5": map[string]interface{}{
|
|
"allow-auto-connection": map[string]interface{}{
|
|
sidePrefix + "-attributes": map[string]interface{}{
|
|
"x": []interface{}{"alt1", "alt2"}, // alt matcher
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
fmtnum, err = asserts.SuggestFormat(asserts.SnapDeclarationType, headers, nil)
|
|
c.Assert(err, IsNil)
|
|
c.Check(fmtnum, Equals, 5)
|
|
}
|
|
|
|
for _, cstr := range []string{"$PLUG_PUBLISHER_ID", "$SLOT_PUBLISHER_ID"} {
|
|
for _, sidePrefix := range []string{"plug", "slot"} {
|
|
headers = map[string]interface{}{
|
|
sidePrefix + "s": map[string]interface{}{
|
|
"interface6": map[string]interface{}{
|
|
"allow-auto-connection": map[string]interface{}{
|
|
sidePrefix + "-attributes": map[string]interface{}{
|
|
"x": cstr,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
fmtnum, err = asserts.SuggestFormat(asserts.SnapDeclarationType, headers, nil)
|
|
c.Assert(err, IsNil)
|
|
c.Check(fmtnum, Equals, 6)
|
|
}
|
|
}
|
|
}
|
|
|
|
func prereqDevAccount(c *C, storeDB assertstest.SignerDB, db *asserts.Database) {
|
|
dev1Acct := assertstest.NewAccount(storeDB, "developer1", map[string]interface{}{
|
|
"account-id": "dev-id1",
|
|
}, "")
|
|
err := db.Add(dev1Acct)
|
|
c.Assert(err, IsNil)
|
|
}
|
|
|
|
func (sds *snapDeclSuite) TestSnapDeclarationCheck(c *C) {
|
|
storeDB, db := makeStoreAndCheckDB(c)
|
|
|
|
prereqDevAccount(c, storeDB, db)
|
|
|
|
headers := map[string]interface{}{
|
|
"series": "16",
|
|
"snap-id": "snap-id-1",
|
|
"snap-name": "foo",
|
|
"publisher-id": "dev-id1",
|
|
"timestamp": time.Now().Format(time.RFC3339),
|
|
}
|
|
snapDecl, err := storeDB.Sign(asserts.SnapDeclarationType, headers, nil, "")
|
|
c.Assert(err, IsNil)
|
|
|
|
err = db.Check(snapDecl)
|
|
c.Assert(err, IsNil)
|
|
}
|
|
|
|
func (sds *snapDeclSuite) TestSnapDeclarationCheckUntrustedAuthority(c *C) {
|
|
storeDB, db := makeStoreAndCheckDB(c)
|
|
|
|
otherDB := setup3rdPartySigning(c, "other", storeDB, db)
|
|
|
|
headers := map[string]interface{}{
|
|
"series": "16",
|
|
"snap-id": "snap-id-1",
|
|
"snap-name": "foo",
|
|
"publisher-id": "dev-id1",
|
|
"timestamp": time.Now().Format(time.RFC3339),
|
|
}
|
|
snapDecl, err := otherDB.Sign(asserts.SnapDeclarationType, headers, nil, "")
|
|
c.Assert(err, IsNil)
|
|
|
|
err = db.Check(snapDecl)
|
|
c.Assert(err, ErrorMatches, `snap-declaration assertion for "foo" \(id "snap-id-1"\) is not signed by a directly trusted authority:.*`)
|
|
}
|
|
|
|
func (sds *snapDeclSuite) TestSnapDeclarationCheckMissingPublisherAccount(c *C) {
|
|
storeDB, db := makeStoreAndCheckDB(c)
|
|
|
|
headers := map[string]interface{}{
|
|
"series": "16",
|
|
"snap-id": "snap-id-1",
|
|
"snap-name": "foo",
|
|
"publisher-id": "dev-id1",
|
|
"timestamp": time.Now().Format(time.RFC3339),
|
|
}
|
|
snapDecl, err := storeDB.Sign(asserts.SnapDeclarationType, headers, nil, "")
|
|
c.Assert(err, IsNil)
|
|
|
|
err = db.Check(snapDecl)
|
|
c.Assert(err, ErrorMatches, `snap-declaration assertion for "foo" \(id "snap-id-1"\) does not have a matching account assertion for the publisher "dev-id1"`)
|
|
}
|
|
|
|
type snapFileDigestSuite struct{}
|
|
|
|
func (s *snapFileDigestSuite) TestSnapFileSHA3_384(c *C) {
|
|
exData := []byte("hashmeplease")
|
|
|
|
tempdir := c.MkDir()
|
|
snapFn := filepath.Join(tempdir, "ex.snap")
|
|
err := os.WriteFile(snapFn, exData, 0644)
|
|
c.Assert(err, IsNil)
|
|
|
|
encDgst, size, err := asserts.SnapFileSHA3_384(snapFn)
|
|
c.Assert(err, IsNil)
|
|
c.Check(size, Equals, uint64(len(exData)))
|
|
|
|
h3_384 := sha3.Sum384(exData)
|
|
expected := base64.RawURLEncoding.EncodeToString(h3_384[:])
|
|
c.Check(encDgst, DeepEquals, expected)
|
|
}
|
|
|
|
type snapBuildSuite struct {
|
|
ts time.Time
|
|
tsLine string
|
|
}
|
|
|
|
func (sds *snapDeclSuite) TestPrerequisites(c *C) {
|
|
encoded := "type: snap-declaration\n" +
|
|
"authority-id: canonical\n" +
|
|
"series: 16\n" +
|
|
"snap-id: snap-id-1\n" +
|
|
"snap-name: first\n" +
|
|
"publisher-id: dev-id1\n" +
|
|
sds.tsLine +
|
|
"body-length: 0\n" +
|
|
"sign-key-sha3-384: Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij" +
|
|
"\n\n" +
|
|
"AXNpZw=="
|
|
a, err := asserts.Decode([]byte(encoded))
|
|
c.Assert(err, IsNil)
|
|
|
|
prereqs := a.Prerequisites()
|
|
c.Assert(prereqs, HasLen, 1)
|
|
c.Check(prereqs[0], DeepEquals, &asserts.Ref{
|
|
Type: asserts.AccountType,
|
|
PrimaryKey: []string{"dev-id1"},
|
|
})
|
|
}
|
|
|
|
func (sbs *snapBuildSuite) SetUpSuite(c *C) {
|
|
sbs.ts = time.Now().Truncate(time.Second).UTC()
|
|
sbs.tsLine = "timestamp: " + sbs.ts.Format(time.RFC3339) + "\n"
|
|
}
|
|
|
|
const (
|
|
blobSHA3_384 = "QlqR0uAWEAWF5Nwnzj5kqmmwFslYPu1IL16MKtLKhwhv0kpBv5wKZ_axf_nf_2cL"
|
|
)
|
|
|
|
func (sbs *snapBuildSuite) TestDecodeOK(c *C) {
|
|
encoded := "type: snap-build\n" +
|
|
"authority-id: dev-id1\n" +
|
|
"snap-sha3-384: " + blobSHA3_384 + "\n" +
|
|
"grade: stable\n" +
|
|
"snap-id: snap-id-1\n" +
|
|
"snap-size: 10000\n" +
|
|
sbs.tsLine +
|
|
"body-length: 0\n" +
|
|
"sign-key-sha3-384: Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij" +
|
|
"\n\n" +
|
|
"AXNpZw=="
|
|
a, err := asserts.Decode([]byte(encoded))
|
|
c.Assert(err, IsNil)
|
|
c.Check(a.Type(), Equals, asserts.SnapBuildType)
|
|
snapBuild := a.(*asserts.SnapBuild)
|
|
c.Check(snapBuild.AuthorityID(), Equals, "dev-id1")
|
|
c.Check(snapBuild.Timestamp(), Equals, sbs.ts)
|
|
c.Check(snapBuild.SnapID(), Equals, "snap-id-1")
|
|
c.Check(snapBuild.SnapSHA3_384(), Equals, blobSHA3_384)
|
|
c.Check(snapBuild.SnapSize(), Equals, uint64(10000))
|
|
c.Check(snapBuild.Grade(), Equals, "stable")
|
|
}
|
|
|
|
const (
|
|
snapBuildErrPrefix = "assertion snap-build: "
|
|
)
|
|
|
|
func (sbs *snapBuildSuite) TestDecodeInvalid(c *C) {
|
|
digestHdr := "snap-sha3-384: " + blobSHA3_384 + "\n"
|
|
|
|
encoded := "type: snap-build\n" +
|
|
"authority-id: dev-id1\n" +
|
|
digestHdr +
|
|
"grade: stable\n" +
|
|
"snap-id: snap-id-1\n" +
|
|
"snap-size: 10000\n" +
|
|
sbs.tsLine +
|
|
"body-length: 0\n" +
|
|
"sign-key-sha3-384: Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij" +
|
|
"\n\n" +
|
|
"AXNpZw=="
|
|
|
|
invalidTests := []struct{ original, invalid, expectedErr string }{
|
|
{"snap-id: snap-id-1\n", "", `"snap-id" header is mandatory`},
|
|
{"snap-id: snap-id-1\n", "snap-id: \n", `"snap-id" header should not be empty`},
|
|
{digestHdr, "", `"snap-sha3-384" header is mandatory`},
|
|
{digestHdr, "snap-sha3-384: \n", `"snap-sha3-384" header should not be empty`},
|
|
{digestHdr, "snap-sha3-384: #\n", `"snap-sha3-384" header cannot be decoded:.*`},
|
|
{"snap-size: 10000\n", "", `"snap-size" header is mandatory`},
|
|
{"snap-size: 10000\n", "snap-size: -1\n", `"snap-size" header is not an unsigned integer: -1`},
|
|
{"snap-size: 10000\n", "snap-size: zzz\n", `"snap-size" header is not an unsigned integer: zzz`},
|
|
{"snap-size: 10000\n", "snap-size: 010\n", `"snap-size" header has invalid prefix zeros: 010`},
|
|
{"snap-size: 10000\n", "snap-size: 99999999999999999999\n", `"snap-size" header is out of range: 99999999999999999999`},
|
|
{"grade: stable\n", "", `"grade" header is mandatory`},
|
|
{"grade: stable\n", "grade: \n", `"grade" header should not be empty`},
|
|
{sbs.tsLine, "", `"timestamp" header is mandatory`},
|
|
{sbs.tsLine, "timestamp: \n", `"timestamp" header should not be empty`},
|
|
{sbs.tsLine, "timestamp: 12:30\n", `"timestamp" header is not a RFC3339 date: .*`},
|
|
}
|
|
|
|
for _, test := range invalidTests {
|
|
invalid := strings.Replace(encoded, test.original, test.invalid, 1)
|
|
_, err := asserts.Decode([]byte(invalid))
|
|
c.Check(err, ErrorMatches, snapBuildErrPrefix+test.expectedErr)
|
|
}
|
|
}
|
|
|
|
func makeStoreAndCheckDB(c *C) (store *assertstest.StoreStack, checkDB *asserts.Database) {
|
|
store = assertstest.NewStoreStack("canonical", nil)
|
|
cfg := &asserts.DatabaseConfig{
|
|
Backstore: asserts.NewMemoryBackstore(),
|
|
Trusted: store.Trusted,
|
|
OtherPredefined: store.Generic,
|
|
}
|
|
checkDB, err := asserts.OpenDatabase(cfg)
|
|
c.Assert(err, IsNil)
|
|
|
|
// add store key
|
|
err = checkDB.Add(store.StoreAccountKey(""))
|
|
c.Assert(err, IsNil)
|
|
// add generic key
|
|
err = checkDB.Add(store.GenericKey)
|
|
c.Assert(err, IsNil)
|
|
|
|
return store, checkDB
|
|
}
|
|
|
|
func setup3rdPartySigning(c *C, username string, storeDB assertstest.SignerDB, checkDB *asserts.Database) (signingDB *assertstest.SigningDB) {
|
|
privKey := testPrivKey2
|
|
|
|
acct := assertstest.NewAccount(storeDB, username, map[string]interface{}{
|
|
"account-id": username,
|
|
}, "")
|
|
accKey := assertstest.NewAccountKey(storeDB, acct, nil, privKey.PublicKey(), "")
|
|
|
|
err := checkDB.Add(acct)
|
|
c.Assert(err, IsNil)
|
|
err = checkDB.Add(accKey)
|
|
c.Assert(err, IsNil)
|
|
|
|
return assertstest.NewSigningDB(acct.AccountID(), privKey)
|
|
}
|
|
|
|
func (sbs *snapBuildSuite) TestSnapBuildCheck(c *C) {
|
|
storeDB, db := makeStoreAndCheckDB(c)
|
|
devDB := setup3rdPartySigning(c, "devel1", storeDB, db)
|
|
|
|
headers := map[string]interface{}{
|
|
"authority-id": "devel1",
|
|
"snap-sha3-384": blobSHA3_384,
|
|
"snap-id": "snap-id-1",
|
|
"grade": "devel",
|
|
"snap-size": "1025",
|
|
"timestamp": time.Now().Format(time.RFC3339),
|
|
}
|
|
snapBuild, err := devDB.Sign(asserts.SnapBuildType, headers, nil, "")
|
|
c.Assert(err, IsNil)
|
|
|
|
err = db.Check(snapBuild)
|
|
c.Assert(err, IsNil)
|
|
}
|
|
|
|
func (sbs *snapBuildSuite) TestSnapBuildCheckInconsistentTimestamp(c *C) {
|
|
storeDB, db := makeStoreAndCheckDB(c)
|
|
devDB := setup3rdPartySigning(c, "devel1", storeDB, db)
|
|
|
|
headers := map[string]interface{}{
|
|
"snap-sha3-384": blobSHA3_384,
|
|
"snap-id": "snap-id-1",
|
|
"grade": "devel",
|
|
"snap-size": "1025",
|
|
"timestamp": "2013-01-01T14:00:00Z",
|
|
}
|
|
snapBuild, err := devDB.Sign(asserts.SnapBuildType, headers, nil, "")
|
|
c.Assert(err, IsNil)
|
|
|
|
err = db.Check(snapBuild)
|
|
c.Assert(err, ErrorMatches, `snap-build assertion timestamp "2013-01-01 14:00:00 \+0000 UTC" outside of signing key validity \(key valid since.*\)`)
|
|
}
|
|
|
|
type snapRevSuite struct {
|
|
ts time.Time
|
|
tsLine string
|
|
}
|
|
|
|
func (srs *snapRevSuite) SetUpSuite(c *C) {
|
|
srs.ts = time.Now().Truncate(time.Second).UTC()
|
|
srs.tsLine = "timestamp: " + srs.ts.Format(time.RFC3339) + "\n"
|
|
}
|
|
|
|
func (srs *snapRevSuite) makeValidEncoded() string {
|
|
return "type: snap-revision\n" +
|
|
"authority-id: store-id1\n" +
|
|
"snap-sha3-384: " + blobSHA3_384 + "\n" +
|
|
"snap-id: snap-id-1\n" +
|
|
"snap-size: 123\n" +
|
|
"snap-revision: 1\n" +
|
|
"developer-id: dev-id1\n" +
|
|
"revision: 1\n" +
|
|
srs.tsLine +
|
|
"body-length: 0\n" +
|
|
"sign-key-sha3-384: Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij" +
|
|
"\n\n" +
|
|
"AXNpZw=="
|
|
}
|
|
|
|
func (srs *snapRevSuite) makeValidEncodedWithIntegrity() string {
|
|
return "type: snap-revision\n" +
|
|
"authority-id: store-id1\n" +
|
|
"snap-sha3-384: " + blobSHA3_384 + "\n" +
|
|
"snap-id: snap-id-1\n" +
|
|
"snap-size: 123\n" +
|
|
"snap-revision: 1\n" +
|
|
"integrity:\n" +
|
|
" sha3-384: " + blobSHA3_384 + "\n" +
|
|
" size: 128\n" +
|
|
"developer-id: dev-id1\n" +
|
|
"revision: 1\n" +
|
|
srs.tsLine +
|
|
"body-length: 0\n" +
|
|
"sign-key-sha3-384: Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij" +
|
|
"\n\n" +
|
|
"AXNpZw=="
|
|
}
|
|
|
|
func makeSnapRevisionHeaders(overrides map[string]interface{}) map[string]interface{} {
|
|
headers := map[string]interface{}{
|
|
"authority-id": "canonical",
|
|
"snap-sha3-384": blobSHA3_384,
|
|
"snap-id": "snap-id-1",
|
|
"snap-size": "123",
|
|
"snap-revision": "1",
|
|
"developer-id": "dev-id1",
|
|
"revision": "1",
|
|
"timestamp": time.Now().Format(time.RFC3339),
|
|
}
|
|
for k, v := range overrides {
|
|
headers[k] = v
|
|
}
|
|
return headers
|
|
}
|
|
|
|
func (srs *snapRevSuite) makeHeaders(overrides map[string]interface{}) map[string]interface{} {
|
|
return makeSnapRevisionHeaders(overrides)
|
|
}
|
|
|
|
func (srs *snapRevSuite) TestDecodeOK(c *C) {
|
|
encoded := srs.makeValidEncoded()
|
|
a, err := asserts.Decode([]byte(encoded))
|
|
c.Assert(err, IsNil)
|
|
c.Check(a.Type(), Equals, asserts.SnapRevisionType)
|
|
snapRev := a.(*asserts.SnapRevision)
|
|
c.Check(snapRev.AuthorityID(), Equals, "store-id1")
|
|
c.Check(snapRev.Timestamp(), Equals, srs.ts)
|
|
c.Check(snapRev.SnapID(), Equals, "snap-id-1")
|
|
c.Check(snapRev.SnapSHA3_384(), Equals, blobSHA3_384)
|
|
c.Check(snapRev.SnapSize(), Equals, uint64(123))
|
|
c.Check(snapRev.SnapRevision(), Equals, 1)
|
|
c.Check(snapRev.DeveloperID(), Equals, "dev-id1")
|
|
c.Check(snapRev.Revision(), Equals, 1)
|
|
c.Check(snapRev.Provenance(), Equals, "global-upload")
|
|
}
|
|
|
|
func (srs *snapRevSuite) TestDecodeOKWithProvenance(c *C) {
|
|
encoded := srs.makeValidEncoded()
|
|
encoded = strings.Replace(encoded, "snap-id: snap-id-1", "provenance: foo\nsnap-id: snap-id-1", 1)
|
|
a, err := asserts.Decode([]byte(encoded))
|
|
c.Assert(err, IsNil)
|
|
c.Check(a.Type(), Equals, asserts.SnapRevisionType)
|
|
snapRev := a.(*asserts.SnapRevision)
|
|
c.Check(snapRev.AuthorityID(), Equals, "store-id1")
|
|
c.Check(snapRev.Timestamp(), Equals, srs.ts)
|
|
c.Check(snapRev.SnapID(), Equals, "snap-id-1")
|
|
c.Check(snapRev.SnapSHA3_384(), Equals, blobSHA3_384)
|
|
c.Check(snapRev.SnapSize(), Equals, uint64(123))
|
|
c.Check(snapRev.SnapRevision(), Equals, 1)
|
|
c.Check(snapRev.DeveloperID(), Equals, "dev-id1")
|
|
c.Check(snapRev.Revision(), Equals, 1)
|
|
c.Check(snapRev.Provenance(), Equals, "foo")
|
|
}
|
|
|
|
func (srs *snapRevSuite) TestDecodeOKWithIntegrity(c *C) {
|
|
encoded := srs.makeValidEncodedWithIntegrity()
|
|
a, err := asserts.Decode([]byte(encoded))
|
|
c.Assert(err, IsNil)
|
|
c.Check(a.Type(), Equals, asserts.SnapRevisionType)
|
|
snapRev := a.(*asserts.SnapRevision)
|
|
c.Check(snapRev.AuthorityID(), Equals, "store-id1")
|
|
c.Check(snapRev.Timestamp(), Equals, srs.ts)
|
|
c.Check(snapRev.SnapID(), Equals, "snap-id-1")
|
|
c.Check(snapRev.SnapSHA3_384(), Equals, blobSHA3_384)
|
|
c.Check(snapRev.SnapSize(), Equals, uint64(123))
|
|
c.Check(snapRev.SnapRevision(), Equals, 1)
|
|
c.Check(snapRev.DeveloperID(), Equals, "dev-id1")
|
|
c.Check(snapRev.Revision(), Equals, 1)
|
|
c.Check(snapRev.Provenance(), Equals, "global-upload")
|
|
c.Check(snapRev.SnapIntegrity().SHA3_384, Equals, blobSHA3_384)
|
|
c.Check(snapRev.SnapIntegrity().Size, Equals, uint64(128))
|
|
}
|
|
|
|
const (
|
|
snapRevErrPrefix = "assertion snap-revision: "
|
|
)
|
|
|
|
func (srs *snapRevSuite) TestDecodeInvalid(c *C) {
|
|
encoded := srs.makeValidEncoded()
|
|
|
|
digestHdr := "snap-sha3-384: " + blobSHA3_384 + "\n"
|
|
invalidTests := []struct{ original, invalid, expectedErr string }{
|
|
{"snap-id: snap-id-1\n", "", `"snap-id" header is mandatory`},
|
|
{"snap-id: snap-id-1\n", "snap-id: \n", `"snap-id" header should not be empty`},
|
|
{digestHdr, "", `"snap-sha3-384" header is mandatory`},
|
|
{digestHdr, "snap-sha3-384: \n", `"snap-sha3-384" header should not be empty`},
|
|
{digestHdr, "snap-sha3-384: #\n", `"snap-sha3-384" header cannot be decoded:.*`},
|
|
{digestHdr, "snap-sha3-384: eHl6\n", `"snap-sha3-384" header does not have the expected bit length: 24`},
|
|
{"snap-id: snap-id-1\n", "provenance: \nsnap-id: snap-id-1\n", `"provenance" header should not be empty`},
|
|
{"snap-id: snap-id-1\n", "provenance: *\nsnap-id: snap-id-1\n", `"provenance" header contains invalid characters: "\*"`},
|
|
{"snap-size: 123\n", "", `"snap-size" header is mandatory`},
|
|
{"snap-size: 123\n", "snap-size: \n", `"snap-size" header should not be empty`},
|
|
{"snap-size: 123\n", "snap-size: -1\n", `"snap-size" header is not an unsigned integer: -1`},
|
|
{"snap-size: 123\n", "snap-size: zzz\n", `"snap-size" header is not an unsigned integer: zzz`},
|
|
{"snap-revision: 1\n", "", `"snap-revision" header is mandatory`},
|
|
{"snap-revision: 1\n", "snap-revision: \n", `"snap-revision" header should not be empty`},
|
|
{"snap-revision: 1\n", "snap-revision: -1\n", `"snap-revision" header must be >=1: -1`},
|
|
{"snap-revision: 1\n", "snap-revision: 0\n", `"snap-revision" header must be >=1: 0`},
|
|
{"snap-revision: 1\n", "snap-revision: zzz\n", `"snap-revision" header is not an integer: zzz`},
|
|
{"developer-id: dev-id1\n", "", `"developer-id" header is mandatory`},
|
|
{"developer-id: dev-id1\n", "developer-id: \n", `"developer-id" header should not be empty`},
|
|
{srs.tsLine, "", `"timestamp" header is mandatory`},
|
|
{srs.tsLine, "timestamp: \n", `"timestamp" header should not be empty`},
|
|
{srs.tsLine, "timestamp: 12:30\n", `"timestamp" header is not a RFC3339 date: .*`},
|
|
}
|
|
|
|
for _, test := range invalidTests {
|
|
invalid := strings.Replace(encoded, test.original, test.invalid, 1)
|
|
_, err := asserts.Decode([]byte(invalid))
|
|
c.Check(err, ErrorMatches, snapRevErrPrefix+test.expectedErr)
|
|
}
|
|
}
|
|
|
|
func (srs *snapRevSuite) TestDecodeInvalidWithIntegrity(c *C) {
|
|
encoded := srs.makeValidEncodedWithIntegrity()
|
|
|
|
integrityHdr := "integrity:\n" +
|
|
" sha3-384: " + blobSHA3_384 + "\n" +
|
|
" size: 128\n"
|
|
|
|
integrityShaHdr := " sha3-384: " + blobSHA3_384 + "\n"
|
|
|
|
integritySizeHdr := " size: 128\n"
|
|
|
|
invalidTests := []struct{ original, invalid, expectedErr string }{
|
|
{integrityHdr, "integrity: \n", `"integrity" header must be a map`},
|
|
{integrityShaHdr, " sha3-384: \n", `"sha3-384" of integrity header should not be empty`},
|
|
{integrityShaHdr, " sha3-384: #\n", `"sha3-384" of integrity header cannot be decoded:.*`},
|
|
{integrityShaHdr, " sha3-384: eHl6\n", `"sha3-384" of integrity header does not have the expected bit length: 24`},
|
|
{integritySizeHdr, "", `"size" of integrity header is mandatory`},
|
|
{integritySizeHdr, " size: \n", `"size" of integrity header should not be empty`},
|
|
{integritySizeHdr, " size: -1\n", `"size" of integrity header is not an unsigned integer: -1`},
|
|
{integritySizeHdr, " size: zzz\n", `"size" of integrity header is not an unsigned integer: zzz`},
|
|
}
|
|
|
|
for _, test := range invalidTests {
|
|
invalid := strings.Replace(encoded, test.original, test.invalid, 1)
|
|
_, err := asserts.Decode([]byte(invalid))
|
|
c.Check(err, ErrorMatches, snapRevErrPrefix+test.expectedErr)
|
|
}
|
|
}
|
|
|
|
func prereqSnapDecl(c *C, storeDB assertstest.SignerDB, db *asserts.Database) {
|
|
snapDecl, err := storeDB.Sign(asserts.SnapDeclarationType, map[string]interface{}{
|
|
"series": "16",
|
|
"snap-id": "snap-id-1",
|
|
"snap-name": "foo",
|
|
"publisher-id": "dev-id1",
|
|
"timestamp": time.Now().Format(time.RFC3339),
|
|
}, nil, "")
|
|
c.Assert(err, IsNil)
|
|
err = db.Add(snapDecl)
|
|
c.Assert(err, IsNil)
|
|
}
|
|
|
|
func (srs *snapRevSuite) TestSnapRevisionCheck(c *C) {
|
|
storeDB, db := makeStoreAndCheckDB(c)
|
|
|
|
prereqDevAccount(c, storeDB, db)
|
|
prereqSnapDecl(c, storeDB, db)
|
|
|
|
headers := srs.makeHeaders(nil)
|
|
snapRev, err := storeDB.Sign(asserts.SnapRevisionType, headers, nil, "")
|
|
c.Assert(err, IsNil)
|
|
|
|
err = db.Check(snapRev)
|
|
c.Assert(err, IsNil)
|
|
}
|
|
|
|
func (srs *snapRevSuite) TestSnapRevisionCheckInconsistentTimestamp(c *C) {
|
|
storeDB, db := makeStoreAndCheckDB(c)
|
|
|
|
headers := srs.makeHeaders(map[string]interface{}{
|
|
"timestamp": "2013-01-01T14:00:00Z",
|
|
})
|
|
snapRev, err := storeDB.Sign(asserts.SnapRevisionType, headers, nil, "")
|
|
c.Assert(err, IsNil)
|
|
|
|
err = db.Check(snapRev)
|
|
c.Assert(err, ErrorMatches, `snap-revision assertion timestamp "2013-01-01 14:00:00 \+0000 UTC" outside of signing key validity \(key valid since.*\)`)
|
|
}
|
|
|
|
func (srs *snapRevSuite) TestSnapRevisionCheckUntrustedAuthority(c *C) {
|
|
storeDB, db := makeStoreAndCheckDB(c)
|
|
|
|
otherDB := setup3rdPartySigning(c, "other", storeDB, db)
|
|
|
|
headers := srs.makeHeaders(map[string]interface{}{
|
|
"authority-id": "other",
|
|
})
|
|
snapRev, err := otherDB.Sign(asserts.SnapRevisionType, headers, nil, "")
|
|
c.Assert(err, IsNil)
|
|
|
|
err = db.Check(snapRev)
|
|
c.Assert(err, ErrorMatches, `snap-revision assertion for snap id "snap-id-1" is not signed by a store:.*`)
|
|
}
|
|
|
|
func (srs *snapRevSuite) TestSnapRevisionCheckMissingDeveloperAccount(c *C) {
|
|
storeDB, db := makeStoreAndCheckDB(c)
|
|
|
|
headers := srs.makeHeaders(nil)
|
|
snapRev, err := storeDB.Sign(asserts.SnapRevisionType, headers, nil, "")
|
|
c.Assert(err, IsNil)
|
|
|
|
err = db.Check(snapRev)
|
|
c.Assert(err, ErrorMatches, `snap-revision assertion for snap id "snap-id-1" does not have a matching account assertion for the developer "dev-id1"`)
|
|
}
|
|
|
|
func (srs *snapRevSuite) TestSnapRevisionCheckMissingDeclaration(c *C) {
|
|
storeDB, db := makeStoreAndCheckDB(c)
|
|
|
|
prereqDevAccount(c, storeDB, db)
|
|
|
|
headers := srs.makeHeaders(nil)
|
|
snapRev, err := storeDB.Sign(asserts.SnapRevisionType, headers, nil, "")
|
|
c.Assert(err, IsNil)
|
|
|
|
err = db.Check(snapRev)
|
|
c.Assert(err, ErrorMatches, `snap-revision assertion for snap id "snap-id-1" does not have a matching snap-declaration assertion`)
|
|
}
|
|
|
|
func (srs *snapRevSuite) TestRevisionAuthorityCheck(c *C) {
|
|
storeDB, db := makeStoreAndCheckDB(c)
|
|
|
|
delegatedDB := setup3rdPartySigning(c, "delegated-id", storeDB, db)
|
|
headers := srs.makeHeaders(map[string]interface{}{
|
|
"authority-id": "delegated-id",
|
|
"developer-id": "delegated-id",
|
|
"snap-revision": "200",
|
|
"provenance": "prov1",
|
|
})
|
|
a, err := delegatedDB.Sign(asserts.SnapRevisionType, headers, nil, "")
|
|
c.Assert(err, IsNil)
|
|
snapRev := a.(*asserts.SnapRevision)
|
|
|
|
tests := []struct {
|
|
revAuth asserts.RevisionAuthority
|
|
err string
|
|
}{
|
|
{asserts.RevisionAuthority{
|
|
AccountID: "delegated-id",
|
|
Provenance: []string{"prov1", "prov2"},
|
|
MinRevision: 1,
|
|
}, ""},
|
|
{asserts.RevisionAuthority{
|
|
AccountID: "delegated-id",
|
|
Provenance: []string{"prov1", "prov2"},
|
|
MinRevision: 1,
|
|
MaxRevision: 1000,
|
|
}, ""},
|
|
{asserts.RevisionAuthority{
|
|
AccountID: "delegated-id",
|
|
Provenance: []string{"prov2"},
|
|
MinRevision: 1,
|
|
MaxRevision: 1000,
|
|
}, "provenance mismatch"},
|
|
{asserts.RevisionAuthority{
|
|
AccountID: "delegated-id-2",
|
|
Provenance: []string{"prov1", "prov2"},
|
|
MinRevision: 1,
|
|
MaxRevision: 1000,
|
|
}, "authority-id mismatch"},
|
|
{asserts.RevisionAuthority{
|
|
AccountID: "delegated-id",
|
|
Provenance: []string{"prov1", "prov2"},
|
|
MinRevision: 1000,
|
|
}, "snap revision 200 is less than min-revision 1000"},
|
|
{asserts.RevisionAuthority{
|
|
AccountID: "delegated-id",
|
|
Provenance: []string{"prov1", "prov2"},
|
|
MinRevision: 10,
|
|
MaxRevision: 110,
|
|
}, "snap revision 200 is greater than max-revision 110"},
|
|
}
|
|
|
|
for _, t := range tests {
|
|
err := t.revAuth.Check(snapRev, nil, nil)
|
|
if t.err == "" {
|
|
c.Check(err, IsNil)
|
|
} else {
|
|
c.Check(err, ErrorMatches, t.err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (srs *snapRevSuite) TestRevisionAuthorityCheckDeviceScope(c *C) {
|
|
a, err := asserts.Decode([]byte(`type: model
|
|
authority-id: my-brand
|
|
series: 16
|
|
brand-id: my-brand
|
|
model: my-model
|
|
store: substore
|
|
architecture: armhf
|
|
kernel: krnl
|
|
gadget: gadget
|
|
timestamp: 2018-09-12T12:00:00Z
|
|
sign-key-sha3-384: Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij
|
|
|
|
AXNpZw==`))
|
|
c.Assert(err, IsNil)
|
|
myModel := a.(*asserts.Model)
|
|
|
|
a, err = asserts.Decode([]byte(`type: store
|
|
store: substore
|
|
authority-id: canonical
|
|
operator-id: canonical
|
|
friendly-stores:
|
|
- a-store
|
|
- store1
|
|
- store2
|
|
timestamp: 2018-09-12T12:00:00Z
|
|
sign-key-sha3-384: Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij
|
|
|
|
AXNpZw==`))
|
|
c.Assert(err, IsNil)
|
|
substore := a.(*asserts.Store)
|
|
|
|
storeDB, db := makeStoreAndCheckDB(c)
|
|
|
|
delegatedDB := setup3rdPartySigning(c, "my-brand", storeDB, db)
|
|
headers := srs.makeHeaders(map[string]interface{}{
|
|
"authority-id": "my-brand",
|
|
"developer-id": "my-brand",
|
|
"snap-revision": "200",
|
|
"provenance": "prov1",
|
|
})
|
|
a, err = delegatedDB.Sign(asserts.SnapRevisionType, headers, nil, "")
|
|
c.Assert(err, IsNil)
|
|
snapRev := a.(*asserts.SnapRevision)
|
|
|
|
tests := []struct {
|
|
revAuth asserts.RevisionAuthority
|
|
substore *asserts.Store
|
|
err string
|
|
}{
|
|
{asserts.RevisionAuthority{
|
|
AccountID: "my-brand",
|
|
Provenance: []string{"prov1", "prov2"},
|
|
MinRevision: 1,
|
|
}, nil, ""},
|
|
{asserts.RevisionAuthority{
|
|
AccountID: "my-brand",
|
|
Provenance: []string{"prov1", "prov2"},
|
|
MinRevision: 1,
|
|
DeviceScope: &asserts.DeviceScopeConstraint{
|
|
Store: []string{"other-store"},
|
|
},
|
|
}, nil, "on-store mismatch"},
|
|
{asserts.RevisionAuthority{
|
|
AccountID: "my-brand",
|
|
Provenance: []string{"prov1", "prov2"},
|
|
MinRevision: 1,
|
|
DeviceScope: &asserts.DeviceScopeConstraint{
|
|
Store: []string{"substore"},
|
|
},
|
|
}, nil, ""},
|
|
{asserts.RevisionAuthority{
|
|
AccountID: "my-brand",
|
|
Provenance: []string{"prov1", "prov2"},
|
|
MinRevision: 1,
|
|
DeviceScope: &asserts.DeviceScopeConstraint{
|
|
Store: []string{"substore"},
|
|
},
|
|
}, substore, ""},
|
|
{asserts.RevisionAuthority{
|
|
AccountID: "my-brand",
|
|
Provenance: []string{"prov1", "prov2"},
|
|
MinRevision: 1,
|
|
DeviceScope: &asserts.DeviceScopeConstraint{
|
|
Store: []string{"a-store"},
|
|
},
|
|
}, substore, ""},
|
|
{asserts.RevisionAuthority{
|
|
AccountID: "my-brand",
|
|
Provenance: []string{"prov1", "prov2"},
|
|
MinRevision: 1,
|
|
DeviceScope: &asserts.DeviceScopeConstraint{
|
|
Store: []string{"store1"},
|
|
},
|
|
}, nil, "on-store mismatch"},
|
|
{asserts.RevisionAuthority{
|
|
AccountID: "my-brand",
|
|
Provenance: []string{"prov1", "prov2"},
|
|
MinRevision: 1,
|
|
DeviceScope: &asserts.DeviceScopeConstraint{
|
|
Store: []string{"store1", "other-store"},
|
|
},
|
|
}, substore, ""},
|
|
}
|
|
|
|
for _, t := range tests {
|
|
err := t.revAuth.Check(snapRev, myModel, t.substore)
|
|
if t.err == "" {
|
|
c.Check(err, IsNil)
|
|
} else {
|
|
c.Check(err, ErrorMatches, t.err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (srs *snapRevSuite) TestSnapRevisionDelegation(c *C) {
|
|
storeDB, db := makeStoreAndCheckDB(c)
|
|
|
|
delegatedDB := setup3rdPartySigning(c, "delegated-id", storeDB, db)
|
|
|
|
snapDecl, err := storeDB.Sign(asserts.SnapDeclarationType, map[string]interface{}{
|
|
"series": "16",
|
|
"snap-id": "snap-id-1",
|
|
"snap-name": "foo",
|
|
"publisher-id": "delegated-id",
|
|
"timestamp": time.Now().Format(time.RFC3339),
|
|
}, nil, "")
|
|
c.Assert(err, IsNil)
|
|
err = db.Add(snapDecl)
|
|
c.Assert(err, IsNil)
|
|
|
|
headers := srs.makeHeaders(map[string]interface{}{
|
|
"authority-id": "delegated-id",
|
|
"developer-id": "delegated-id",
|
|
"provenance": "prov1",
|
|
})
|
|
snapRev, err := delegatedDB.Sign(asserts.SnapRevisionType, headers, nil, "")
|
|
c.Assert(err, IsNil)
|
|
|
|
err = db.Check(snapRev)
|
|
c.Check(err, ErrorMatches, `snap-revision assertion with provenance "prov1" for snap id "snap-id-1" is not signed by an authorized authority: delegated-id`)
|
|
|
|
// establish delegation
|
|
snapDecl, err = storeDB.Sign(asserts.SnapDeclarationType, map[string]interface{}{
|
|
"series": "16",
|
|
"snap-id": "snap-id-1",
|
|
"snap-name": "foo",
|
|
"publisher-id": "delegated-id",
|
|
"revision": "1",
|
|
"revision-authority": []interface{}{
|
|
map[string]interface{}{
|
|
"account-id": "delegated-id",
|
|
"provenance": []interface{}{
|
|
"prov1",
|
|
},
|
|
// present but not checked at this level
|
|
"on-store": []interface{}{
|
|
"store1",
|
|
},
|
|
},
|
|
},
|
|
"timestamp": time.Now().Format(time.RFC3339),
|
|
}, nil, "")
|
|
c.Assert(err, IsNil)
|
|
err = db.Add(snapDecl)
|
|
c.Assert(err, IsNil)
|
|
|
|
// now revision should be accepted
|
|
err = db.Check(snapRev)
|
|
c.Check(err, IsNil)
|
|
}
|
|
|
|
func (srs *snapRevSuite) TestSnapRevisionDelegationRevisionOutOfRange(c *C) {
|
|
storeDB, db := makeStoreAndCheckDB(c)
|
|
|
|
delegatedDB := setup3rdPartySigning(c, "delegated-id", storeDB, db)
|
|
|
|
// establish delegation
|
|
snapDecl, err := storeDB.Sign(asserts.SnapDeclarationType, map[string]interface{}{
|
|
"series": "16",
|
|
"snap-id": "snap-id-1",
|
|
"snap-name": "foo",
|
|
"publisher-id": "delegated-id",
|
|
"revision-authority": []interface{}{
|
|
map[string]interface{}{
|
|
"account-id": "delegated-id",
|
|
"provenance": []interface{}{
|
|
"prov1",
|
|
},
|
|
// present but not checked at this level
|
|
"on-store": []interface{}{
|
|
"store1",
|
|
},
|
|
"max-revision": "200",
|
|
},
|
|
},
|
|
"timestamp": time.Now().Format(time.RFC3339),
|
|
}, nil, "")
|
|
c.Assert(err, IsNil)
|
|
err = db.Add(snapDecl)
|
|
c.Assert(err, IsNil)
|
|
|
|
headers := srs.makeHeaders(map[string]interface{}{
|
|
"authority-id": "delegated-id",
|
|
"developer-id": "delegated-id",
|
|
"provenance": "prov1",
|
|
"snap-revision": "1000",
|
|
})
|
|
snapRev, err := delegatedDB.Sign(asserts.SnapRevisionType, headers, nil, "")
|
|
c.Assert(err, IsNil)
|
|
|
|
err = db.Check(snapRev)
|
|
c.Check(err, ErrorMatches, `snap-revision assertion with provenance "prov1" for snap id "snap-id-1" is not signed by an authorized authority: delegated-id`)
|
|
}
|
|
|
|
func (srs *snapRevSuite) TestPrimaryKey(c *C) {
|
|
storeDB, db := makeStoreAndCheckDB(c)
|
|
|
|
prereqDevAccount(c, storeDB, db)
|
|
prereqSnapDecl(c, storeDB, db)
|
|
|
|
headers := srs.makeHeaders(nil)
|
|
snapRev, err := storeDB.Sign(asserts.SnapRevisionType, headers, nil, "")
|
|
c.Assert(err, IsNil)
|
|
err = db.Add(snapRev)
|
|
c.Assert(err, IsNil)
|
|
|
|
_, err = db.Find(asserts.SnapRevisionType, map[string]string{
|
|
"snap-sha3-384": headers["snap-sha3-384"].(string),
|
|
})
|
|
c.Assert(err, IsNil)
|
|
}
|
|
|
|
func (srs *snapRevSuite) TestPrerequisites(c *C) {
|
|
encoded := srs.makeValidEncoded()
|
|
a, err := asserts.Decode([]byte(encoded))
|
|
c.Assert(err, IsNil)
|
|
|
|
prereqs := a.Prerequisites()
|
|
c.Assert(prereqs, HasLen, 2)
|
|
c.Check(prereqs[0], DeepEquals, &asserts.Ref{
|
|
Type: asserts.SnapDeclarationType,
|
|
PrimaryKey: []string{"16", "snap-id-1"},
|
|
})
|
|
c.Check(prereqs[1], DeepEquals, &asserts.Ref{
|
|
Type: asserts.AccountType,
|
|
PrimaryKey: []string{"dev-id1"},
|
|
})
|
|
}
|
|
|
|
type validationSuite struct {
|
|
ts time.Time
|
|
tsLine string
|
|
}
|
|
|
|
func (vs *validationSuite) SetUpSuite(c *C) {
|
|
vs.ts = time.Now().Truncate(time.Second).UTC()
|
|
vs.tsLine = "timestamp: " + vs.ts.Format(time.RFC3339) + "\n"
|
|
}
|
|
|
|
func (vs *validationSuite) makeValidEncoded() string {
|
|
return "type: validation\n" +
|
|
"authority-id: dev-id1\n" +
|
|
"series: 16\n" +
|
|
"snap-id: snap-id-1\n" +
|
|
"approved-snap-id: snap-id-2\n" +
|
|
"approved-snap-revision: 42\n" +
|
|
"revision: 1\n" +
|
|
vs.tsLine +
|
|
"sign-key-sha3-384: Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij" +
|
|
"\n\n" +
|
|
"AXNpZw=="
|
|
}
|
|
|
|
func (vs *validationSuite) makeHeaders(overrides map[string]interface{}) map[string]interface{} {
|
|
headers := map[string]interface{}{
|
|
"authority-id": "dev-id1",
|
|
"series": "16",
|
|
"snap-id": "snap-id-1",
|
|
"approved-snap-id": "snap-id-2",
|
|
"approved-snap-revision": "42",
|
|
"revision": "1",
|
|
"timestamp": time.Now().Format(time.RFC3339),
|
|
}
|
|
for k, v := range overrides {
|
|
headers[k] = v
|
|
}
|
|
return headers
|
|
}
|
|
|
|
func (vs *validationSuite) TestDecodeOK(c *C) {
|
|
encoded := vs.makeValidEncoded()
|
|
a, err := asserts.Decode([]byte(encoded))
|
|
c.Assert(err, IsNil)
|
|
c.Check(a.Type(), Equals, asserts.ValidationType)
|
|
validation := a.(*asserts.Validation)
|
|
c.Check(validation.AuthorityID(), Equals, "dev-id1")
|
|
c.Check(validation.Timestamp(), Equals, vs.ts)
|
|
c.Check(validation.Series(), Equals, "16")
|
|
c.Check(validation.SnapID(), Equals, "snap-id-1")
|
|
c.Check(validation.ApprovedSnapID(), Equals, "snap-id-2")
|
|
c.Check(validation.ApprovedSnapRevision(), Equals, 42)
|
|
c.Check(validation.Revoked(), Equals, false)
|
|
c.Check(validation.Revision(), Equals, 1)
|
|
}
|
|
|
|
const (
|
|
validationErrPrefix = "assertion validation: "
|
|
)
|
|
|
|
func (vs *validationSuite) TestDecodeInvalid(c *C) {
|
|
encoded := vs.makeValidEncoded()
|
|
|
|
invalidTests := []struct{ original, invalid, expectedErr string }{
|
|
{"series: 16\n", "", `"series" header is mandatory`},
|
|
{"series: 16\n", "series: \n", `"series" header should not be empty`},
|
|
{"snap-id: snap-id-1\n", "", `"snap-id" header is mandatory`},
|
|
{"snap-id: snap-id-1\n", "snap-id: \n", `"snap-id" header should not be empty`},
|
|
{"approved-snap-id: snap-id-2\n", "", `"approved-snap-id" header is mandatory`},
|
|
{"approved-snap-id: snap-id-2\n", "approved-snap-id: \n", `"approved-snap-id" header should not be empty`},
|
|
{"approved-snap-revision: 42\n", "", `"approved-snap-revision" header is mandatory`},
|
|
{"approved-snap-revision: 42\n", "approved-snap-revision: z\n", `"approved-snap-revision" header is not an integer: z`},
|
|
{"approved-snap-revision: 42\n", "approved-snap-revision: 0\n", `"approved-snap-revision" header must be >=1: 0`},
|
|
{"approved-snap-revision: 42\n", "approved-snap-revision: -1\n", `"approved-snap-revision" header must be >=1: -1`},
|
|
{vs.tsLine, "", `"timestamp" header is mandatory`},
|
|
{vs.tsLine, "timestamp: \n", `"timestamp" header should not be empty`},
|
|
{vs.tsLine, "timestamp: 12:30\n", `"timestamp" header is not a RFC3339 date: .*`},
|
|
}
|
|
|
|
for _, test := range invalidTests {
|
|
invalid := strings.Replace(encoded, test.original, test.invalid, 1)
|
|
_, err := asserts.Decode([]byte(invalid))
|
|
c.Check(err, ErrorMatches, validationErrPrefix+test.expectedErr)
|
|
}
|
|
}
|
|
|
|
func prereqSnapDecl2(c *C, storeDB assertstest.SignerDB, db *asserts.Database) {
|
|
snapDecl, err := storeDB.Sign(asserts.SnapDeclarationType, map[string]interface{}{
|
|
"series": "16",
|
|
"snap-id": "snap-id-2",
|
|
"snap-name": "bar",
|
|
"publisher-id": "dev-id1",
|
|
"timestamp": time.Now().Format(time.RFC3339),
|
|
}, nil, "")
|
|
c.Assert(err, IsNil)
|
|
err = db.Add(snapDecl)
|
|
c.Assert(err, IsNil)
|
|
}
|
|
|
|
func (vs *validationSuite) TestValidationCheck(c *C) {
|
|
storeDB, db := makeStoreAndCheckDB(c)
|
|
devDB := setup3rdPartySigning(c, "dev-id1", storeDB, db)
|
|
|
|
prereqSnapDecl(c, storeDB, db)
|
|
prereqSnapDecl2(c, storeDB, db)
|
|
|
|
headers := vs.makeHeaders(nil)
|
|
validation, err := devDB.Sign(asserts.ValidationType, headers, nil, "")
|
|
c.Assert(err, IsNil)
|
|
|
|
err = db.Check(validation)
|
|
c.Assert(err, IsNil)
|
|
}
|
|
|
|
func (vs *validationSuite) TestValidationCheckWrongAuthority(c *C) {
|
|
storeDB, db := makeStoreAndCheckDB(c)
|
|
|
|
prereqDevAccount(c, storeDB, db)
|
|
prereqSnapDecl(c, storeDB, db)
|
|
prereqSnapDecl2(c, storeDB, db)
|
|
|
|
headers := vs.makeHeaders(map[string]interface{}{
|
|
"authority-id": "canonical", // not the publisher
|
|
})
|
|
validation, err := storeDB.Sign(asserts.ValidationType, headers, nil, "")
|
|
c.Assert(err, IsNil)
|
|
|
|
err = db.Check(validation)
|
|
c.Assert(err, ErrorMatches, `validation assertion by snap "foo" \(id "snap-id-1"\) not signed by its publisher`)
|
|
}
|
|
|
|
func (vs *validationSuite) TestRevocation(c *C) {
|
|
encoded := "type: validation\n" +
|
|
"authority-id: dev-id1\n" +
|
|
"series: 16\n" +
|
|
"snap-id: snap-id-1\n" +
|
|
"approved-snap-id: snap-id-2\n" +
|
|
"approved-snap-revision: 42\n" +
|
|
"revoked: true\n" +
|
|
"revision: 1\n" +
|
|
vs.tsLine +
|
|
"sign-key-sha3-384: Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij" +
|
|
"\n\n" +
|
|
"AXNpZw=="
|
|
a, err := asserts.Decode([]byte(encoded))
|
|
c.Assert(err, IsNil)
|
|
validation := a.(*asserts.Validation)
|
|
c.Check(validation.Revoked(), Equals, true)
|
|
}
|
|
|
|
func (vs *validationSuite) TestRevokedFalse(c *C) {
|
|
encoded := "type: validation\n" +
|
|
"authority-id: dev-id1\n" +
|
|
"series: 16\n" +
|
|
"snap-id: snap-id-1\n" +
|
|
"approved-snap-id: snap-id-2\n" +
|
|
"approved-snap-revision: 42\n" +
|
|
"revoked: false\n" +
|
|
"revision: 1\n" +
|
|
vs.tsLine +
|
|
"sign-key-sha3-384: Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij" +
|
|
"\n\n" +
|
|
"AXNpZw=="
|
|
a, err := asserts.Decode([]byte(encoded))
|
|
c.Assert(err, IsNil)
|
|
validation := a.(*asserts.Validation)
|
|
c.Check(validation.Revoked(), Equals, false)
|
|
}
|
|
|
|
func (vs *validationSuite) TestRevokedInvalid(c *C) {
|
|
encoded := "type: validation\n" +
|
|
"authority-id: dev-id1\n" +
|
|
"series: 16\n" +
|
|
"snap-id: snap-id-1\n" +
|
|
"approved-snap-id: snap-id-2\n" +
|
|
"approved-snap-revision: 42\n" +
|
|
"revoked: foo\n" +
|
|
"revision: 1\n" +
|
|
vs.tsLine +
|
|
"sign-key-sha3-384: Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij" +
|
|
"\n\n" +
|
|
"AXNpZw=="
|
|
_, err := asserts.Decode([]byte(encoded))
|
|
c.Check(err, ErrorMatches, `.*: "revoked" header must be 'true' or 'false'`)
|
|
}
|
|
|
|
func (vs *validationSuite) TestMissingGatedSnapDeclaration(c *C) {
|
|
storeDB, db := makeStoreAndCheckDB(c)
|
|
devDB := setup3rdPartySigning(c, "dev-id1", storeDB, db)
|
|
|
|
headers := vs.makeHeaders(nil)
|
|
a, err := devDB.Sign(asserts.ValidationType, headers, nil, "")
|
|
c.Assert(err, IsNil)
|
|
|
|
err = db.Check(a)
|
|
c.Assert(err, ErrorMatches, `validation assertion by snap-id "snap-id-1" does not have a matching snap-declaration assertion for approved-snap-id "snap-id-2"`)
|
|
}
|
|
|
|
func (vs *validationSuite) TestMissingGatingSnapDeclaration(c *C) {
|
|
storeDB, db := makeStoreAndCheckDB(c)
|
|
devDB := setup3rdPartySigning(c, "dev-id1", storeDB, db)
|
|
|
|
prereqSnapDecl2(c, storeDB, db)
|
|
|
|
headers := vs.makeHeaders(nil)
|
|
a, err := devDB.Sign(asserts.ValidationType, headers, nil, "")
|
|
c.Assert(err, IsNil)
|
|
|
|
err = db.Check(a)
|
|
c.Assert(err, ErrorMatches, `validation assertion by snap-id "snap-id-1" does not have a matching snap-declaration assertion`)
|
|
}
|
|
|
|
func (vs *validationSuite) TestPrerequisites(c *C) {
|
|
encoded := vs.makeValidEncoded()
|
|
a, err := asserts.Decode([]byte(encoded))
|
|
c.Assert(err, IsNil)
|
|
|
|
prereqs := a.Prerequisites()
|
|
c.Assert(prereqs, HasLen, 2)
|
|
c.Check(prereqs[0], DeepEquals, &asserts.Ref{
|
|
Type: asserts.SnapDeclarationType,
|
|
PrimaryKey: []string{"16", "snap-id-1"},
|
|
})
|
|
c.Check(prereqs[1], DeepEquals, &asserts.Ref{
|
|
Type: asserts.SnapDeclarationType,
|
|
PrimaryKey: []string{"16", "snap-id-2"},
|
|
})
|
|
}
|
|
|
|
type baseDeclSuite struct{}
|
|
|
|
func (s *baseDeclSuite) TestDecodeOK(c *C) {
|
|
encoded := `type: base-declaration
|
|
authority-id: canonical
|
|
series: 16
|
|
plugs:
|
|
interface1:
|
|
deny-installation: false
|
|
allow-auto-connection:
|
|
slot-snap-type:
|
|
- app
|
|
slot-publisher-id:
|
|
- acme
|
|
slot-attributes:
|
|
a1: /foo/.*
|
|
plug-attributes:
|
|
b1: B1
|
|
deny-auto-connection:
|
|
slot-attributes:
|
|
a1: !A1
|
|
plug-attributes:
|
|
b1: !B1
|
|
interface2:
|
|
allow-installation: true
|
|
allow-connection:
|
|
plug-attributes:
|
|
a2: A2
|
|
slot-attributes:
|
|
b2: B2
|
|
deny-connection:
|
|
slot-snap-id:
|
|
- snapidsnapidsnapidsnapidsnapid01
|
|
- snapidsnapidsnapidsnapidsnapid02
|
|
plug-attributes:
|
|
a2: !A2
|
|
slot-attributes:
|
|
b2: !B2
|
|
slots:
|
|
interface3:
|
|
deny-installation: false
|
|
allow-auto-connection:
|
|
plug-snap-type:
|
|
- app
|
|
plug-publisher-id:
|
|
- acme
|
|
slot-attributes:
|
|
c1: /foo/.*
|
|
plug-attributes:
|
|
d1: C1
|
|
deny-auto-connection:
|
|
slot-attributes:
|
|
c1: !C1
|
|
plug-attributes:
|
|
d1: !D1
|
|
interface4:
|
|
allow-connection:
|
|
plug-attributes:
|
|
c2: C2
|
|
slot-attributes:
|
|
d2: D2
|
|
deny-connection:
|
|
plug-snap-id:
|
|
- snapidsnapidsnapidsnapidsnapid01
|
|
- snapidsnapidsnapidsnapidsnapid02
|
|
plug-attributes:
|
|
c2: !D2
|
|
slot-attributes:
|
|
d2: !D2
|
|
allow-installation:
|
|
slot-snap-type:
|
|
- app
|
|
slot-attributes:
|
|
e1: E1
|
|
timestamp: 2016-09-29T19:50:49Z
|
|
sign-key-sha3-384: Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij
|
|
|
|
AXNpZw==`
|
|
a, err := asserts.Decode([]byte(encoded))
|
|
c.Assert(err, IsNil)
|
|
baseDecl := a.(*asserts.BaseDeclaration)
|
|
c.Check(baseDecl.Series(), Equals, "16")
|
|
ts, err := time.Parse(time.RFC3339, "2016-09-29T19:50:49Z")
|
|
c.Assert(err, IsNil)
|
|
c.Check(baseDecl.Timestamp().Equal(ts), Equals, true)
|
|
|
|
c.Check(baseDecl.PlugRule("interfaceX"), IsNil)
|
|
c.Check(baseDecl.SlotRule("interfaceX"), IsNil)
|
|
|
|
plug := emptyAttrerObject{}
|
|
slot := emptyAttrerObject{}
|
|
|
|
plugRule1 := baseDecl.PlugRule("interface1")
|
|
c.Assert(plugRule1, NotNil)
|
|
c.Assert(plugRule1.DenyInstallation, HasLen, 1)
|
|
c.Check(plugRule1.DenyInstallation[0].PlugAttributes, Equals, asserts.NeverMatchAttributes)
|
|
c.Assert(plugRule1.AllowAutoConnection, HasLen, 1)
|
|
c.Check(plugRule1.AllowAutoConnection[0].SlotAttributes.Check(slot, nil), ErrorMatches, `attribute "a1".*`)
|
|
c.Check(plugRule1.AllowAutoConnection[0].PlugAttributes.Check(plug, nil), ErrorMatches, `attribute "b1".*`)
|
|
c.Check(plugRule1.AllowAutoConnection[0].SlotSnapTypes, DeepEquals, []string{"app"})
|
|
c.Check(plugRule1.AllowAutoConnection[0].SlotPublisherIDs, DeepEquals, []string{"acme"})
|
|
c.Assert(plugRule1.DenyAutoConnection, HasLen, 1)
|
|
c.Check(plugRule1.DenyAutoConnection[0].SlotAttributes.Check(slot, nil), ErrorMatches, `attribute "a1".*`)
|
|
c.Check(plugRule1.DenyAutoConnection[0].PlugAttributes.Check(plug, nil), ErrorMatches, `attribute "b1".*`)
|
|
plugRule2 := baseDecl.PlugRule("interface2")
|
|
c.Assert(plugRule2, NotNil)
|
|
c.Assert(plugRule2.AllowInstallation, HasLen, 1)
|
|
c.Check(plugRule2.AllowInstallation[0].PlugAttributes, Equals, asserts.AlwaysMatchAttributes)
|
|
c.Assert(plugRule2.AllowConnection, HasLen, 1)
|
|
c.Check(plugRule2.AllowConnection[0].PlugAttributes.Check(plug, nil), ErrorMatches, `attribute "a2".*`)
|
|
c.Check(plugRule2.AllowConnection[0].SlotAttributes.Check(slot, nil), ErrorMatches, `attribute "b2".*`)
|
|
c.Assert(plugRule2.DenyConnection, HasLen, 1)
|
|
c.Check(plugRule2.DenyConnection[0].PlugAttributes.Check(plug, nil), ErrorMatches, `attribute "a2".*`)
|
|
c.Check(plugRule2.DenyConnection[0].SlotAttributes.Check(slot, nil), ErrorMatches, `attribute "b2".*`)
|
|
c.Check(plugRule2.DenyConnection[0].SlotSnapIDs, DeepEquals, []string{"snapidsnapidsnapidsnapidsnapid01", "snapidsnapidsnapidsnapidsnapid02"})
|
|
|
|
slotRule3 := baseDecl.SlotRule("interface3")
|
|
c.Assert(slotRule3, NotNil)
|
|
c.Assert(slotRule3.DenyInstallation, HasLen, 1)
|
|
c.Check(slotRule3.DenyInstallation[0].SlotAttributes, Equals, asserts.NeverMatchAttributes)
|
|
c.Assert(slotRule3.AllowAutoConnection, HasLen, 1)
|
|
c.Check(slotRule3.AllowAutoConnection[0].SlotAttributes.Check(slot, nil), ErrorMatches, `attribute "c1".*`)
|
|
c.Check(slotRule3.AllowAutoConnection[0].PlugAttributes.Check(plug, nil), ErrorMatches, `attribute "d1".*`)
|
|
c.Check(slotRule3.AllowAutoConnection[0].PlugSnapTypes, DeepEquals, []string{"app"})
|
|
c.Check(slotRule3.AllowAutoConnection[0].PlugPublisherIDs, DeepEquals, []string{"acme"})
|
|
c.Assert(slotRule3.DenyAutoConnection, HasLen, 1)
|
|
c.Check(slotRule3.DenyAutoConnection[0].SlotAttributes.Check(slot, nil), ErrorMatches, `attribute "c1".*`)
|
|
c.Check(slotRule3.DenyAutoConnection[0].PlugAttributes.Check(plug, nil), ErrorMatches, `attribute "d1".*`)
|
|
slotRule4 := baseDecl.SlotRule("interface4")
|
|
c.Assert(slotRule4, NotNil)
|
|
c.Assert(slotRule4.AllowConnection, HasLen, 1)
|
|
c.Check(slotRule4.AllowConnection[0].PlugAttributes.Check(plug, nil), ErrorMatches, `attribute "c2".*`)
|
|
c.Check(slotRule4.AllowConnection[0].SlotAttributes.Check(slot, nil), ErrorMatches, `attribute "d2".*`)
|
|
c.Assert(slotRule4.DenyConnection, HasLen, 1)
|
|
c.Check(slotRule4.DenyConnection[0].PlugAttributes.Check(plug, nil), ErrorMatches, `attribute "c2".*`)
|
|
c.Check(slotRule4.DenyConnection[0].SlotAttributes.Check(slot, nil), ErrorMatches, `attribute "d2".*`)
|
|
c.Check(slotRule4.DenyConnection[0].PlugSnapIDs, DeepEquals, []string{"snapidsnapidsnapidsnapidsnapid01", "snapidsnapidsnapidsnapidsnapid02"})
|
|
c.Assert(slotRule4.AllowInstallation, HasLen, 1)
|
|
c.Check(slotRule4.AllowInstallation[0].SlotAttributes.Check(slot, nil), ErrorMatches, `attribute "e1".*`)
|
|
c.Check(slotRule4.AllowInstallation[0].SlotSnapTypes, DeepEquals, []string{"app"})
|
|
|
|
}
|
|
|
|
func (s *baseDeclSuite) TestBaseDeclarationCheckUntrustedAuthority(c *C) {
|
|
storeDB, db := makeStoreAndCheckDB(c)
|
|
|
|
otherDB := setup3rdPartySigning(c, "other", storeDB, db)
|
|
|
|
headers := map[string]interface{}{
|
|
"series": "16",
|
|
"timestamp": time.Now().Format(time.RFC3339),
|
|
}
|
|
baseDecl, err := otherDB.Sign(asserts.BaseDeclarationType, headers, nil, "")
|
|
c.Assert(err, IsNil)
|
|
|
|
err = db.Check(baseDecl)
|
|
c.Assert(err, ErrorMatches, `base-declaration assertion for series 16 is not signed by a directly trusted authority: other`)
|
|
}
|
|
|
|
const (
|
|
baseDeclErrPrefix = "assertion base-declaration: "
|
|
)
|
|
|
|
func (s *baseDeclSuite) TestDecodeInvalid(c *C) {
|
|
tsLine := "timestamp: 2016-09-29T19:50:49Z\n"
|
|
|
|
encoded := "type: base-declaration\n" +
|
|
"authority-id: canonical\n" +
|
|
"series: 16\n" +
|
|
"plugs:\n interface1: true\n" +
|
|
"slots:\n interface2: true\n" +
|
|
tsLine +
|
|
"sign-key-sha3-384: Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij" +
|
|
"\n\n" +
|
|
"AXNpZw=="
|
|
|
|
invalidTests := []struct{ original, invalid, expectedErr string }{
|
|
{"series: 16\n", "", `"series" header is mandatory`},
|
|
{"series: 16\n", "series: \n", `"series" header should not be empty`},
|
|
{"plugs:\n interface1: true\n", "plugs: \n", `"plugs" header must be a map`},
|
|
{"plugs:\n interface1: true\n", "plugs:\n intf1:\n foo: bar\n", `plug rule for interface "intf1" must specify at least one of.*`},
|
|
{"slots:\n interface2: true\n", "slots: \n", `"slots" header must be a map`},
|
|
{"slots:\n interface2: true\n", "slots:\n intf1:\n foo: bar\n", `slot rule for interface "intf1" must specify at least one of.*`},
|
|
{tsLine, "", `"timestamp" header is mandatory`},
|
|
{tsLine, "timestamp: 12:30\n", `"timestamp" header is not a RFC3339 date: .*`},
|
|
}
|
|
|
|
for _, test := range invalidTests {
|
|
invalid := strings.Replace(encoded, test.original, test.invalid, 1)
|
|
_, err := asserts.Decode([]byte(invalid))
|
|
c.Check(err, ErrorMatches, baseDeclErrPrefix+test.expectedErr)
|
|
}
|
|
|
|
}
|
|
|
|
func (s *baseDeclSuite) TestBuiltin(c *C) {
|
|
baseDecl := asserts.BuiltinBaseDeclaration()
|
|
c.Check(baseDecl, IsNil)
|
|
|
|
defer asserts.InitBuiltinBaseDeclaration(nil)
|
|
|
|
const headers = `
|
|
type: base-declaration
|
|
authority-id: canonical
|
|
series: 16
|
|
revision: 0
|
|
plugs:
|
|
network: true
|
|
slots:
|
|
network:
|
|
allow-installation:
|
|
slot-snap-type:
|
|
- core
|
|
`
|
|
|
|
err := asserts.InitBuiltinBaseDeclaration([]byte(headers))
|
|
c.Assert(err, IsNil)
|
|
|
|
baseDecl = asserts.BuiltinBaseDeclaration()
|
|
c.Assert(baseDecl, NotNil)
|
|
|
|
cont, _ := baseDecl.Signature()
|
|
c.Check(string(cont), Equals, strings.TrimSpace(headers))
|
|
|
|
c.Check(baseDecl.AuthorityID(), Equals, "canonical")
|
|
c.Check(baseDecl.Series(), Equals, "16")
|
|
c.Check(baseDecl.PlugRule("network").AllowAutoConnection[0].SlotAttributes, Equals, asserts.AlwaysMatchAttributes)
|
|
c.Check(baseDecl.SlotRule("network").AllowInstallation[0].SlotSnapTypes, DeepEquals, []string{"core"})
|
|
|
|
enc := asserts.Encode(baseDecl)
|
|
// it's expected that it cannot be decoded
|
|
_, err = asserts.Decode(enc)
|
|
c.Check(err, NotNil)
|
|
}
|
|
|
|
func (s *baseDeclSuite) TestBuiltinInitErrors(c *C) {
|
|
defer asserts.InitBuiltinBaseDeclaration(nil)
|
|
|
|
tests := []struct {
|
|
headers string
|
|
err string
|
|
}{
|
|
{"", `header entry missing ':' separator: ""`},
|
|
{"type: foo\n", `the builtin base-declaration "type" header is not set to expected value "base-declaration"`},
|
|
{"type: base-declaration", `the builtin base-declaration "authority-id" header is not set to expected value "canonical"`},
|
|
{"type: base-declaration\nauthority-id: canonical", `the builtin base-declaration "series" header is not set to expected value "16"`},
|
|
{"type: base-declaration\nauthority-id: canonical\nseries: 16\nrevision: zzz", `cannot assemble the builtin-base declaration: "revision" header is not an integer: zzz`},
|
|
{"type: base-declaration\nauthority-id: canonical\nseries: 16\nplugs: foo", `cannot assemble the builtin base-declaration: "plugs" header must be a map`},
|
|
}
|
|
|
|
for _, t := range tests {
|
|
err := asserts.InitBuiltinBaseDeclaration([]byte(t.headers))
|
|
c.Check(err, ErrorMatches, t.err, Commentf(t.headers))
|
|
}
|
|
}
|
|
|
|
type snapDevSuite struct {
|
|
developersLines string
|
|
validEncoded string
|
|
}
|
|
|
|
func (sds *snapDevSuite) SetUpSuite(c *C) {
|
|
sds.developersLines = "developers:\n -\n developer-id: dev-id2\n since: 2017-01-01T00:00:00.0Z\n until: 2017-02-01T00:00:00.0Z\n"
|
|
sds.validEncoded = "type: snap-developer\n" +
|
|
"authority-id: dev-id1\n" +
|
|
"snap-id: snap-id-1\n" +
|
|
"publisher-id: dev-id1\n" +
|
|
sds.developersLines +
|
|
"sign-key-sha3-384: Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij" +
|
|
"\n\n" +
|
|
"AXNpZw=="
|
|
}
|
|
|
|
func (sds *snapDevSuite) TestDecodeOK(c *C) {
|
|
encoded := sds.validEncoded
|
|
a, err := asserts.Decode([]byte(encoded))
|
|
c.Assert(err, IsNil)
|
|
c.Check(a.Type(), Equals, asserts.SnapDeveloperType)
|
|
snapDev := a.(*asserts.SnapDeveloper)
|
|
c.Check(snapDev.AuthorityID(), Equals, "dev-id1")
|
|
c.Check(snapDev.PublisherID(), Equals, "dev-id1")
|
|
c.Check(snapDev.SnapID(), Equals, "snap-id-1")
|
|
}
|
|
|
|
func (sds *snapDevSuite) TestDevelopersOptional(c *C) {
|
|
encoded := strings.Replace(sds.validEncoded, sds.developersLines, "", 1)
|
|
_, err := asserts.Decode([]byte(encoded))
|
|
c.Check(err, IsNil)
|
|
}
|
|
|
|
func (sds *snapDevSuite) TestDevelopersUntilOptional(c *C) {
|
|
encoded := strings.Replace(
|
|
sds.validEncoded, sds.developersLines,
|
|
"developers:\n -\n developer-id: dev-id2\n since: 2017-01-01T00:00:00.0Z\n", 1)
|
|
_, err := asserts.Decode([]byte(encoded))
|
|
c.Check(err, IsNil)
|
|
}
|
|
|
|
func (sds *snapDevSuite) TestDevelopersRevoked(c *C) {
|
|
encoded := sds.validEncoded
|
|
encoded = strings.Replace(
|
|
encoded, sds.developersLines,
|
|
"developers:\n -\n developer-id: dev-id2\n since: 2017-01-01T00:00:00.0Z\n until: 2017-01-01T00:00:00.0Z\n", 1)
|
|
_, err := asserts.Decode([]byte(encoded))
|
|
c.Check(err, IsNil)
|
|
// TODO(matt): check actually revoked rather than just parsed
|
|
}
|
|
|
|
const (
|
|
snapDevErrPrefix = "assertion snap-developer: "
|
|
)
|
|
|
|
func (sds *snapDevSuite) TestDecodeInvalid(c *C) {
|
|
encoded := sds.validEncoded
|
|
|
|
invalidTests := []struct{ original, invalid, expectedErr string }{
|
|
{"publisher-id: dev-id1\n", "", `"publisher-id" header is mandatory`},
|
|
{"publisher-id: dev-id1\n", "publisher-id: \n", `"publisher-id" header should not be empty`},
|
|
{"snap-id: snap-id-1\n", "", `"snap-id" header is mandatory`},
|
|
{"snap-id: snap-id-1\n", "snap-id: \n", `"snap-id" header should not be empty`},
|
|
{sds.developersLines, "developers: \n", `"developers" must be a list of developer maps`},
|
|
{sds.developersLines, "developers: foo\n", `"developers" must be a list of developer maps`},
|
|
{sds.developersLines, "developers:\n foo: bar\n", `"developers" must be a list of developer maps`},
|
|
{sds.developersLines, "developers:\n - foo\n", `"developers" must be a list of developer maps`},
|
|
{sds.developersLines, "developers:\n -\n foo: bar\n", `"developer-id" in "developers" item 1 is mandatory`},
|
|
{sds.developersLines, "developers:\n -\n developer-id: a\n",
|
|
`"developer-id" in "developers" item 1 contains invalid characters: "a"`},
|
|
{sds.developersLines, "developers:\n -\n developer-id: dev-id2\n",
|
|
`"since" in "developers" item 1 for developer "dev-id2" is mandatory`},
|
|
{sds.developersLines, "developers:\n -\n developer-id: dev-id2\n since: \n",
|
|
`"since" in "developers" item 1 for developer "dev-id2" should not be empty`},
|
|
{sds.developersLines, "developers:\n -\n developer-id: dev-id2\n since: foo\n",
|
|
`"since" in "developers" item 1 for developer "dev-id2" is not a RFC3339 date.*`},
|
|
{sds.developersLines, "developers:\n -\n developer-id: dev-id2\n since: 2017-01-01T00:00:00.0Z\n until: \n",
|
|
`"until" in "developers" item 1 for developer "dev-id2" is not a RFC3339 date.*`},
|
|
{sds.developersLines, "developers:\n -\n developer-id: dev-id2\n since: 2017-01-01T00:00:00.0Z\n until: foo\n",
|
|
`"until" in "developers" item 1 for developer "dev-id2" is not a RFC3339 date.*`},
|
|
{sds.developersLines, "developers:\n -\n developer-id: dev-id2\n since: 2017-01-01T00:00:00.0Z\n -\n foo: bar\n",
|
|
`"developer-id" in "developers" item 2 is mandatory`},
|
|
{sds.developersLines, "developers:\n -\n developer-id: dev-id2\n since: 2017-01-02T00:00:00.0Z\n until: 2017-01-01T00:00:00.0Z\n",
|
|
`"since" in "developers" item 1 for developer "dev-id2" must be less than or equal to "until"`},
|
|
}
|
|
|
|
for _, test := range invalidTests {
|
|
invalid := strings.Replace(encoded, test.original, test.invalid, 1)
|
|
_, err := asserts.Decode([]byte(invalid))
|
|
c.Check(err, ErrorMatches, snapDevErrPrefix+test.expectedErr)
|
|
}
|
|
}
|
|
|
|
func (sds *snapDevSuite) TestRevokedValidation(c *C) {
|
|
// Multiple non-revoking items are fine.
|
|
encoded := strings.Replace(sds.validEncoded, sds.developersLines,
|
|
"developers:\n"+
|
|
" -\n developer-id: dev-id2\n since: 2017-01-01T00:00:00.0Z\n until: 2017-02-01T00:00:00.0Z\n"+
|
|
" -\n developer-id: dev-id2\n since: 2017-03-01T00:00:00.0Z\n",
|
|
1)
|
|
_, err := asserts.Decode([]byte(encoded))
|
|
c.Check(err, IsNil)
|
|
|
|
// Multiple revocations for different developers are fine.
|
|
encoded = strings.Replace(sds.validEncoded, sds.developersLines,
|
|
"developers:\n"+
|
|
" -\n developer-id: dev-id2\n since: 2017-01-01T00:00:00.0Z\n until: 2017-01-01T00:00:00.0Z\n"+
|
|
" -\n developer-id: dev-id3\n since: 2017-02-01T00:00:00.0Z\n until: 2017-02-01T00:00:00.0Z\n",
|
|
1)
|
|
_, err = asserts.Decode([]byte(encoded))
|
|
c.Check(err, IsNil)
|
|
|
|
invalidTests := []string{
|
|
// Multiple revocations.
|
|
"developers:\n" +
|
|
" -\n developer-id: dev-id2\n since: 2017-01-01T00:00:00.0Z\n until: 2017-01-01T00:00:00.0Z\n" +
|
|
" -\n developer-id: dev-id2\n since: 2017-02-01T00:00:00.0Z\n until: 2017-02-01T00:00:00.0Z\n",
|
|
// Revocation after non-revoking.
|
|
"developers:\n" +
|
|
" -\n developer-id: dev-id2\n since: 2017-01-01T00:00:00.0Z\n" +
|
|
" -\n developer-id: dev-id2\n since: 2017-03-01T00:00:00.0Z\n until: 2017-03-01T00:00:00.0Z\n",
|
|
// Non-revoking after revocation.
|
|
"developers:\n" +
|
|
" -\n developer-id: dev-id2\n since: 2017-01-01T00:00:00.0Z\n until: 2017-01-01T00:00:00.0Z\n" +
|
|
" -\n developer-id: dev-id2\n since: 2017-02-01T00:00:00.0Z\n",
|
|
}
|
|
for _, test := range invalidTests {
|
|
encoded := strings.Replace(sds.validEncoded, sds.developersLines, test, 1)
|
|
_, err := asserts.Decode([]byte(encoded))
|
|
c.Check(err, ErrorMatches, snapDevErrPrefix+`revocation for developer "dev-id2" must be standalone but found other "developers" items`)
|
|
}
|
|
}
|
|
|
|
func (sds *snapDevSuite) TestAuthorityIsPublisher(c *C) {
|
|
storeDB, db := makeStoreAndCheckDB(c)
|
|
devDB := setup3rdPartySigning(c, "dev-id1", storeDB, db)
|
|
|
|
snapDecl, err := storeDB.Sign(asserts.SnapDeclarationType, map[string]interface{}{
|
|
"series": "16",
|
|
"snap-id": "snap-id-1",
|
|
"snap-name": "snap-name-1",
|
|
"publisher-id": "dev-id1",
|
|
"timestamp": time.Now().UTC().Format(time.RFC3339),
|
|
}, nil, "")
|
|
c.Assert(err, IsNil)
|
|
err = db.Add(snapDecl)
|
|
c.Assert(err, IsNil)
|
|
|
|
snapDev, err := devDB.Sign(asserts.SnapDeveloperType, map[string]interface{}{
|
|
"snap-id": "snap-id-1",
|
|
"publisher-id": "dev-id1",
|
|
}, nil, "")
|
|
c.Assert(err, IsNil)
|
|
// Just to be super sure ...
|
|
c.Assert(snapDev.HeaderString("authority-id"), Equals, "dev-id1")
|
|
c.Assert(snapDev.HeaderString("publisher-id"), Equals, "dev-id1")
|
|
|
|
err = db.Check(snapDev)
|
|
c.Assert(err, IsNil)
|
|
}
|
|
|
|
func (sds *snapDevSuite) TestAuthorityIsNotPublisher(c *C) {
|
|
storeDB, db := makeStoreAndCheckDB(c)
|
|
devDB := setup3rdPartySigning(c, "dev-id1", storeDB, db)
|
|
|
|
snapDecl, err := storeDB.Sign(asserts.SnapDeclarationType, map[string]interface{}{
|
|
"series": "16",
|
|
"snap-id": "snap-id-1",
|
|
"snap-name": "snap-name-1",
|
|
"publisher-id": "dev-id1",
|
|
"timestamp": time.Now().UTC().Format(time.RFC3339),
|
|
}, nil, "")
|
|
c.Assert(err, IsNil)
|
|
err = db.Add(snapDecl)
|
|
c.Assert(err, IsNil)
|
|
|
|
snapDev, err := devDB.Sign(asserts.SnapDeveloperType, map[string]interface{}{
|
|
"authority-id": "dev-id1",
|
|
"snap-id": "snap-id-1",
|
|
"publisher-id": "dev-id2",
|
|
}, nil, "")
|
|
c.Assert(err, IsNil)
|
|
// Just to be super sure ...
|
|
c.Assert(snapDev.HeaderString("authority-id"), Equals, "dev-id1")
|
|
c.Assert(snapDev.HeaderString("publisher-id"), Equals, "dev-id2")
|
|
|
|
err = db.Check(snapDev)
|
|
c.Assert(err, ErrorMatches, `snap-developer must be signed by the publisher or a trusted authority but got authority "dev-id1" and publisher "dev-id2"`)
|
|
}
|
|
|
|
func (sds *snapDevSuite) TestAuthorityIsNotPublisherButIsTrusted(c *C) {
|
|
storeDB, db := makeStoreAndCheckDB(c)
|
|
|
|
account, err := storeDB.Sign(asserts.AccountType, map[string]interface{}{
|
|
"account-id": "dev-id1",
|
|
"display-name": "dev-id1",
|
|
"validation": "unknown",
|
|
"timestamp": time.Now().UTC().Format(time.RFC3339),
|
|
}, nil, "")
|
|
c.Assert(err, IsNil)
|
|
err = db.Add(account)
|
|
c.Assert(err, IsNil)
|
|
|
|
snapDecl, err := storeDB.Sign(asserts.SnapDeclarationType, map[string]interface{}{
|
|
"series": "16",
|
|
"snap-id": "snap-id-1",
|
|
"snap-name": "snap-name-1",
|
|
"publisher-id": "dev-id1",
|
|
"timestamp": time.Now().UTC().Format(time.RFC3339),
|
|
}, nil, "")
|
|
c.Assert(err, IsNil)
|
|
err = db.Add(snapDecl)
|
|
c.Assert(err, IsNil)
|
|
|
|
snapDev, err := storeDB.Sign(asserts.SnapDeveloperType, map[string]interface{}{
|
|
"snap-id": "snap-id-1",
|
|
"publisher-id": "dev-id1",
|
|
}, nil, "")
|
|
c.Assert(err, IsNil)
|
|
// Just to be super sure ...
|
|
c.Assert(snapDev.HeaderString("authority-id"), Equals, "canonical")
|
|
c.Assert(snapDev.HeaderString("publisher-id"), Equals, "dev-id1")
|
|
|
|
err = db.Check(snapDev)
|
|
c.Assert(err, IsNil)
|
|
}
|
|
|
|
func (sds *snapDevSuite) TestCheckNewPublisherAccountExists(c *C) {
|
|
storeDB, db := makeStoreAndCheckDB(c)
|
|
|
|
account, err := storeDB.Sign(asserts.AccountType, map[string]interface{}{
|
|
"account-id": "dev-id1",
|
|
"display-name": "dev-id1",
|
|
"validation": "unknown",
|
|
"timestamp": time.Now().UTC().Format(time.RFC3339),
|
|
}, nil, "")
|
|
c.Assert(err, IsNil)
|
|
err = db.Add(account)
|
|
c.Assert(err, IsNil)
|
|
|
|
snapDecl, err := storeDB.Sign(asserts.SnapDeclarationType, map[string]interface{}{
|
|
"series": "16",
|
|
"snap-id": "snap-id-1",
|
|
"snap-name": "snap-name-1",
|
|
"publisher-id": "dev-id1",
|
|
"timestamp": time.Now().UTC().Format(time.RFC3339),
|
|
}, nil, "")
|
|
c.Assert(err, IsNil)
|
|
err = db.Add(snapDecl)
|
|
c.Assert(err, IsNil)
|
|
|
|
snapDev, err := storeDB.Sign(asserts.SnapDeveloperType, map[string]interface{}{
|
|
"snap-id": "snap-id-1",
|
|
"publisher-id": "dev-id2",
|
|
}, nil, "")
|
|
c.Assert(err, IsNil)
|
|
// Just to be super sure ...
|
|
c.Assert(snapDev.HeaderString("authority-id"), Equals, "canonical")
|
|
c.Assert(snapDev.HeaderString("publisher-id"), Equals, "dev-id2")
|
|
|
|
// There's no account for dev-id2 yet so it should fail.
|
|
err = db.Check(snapDev)
|
|
c.Assert(err, ErrorMatches, `snap-developer assertion for snap-id "snap-id-1" does not have a matching account assertion for the publisher "dev-id2"`)
|
|
|
|
// But once the dev-id2 account is added the snap-developer is ok.
|
|
account, err = storeDB.Sign(asserts.AccountType, map[string]interface{}{
|
|
"account-id": "dev-id2",
|
|
"display-name": "dev-id2",
|
|
"validation": "unknown",
|
|
"timestamp": time.Now().UTC().Format(time.RFC3339),
|
|
}, nil, "")
|
|
c.Assert(err, IsNil)
|
|
err = db.Add(account)
|
|
c.Assert(err, IsNil)
|
|
|
|
err = db.Check(snapDev)
|
|
c.Assert(err, IsNil)
|
|
}
|
|
|
|
func (sds *snapDevSuite) TestCheckDeveloperAccountExists(c *C) {
|
|
storeDB, db := makeStoreAndCheckDB(c)
|
|
devDB := setup3rdPartySigning(c, "dev-id1", storeDB, db)
|
|
|
|
snapDecl, err := storeDB.Sign(asserts.SnapDeclarationType, map[string]interface{}{
|
|
"series": "16",
|
|
"snap-id": "snap-id-1",
|
|
"snap-name": "snap-name-1",
|
|
"publisher-id": "dev-id1",
|
|
"timestamp": time.Now().UTC().Format(time.RFC3339),
|
|
}, nil, "")
|
|
c.Assert(err, IsNil)
|
|
err = db.Add(snapDecl)
|
|
c.Assert(err, IsNil)
|
|
|
|
snapDev, err := devDB.Sign(asserts.SnapDeveloperType, map[string]interface{}{
|
|
"snap-id": "snap-id-1",
|
|
"publisher-id": "dev-id1",
|
|
"developers": []interface{}{
|
|
map[string]interface{}{
|
|
"developer-id": "dev-id2",
|
|
"since": "2017-01-01T00:00:00.0Z",
|
|
},
|
|
},
|
|
}, nil, "")
|
|
c.Assert(err, IsNil)
|
|
err = db.Check(snapDev)
|
|
c.Assert(err, ErrorMatches, `snap-developer assertion for snap-id "snap-id-1" does not have a matching account assertion for the developer "dev-id2"`)
|
|
}
|
|
|
|
func (sds *snapDevSuite) TestCheckMissingDeclaration(c *C) {
|
|
storeDB, db := makeStoreAndCheckDB(c)
|
|
devDB := setup3rdPartySigning(c, "dev-id1", storeDB, db)
|
|
|
|
headers := map[string]interface{}{
|
|
"authority-id": "dev-id1",
|
|
"snap-id": "snap-id-1",
|
|
"publisher-id": "dev-id1",
|
|
}
|
|
snapDev, err := devDB.Sign(asserts.SnapDeveloperType, headers, nil, "")
|
|
c.Assert(err, IsNil)
|
|
|
|
err = db.Check(snapDev)
|
|
c.Assert(err, ErrorMatches, `snap-developer assertion for snap id "snap-id-1" does not have a matching snap-declaration assertion`)
|
|
}
|
|
|
|
func (sds *snapDevSuite) TestPrerequisitesNoDevelopers(c *C) {
|
|
encoded := strings.Replace(sds.validEncoded, sds.developersLines, "", 1)
|
|
assert, err := asserts.Decode([]byte(encoded))
|
|
c.Assert(err, IsNil)
|
|
prereqs := assert.Prerequisites()
|
|
sort.Sort(RefSlice(prereqs))
|
|
c.Assert(prereqs, DeepEquals, []*asserts.Ref{
|
|
{Type: asserts.AccountType, PrimaryKey: []string{"dev-id1"}},
|
|
{Type: asserts.SnapDeclarationType, PrimaryKey: []string{"16", "snap-id-1"}},
|
|
})
|
|
}
|
|
|
|
func (sds *snapDevSuite) TestPrerequisitesWithDevelopers(c *C) {
|
|
encoded := strings.Replace(
|
|
sds.validEncoded, sds.developersLines,
|
|
"developers:\n"+
|
|
" -\n developer-id: dev-id2\n since: 2017-01-01T00:00:00.0Z\n"+
|
|
" -\n developer-id: dev-id3\n since: 2017-01-01T00:00:00.0Z\n",
|
|
1)
|
|
assert, err := asserts.Decode([]byte(encoded))
|
|
c.Assert(err, IsNil)
|
|
prereqs := assert.Prerequisites()
|
|
sort.Sort(RefSlice(prereqs))
|
|
c.Assert(prereqs, DeepEquals, []*asserts.Ref{
|
|
{Type: asserts.AccountType, PrimaryKey: []string{"dev-id1"}},
|
|
{Type: asserts.AccountType, PrimaryKey: []string{"dev-id2"}},
|
|
{Type: asserts.AccountType, PrimaryKey: []string{"dev-id3"}},
|
|
{Type: asserts.SnapDeclarationType, PrimaryKey: []string{"16", "snap-id-1"}},
|
|
})
|
|
}
|
|
|
|
func (sds *snapDevSuite) TestPrerequisitesWithDeveloperRepeated(c *C) {
|
|
encoded := strings.Replace(
|
|
sds.validEncoded, sds.developersLines,
|
|
"developers:\n"+
|
|
" -\n developer-id: dev-id2\n since: 2015-01-01T00:00:00.0Z\n until: 2016-01-01T00:00:00.0Z\n"+
|
|
" -\n developer-id: dev-id2\n since: 2017-01-01T00:00:00.0Z\n",
|
|
1)
|
|
assert, err := asserts.Decode([]byte(encoded))
|
|
c.Assert(err, IsNil)
|
|
prereqs := assert.Prerequisites()
|
|
sort.Sort(RefSlice(prereqs))
|
|
c.Assert(prereqs, DeepEquals, []*asserts.Ref{
|
|
{Type: asserts.AccountType, PrimaryKey: []string{"dev-id1"}},
|
|
{Type: asserts.AccountType, PrimaryKey: []string{"dev-id2"}},
|
|
{Type: asserts.SnapDeclarationType, PrimaryKey: []string{"16", "snap-id-1"}},
|
|
})
|
|
}
|
|
|
|
func (sds *snapDevSuite) TestPrerequisitesWithPublisherAsDeveloper(c *C) {
|
|
encoded := strings.Replace(
|
|
sds.validEncoded, sds.developersLines,
|
|
"developers:\n -\n developer-id: dev-id1\n since: 2017-01-01T00:00:00.0Z\n",
|
|
1)
|
|
assert, err := asserts.Decode([]byte(encoded))
|
|
c.Assert(err, IsNil)
|
|
prereqs := assert.Prerequisites()
|
|
sort.Sort(RefSlice(prereqs))
|
|
c.Assert(prereqs, DeepEquals, []*asserts.Ref{
|
|
{Type: asserts.AccountType, PrimaryKey: []string{"dev-id1"}},
|
|
{Type: asserts.SnapDeclarationType, PrimaryKey: []string{"16", "snap-id-1"}},
|
|
})
|
|
}
|
|
|
|
type RefSlice []*asserts.Ref
|
|
|
|
func (s RefSlice) Len() int {
|
|
return len(s)
|
|
}
|
|
|
|
func (s RefSlice) Less(i, j int) bool {
|
|
iref, jref := s[i], s[j]
|
|
if v := strings.Compare(iref.Type.Name, jref.Type.Name); v != 0 {
|
|
return v == -1
|
|
}
|
|
for n, ipk := range iref.PrimaryKey {
|
|
jpk := jref.PrimaryKey[n]
|
|
if v := strings.Compare(ipk, jpk); v != 0 {
|
|
return v == -1
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (s RefSlice) Swap(i, j int) {
|
|
s[i], s[j] = s[j], s[i]
|
|
}
|