gitee.com/mysnapcore/mysnapd@v0.1.0/asserts/snapasserts/snapasserts.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2022 Canonical Ltd 5 * 6 * This program is free software: you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License version 3 as 8 * published by the Free Software Foundation. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program. If not, see <http://www.gnu.org/licenses/>. 17 * 18 */ 19 20 // Package snapasserts offers helpers to handle snap related assertions and their checking for installation. 21 package snapasserts 22 23 import ( 24 "fmt" 25 26 "gitee.com/mysnapcore/mysnapd/asserts" 27 "gitee.com/mysnapcore/mysnapd/release" 28 "gitee.com/mysnapcore/mysnapd/snap" 29 "gitee.com/mysnapcore/mysnapd/snap/snapfile" 30 ) 31 32 type Finder interface { 33 // Find an assertion based on arbitrary headers. Provided 34 // headers must contain the primary key for the assertion 35 // type. It returns a asserts.NotFoundError if the assertion 36 // cannot be found. 37 Find(assertionType *asserts.AssertionType, headers map[string]string) (asserts.Assertion, error) 38 // FindMany finds assertions based on arbitrary headers. 39 // It returns a NotFoundError if no assertion can be found. 40 FindMany(assertionType *asserts.AssertionType, headers map[string]string) ([]asserts.Assertion, error) 41 } 42 43 func findSnapDeclaration(snapID, name string, db Finder) (*asserts.SnapDeclaration, error) { 44 a, err := db.Find(asserts.SnapDeclarationType, map[string]string{ 45 "series": release.Series, 46 "snap-id": snapID, 47 }) 48 if err != nil { 49 return nil, fmt.Errorf("internal error: cannot find snap declaration for %q: %s", name, snapID) 50 } 51 snapDecl := a.(*asserts.SnapDeclaration) 52 53 if snapDecl.SnapName() == "" { 54 return nil, fmt.Errorf("cannot install snap %q with a revoked snap declaration", name) 55 } 56 57 return snapDecl, nil 58 } 59 60 // CrossCheck tries to cross check the instance name, hash digest, provenance 61 // and size of a snap plus its metadata in a SideInfo with the relevant 62 // snap assertions in a database that should have been populated with 63 // them. 64 // The optional model assertion must be passed to have full cross 65 // checks in the case of delegated authority snap-revisions before 66 // installing a snap. 67 // It returns the corresponding cross-checked snap-revision. 68 // Ultimately the provided provenance (if not default) must be checked 69 // with the provenance in the snap metadata by the caller as well, if 70 // the provided provenance was not read safely from there already. 71 func CrossCheck(instanceName, snapSHA3_384, provenance string, snapSize uint64, si *snap.SideInfo, model *asserts.Model, db Finder) (snapRev *asserts.SnapRevision, err error) { 72 // get relevant assertions and do cross checks 73 headers := map[string]string{ 74 "snap-sha3-384": snapSHA3_384, 75 } 76 if provenance != "" { 77 headers["provenance"] = provenance 78 } 79 a, err := db.Find(asserts.SnapRevisionType, headers) 80 if err != nil { 81 provInf := "" 82 if provenance != "" { 83 provInf = fmt.Sprintf(" provenance: %s", provenance) 84 } 85 return nil, fmt.Errorf("internal error: cannot find pre-populated snap-revision assertion for %q: %s%s", instanceName, snapSHA3_384, provInf) 86 } 87 snapRev = a.(*asserts.SnapRevision) 88 89 if snapRev.SnapSize() != snapSize { 90 return nil, fmt.Errorf("snap %q file does not have expected size according to signatures (download is broken or tampered): %d != %d", instanceName, snapSize, snapRev.SnapSize()) 91 } 92 93 snapID := si.SnapID 94 95 if snapRev.SnapID() != snapID || snapRev.SnapRevision() != si.Revision.N { 96 return nil, fmt.Errorf("snap %q does not have expected ID or revision according to assertions (metadata is broken or tampered): %s / %s != %d / %s", instanceName, si.Revision, snapID, snapRev.SnapRevision(), snapRev.SnapID()) 97 } 98 99 snapDecl, err := findSnapDeclaration(snapID, instanceName, db) 100 if err != nil { 101 return nil, err 102 } 103 104 if snapDecl.SnapName() != snap.InstanceSnap(instanceName) { 105 return nil, fmt.Errorf("cannot install %q, snap %q is undergoing a rename to %q", instanceName, snap.InstanceSnap(instanceName), snapDecl.SnapName()) 106 } 107 108 if _, err := CrossCheckProvenance(instanceName, snapRev, snapDecl, model, db); err != nil { 109 return nil, err 110 } 111 112 return snapRev, nil 113 } 114 115 // CrossCheckProvenance tries to cross check the given snap-revision 116 // if it has a non default provenance with the revision-authority 117 // constraints of the given snap-declaration including any device 118 // scope constraints using model (and implied store). 119 // It also returns the provenance if it is different from the default. 120 // Ultimately if not default the provenance must also be checked 121 // with the provenance in the snap metadata by the caller. 122 func CrossCheckProvenance(instanceName string, snapRev *asserts.SnapRevision, snapDecl *asserts.SnapDeclaration, model *asserts.Model, db Finder) (signedProvenance string, err error) { 123 if snapRev.Provenance() == "global-upload" { 124 // nothing to check 125 return "", nil 126 } 127 var store *asserts.Store 128 if model != nil && model.Store() != "" { 129 a, err := db.Find(asserts.StoreType, map[string]string{ 130 "store": model.Store(), 131 }) 132 if err != nil && !asserts.IsNotFound(err) { 133 return "", err 134 } 135 if a != nil { 136 store = a.(*asserts.Store) 137 } 138 } 139 ras := snapDecl.RevisionAuthority(snapRev.Provenance()) 140 matchingRevAuthority := false 141 for _, ra := range ras { 142 if err := ra.Check(snapRev, model, store); err == nil { 143 matchingRevAuthority = true 144 break 145 } 146 } 147 if !matchingRevAuthority { 148 return "", fmt.Errorf("snap %q revision assertion with provenance %q is not signed by an authority authorized on this device: %s", instanceName, snapRev.Provenance(), snapRev.AuthorityID()) 149 } 150 return snapRev.Provenance(), nil 151 } 152 153 // CheckProvenanceWithVerifiedRevision checks that the given snap has 154 // the same provenance as of the provided snap-revision. 155 // It is intended to be called safely on snaps for which a matching 156 // and authorized snap-revision has been already found and cross-checked. 157 // Its purpose is to check that a blob has not been re-signed under an 158 // inappropriate provenance. 159 func CheckProvenanceWithVerifiedRevision(snapPath string, verifiedRev *asserts.SnapRevision) error { 160 snapf, err := snapfile.Open(snapPath) 161 if err != nil { 162 return err 163 } 164 info, err := snap.ReadInfoFromSnapFile(snapf, nil) 165 if err != nil { 166 return err 167 } 168 if verifiedRev.Provenance() != info.Provenance() { 169 return fmt.Errorf("snap %q has been signed under provenance %q different from the metadata one: %q", snapPath, verifiedRev.Provenance(), info.Provenance()) 170 } 171 return nil 172 } 173 174 // DeriveSideInfo tries to construct a SideInfo for the given snap 175 // using its digest to find the relevant snap assertions with the 176 // information in the given database. It will fail with an 177 // asserts.NotFoundError if it cannot find them. 178 // model is used to cross check that the found snap-revision is applicable 179 // on the device. 180 func DeriveSideInfo(snapPath string, model *asserts.Model, db Finder) (*snap.SideInfo, error) { 181 snapSHA3_384, snapSize, err := asserts.SnapFileSHA3_384(snapPath) 182 if err != nil { 183 return nil, err 184 } 185 186 return DeriveSideInfoFromDigestAndSize(snapPath, snapSHA3_384, snapSize, model, db) 187 } 188 189 // DeriveSideInfoFromDigestAndSize tries to construct a SideInfo 190 // using digest and size as provided for the snap to find the relevant 191 // snap assertions with the information in the given database. It will 192 // fail with an asserts.NotFoundError if it cannot find them. 193 // model is used to cross check that the found snap-revision is applicable 194 // on the device. 195 func DeriveSideInfoFromDigestAndSize(snapPath string, snapSHA3_384 string, snapSize uint64, model *asserts.Model, db Finder) (*snap.SideInfo, error) { 196 // get relevant assertions and reconstruct metadata 197 headers := map[string]string{ 198 "snap-sha3-384": snapSHA3_384, 199 } 200 a, err := db.Find(asserts.SnapRevisionType, headers) 201 if err != nil && !asserts.IsNotFound(err) { 202 return nil, err 203 } 204 if a == nil { 205 // non-default provenance? 206 cands, err := db.FindMany(asserts.SnapRevisionType, headers) 207 if err != nil { 208 return nil, err 209 } 210 if len(cands) != 1 { 211 return nil, fmt.Errorf("safely handling snaps with different provenance but same hash not yet supported") 212 } 213 a = cands[0] 214 } 215 216 snapRev := a.(*asserts.SnapRevision) 217 218 if snapRev.SnapSize() != snapSize { 219 return nil, fmt.Errorf("snap %q does not have expected size according to signatures (broken or tampered): %d != %d", snapPath, snapSize, snapRev.SnapSize()) 220 } 221 222 snapID := snapRev.SnapID() 223 224 snapDecl, err := findSnapDeclaration(snapID, snapPath, db) 225 if err != nil { 226 return nil, err 227 } 228 229 if _, err = CrossCheckProvenance(snapDecl.SnapName(), snapRev, snapDecl, model, db); err != nil { 230 return nil, err 231 } 232 233 if err := CheckProvenanceWithVerifiedRevision(snapPath, snapRev); err != nil { 234 return nil, err 235 } 236 237 return SideInfoFromSnapAssertions(snapDecl, snapRev), nil 238 } 239 240 // SideInfoFromSnapAssertions returns a *snap.SideInfo reflecting the given snap assertions. 241 func SideInfoFromSnapAssertions(snapDecl *asserts.SnapDeclaration, snapRev *asserts.SnapRevision) *snap.SideInfo { 242 return &snap.SideInfo{ 243 RealName: snapDecl.SnapName(), 244 SnapID: snapDecl.SnapID(), 245 Revision: snap.R(snapRev.SnapRevision()), 246 } 247 } 248 249 // FetchSnapAssertions fetches the assertions matching the snap file digest and optional provenance using the given fetcher. 250 func FetchSnapAssertions(f asserts.Fetcher, snapSHA3_384, provenance string) error { 251 // for now starting from the snap-revision will get us all other relevant assertions 252 ref := &asserts.Ref{ 253 Type: asserts.SnapRevisionType, 254 PrimaryKey: []string{snapSHA3_384}, 255 } 256 if provenance != "" { 257 ref.PrimaryKey = append(ref.PrimaryKey, provenance) 258 } 259 260 return f.Fetch(ref) 261 } 262 263 // FetchSnapDeclaration fetches the snap declaration and its prerequisites for the given snap id using the given fetcher. 264 func FetchSnapDeclaration(f asserts.Fetcher, snapID string) error { 265 ref := &asserts.Ref{ 266 Type: asserts.SnapDeclarationType, 267 PrimaryKey: []string{release.Series, snapID}, 268 } 269 270 return f.Fetch(ref) 271 } 272 273 // FetchStore fetches the store assertion and its prerequisites for the given store id using the given fetcher. 274 func FetchStore(f asserts.Fetcher, storeID string) error { 275 ref := &asserts.Ref{ 276 Type: asserts.StoreType, 277 PrimaryKey: []string{storeID}, 278 } 279 280 return f.Fetch(ref) 281 }