gopkg.in/ubuntu-core/snappy.v0@v0.0.0-20210902073436-25a8614f10a6/asserts/signtool/sign.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2016 Canonical Ltd 5 * 6 * This program is free software: you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License version 3 as 8 * published by the Free Software Foundation. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program. If not, see <http://www.gnu.org/licenses/>. 17 * 18 */ 19 20 // Package signtool offers tooling to sign assertions. 21 package signtool 22 23 import ( 24 "encoding/json" 25 "fmt" 26 27 "github.com/snapcore/snapd/asserts" 28 ) 29 30 // Options specifies the complete input for signing an assertion. 31 type Options struct { 32 // KeyID specifies the key id of the key to use 33 KeyID string 34 35 // Statement is used as input to construct the assertion 36 // it's a mapping encoded as JSON 37 // of the header fields of the assertion 38 // plus an optional pseudo-header "body" to specify 39 // the body of the assertion 40 Statement []byte 41 42 // Complement specifies complementary headers to what is in 43 // Statement, for use by tools that fill-in/compute some of 44 // the headers. Headers appearing both in Statement and 45 // Complement are an error, except for "type" that needs 46 // instead to match if present. Pseudo-header "body" can also 47 // be specified here. 48 Complement map[string]interface{} 49 } 50 51 // Sign produces the text of a signed assertion as specified by opts. 52 func Sign(opts *Options, keypairMgr asserts.KeypairManager) ([]byte, error) { 53 var headers map[string]interface{} 54 err := json.Unmarshal(opts.Statement, &headers) 55 if err != nil { 56 return nil, fmt.Errorf("cannot parse the assertion input as JSON: %v", err) 57 } 58 59 for name, value := range opts.Complement { 60 if v, ok := headers[name]; ok { 61 if name == "type" { 62 if v != value { 63 return nil, fmt.Errorf("repeated assertion type does not match") 64 } 65 } else { 66 return nil, fmt.Errorf("complementary header %q clashes with assertion input", name) 67 } 68 } 69 headers[name] = value 70 } 71 72 typCand, ok := headers["type"] 73 if !ok { 74 return nil, fmt.Errorf("missing assertion type header") 75 } 76 typStr, ok := typCand.(string) 77 if !ok { 78 return nil, fmt.Errorf("assertion type must be a string, not: %v", typCand) 79 } 80 typ := asserts.Type(typStr) 81 if typ == nil { 82 return nil, fmt.Errorf("invalid assertion type: %v", headers["type"]) 83 } 84 85 var body []byte 86 if bodyCand, ok := headers["body"]; ok { 87 bodyStr, ok := bodyCand.(string) 88 if !ok { 89 return nil, fmt.Errorf("body if specified must be a string") 90 } 91 body = []byte(bodyStr) 92 delete(headers, "body") 93 } 94 95 adb, err := asserts.OpenDatabase(&asserts.DatabaseConfig{ 96 KeypairManager: keypairMgr, 97 }) 98 if err != nil { 99 return nil, err 100 } 101 102 // TODO: teach Sign to cross check keyID and authority-id 103 // against an account-key 104 a, err := adb.Sign(typ, headers, body, opts.KeyID) 105 if err != nil { 106 return nil, err 107 } 108 109 return asserts.Encode(a), nil 110 }