github.com/ubuntu-core/snappy@v0.0.0-20210827154228-9e584df982bb/asserts/extkeypairmgr.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2021 Canonical Ltd 5 * 6 * This program is free software: you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License version 3 as 8 * published by the Free Software Foundation. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program. If not, see <http://www.gnu.org/licenses/>. 17 * 18 */ 19 20 package asserts 21 22 import ( 23 "bytes" 24 "crypto" 25 "crypto/rsa" 26 "crypto/x509" 27 "encoding/json" 28 "fmt" 29 "io" 30 "os/exec" 31 32 "golang.org/x/crypto/openpgp/packet" 33 34 "github.com/snapcore/snapd/strutil" 35 ) 36 37 type ExternalKeyInfo struct { 38 Name string 39 ID string 40 } 41 42 // ExternalKeypairManager is key pair manager implemented via an external program interface. 43 // TODO: points to interface docs 44 type ExternalKeypairManager struct { 45 keyMgrPath string 46 nameToID map[string]string 47 cache map[string]*cachedExtKey 48 } 49 50 // NewExternalKeypairManager creates a new ExternalKeypairManager using the program at keyMgrPath. 51 func NewExternalKeypairManager(keyMgrPath string) (*ExternalKeypairManager, error) { 52 em := &ExternalKeypairManager{ 53 keyMgrPath: keyMgrPath, 54 nameToID: make(map[string]string), 55 cache: make(map[string]*cachedExtKey), 56 } 57 if err := em.checkFeatures(); err != nil { 58 return nil, err 59 } 60 return em, nil 61 } 62 63 func (em *ExternalKeypairManager) keyMgr(op string, args []string, in []byte, out interface{}) error { 64 args = append([]string{op}, args...) 65 cmd := exec.Command(em.keyMgrPath, args...) 66 var outBuf bytes.Buffer 67 var errBuf bytes.Buffer 68 69 if len(in) != 0 { 70 cmd.Stdin = bytes.NewBuffer(in) 71 } 72 cmd.Stdout = &outBuf 73 cmd.Stderr = &errBuf 74 75 if err := cmd.Run(); err != nil { 76 return fmt.Errorf("external keypair manager %q %v failed: %v (%q)", em.keyMgrPath, args, err, errBuf.Bytes()) 77 78 } 79 switch o := out.(type) { 80 case *[]byte: 81 *o = outBuf.Bytes() 82 default: 83 if err := json.Unmarshal(outBuf.Bytes(), out); err != nil { 84 return fmt.Errorf("cannot decode external keypair manager %q %v output: %v", em.keyMgrPath, args, err) 85 } 86 } 87 return nil 88 } 89 90 func (em *ExternalKeypairManager) checkFeatures() error { 91 var feats struct { 92 Signing []string `json:"signing"` 93 PublicKeys []string `json:"public-keys"` 94 } 95 if err := em.keyMgr("features", nil, nil, &feats); err != nil { 96 return err 97 } 98 if !strutil.ListContains(feats.Signing, "RSA-PKCS") { 99 return fmt.Errorf("external keypair manager %q missing support for RSA-PKCS signing", em.keyMgrPath) 100 } 101 if !strutil.ListContains(feats.PublicKeys, "DER") { 102 return fmt.Errorf("external keypair manager %q missing support for public key DER output format", em.keyMgrPath) 103 } 104 return nil 105 } 106 107 func (em *ExternalKeypairManager) keyNames() ([]string, error) { 108 var knames struct { 109 Names []string `json:"key-names"` 110 } 111 if err := em.keyMgr("key-names", nil, nil, &knames); err != nil { 112 return nil, fmt.Errorf("cannot get all external keypair manager key names: %v", err) 113 } 114 return knames.Names, nil 115 } 116 117 func (em *ExternalKeypairManager) findByName(name string) (PublicKey, *rsa.PublicKey, error) { 118 var k []byte 119 err := em.keyMgr("get-public-key", []string{"-f", "DER", "-k", name}, nil, &k) 120 if err != nil { 121 return nil, nil, fmt.Errorf("cannot find external key: %v", err) 122 } 123 pubk, err := x509.ParsePKIXPublicKey(k) 124 if err != nil { 125 return nil, nil, fmt.Errorf("cannot decode external key %q: %v", name, err) 126 } 127 rsaPub, ok := pubk.(*rsa.PublicKey) 128 if !ok { 129 return nil, nil, fmt.Errorf("expected RSA public key, got instead: %T", pubk) 130 } 131 pubKey := RSAPublicKey(rsaPub) 132 return pubKey, rsaPub, nil 133 } 134 135 func (em *ExternalKeypairManager) Export(keyName string) ([]byte, error) { 136 pubKey, _, err := em.findByName(keyName) 137 if err != nil { 138 return nil, err 139 } 140 return EncodePublicKey(pubKey) 141 } 142 143 func (em *ExternalKeypairManager) loadKey(name string) (*cachedExtKey, error) { 144 id, ok := em.nameToID[name] 145 if ok { 146 return em.cache[id], nil 147 } 148 pubKey, rsaPub, err := em.findByName(name) 149 if err != nil { 150 return nil, err 151 } 152 id = pubKey.ID() 153 em.nameToID[name] = id 154 cachedKey := &cachedExtKey{ 155 pubKey: pubKey, 156 signer: &extSigner{ 157 keyName: name, 158 rsaPub: rsaPub, 159 // signWith is filled later 160 }, 161 } 162 em.cache[id] = cachedKey 163 return cachedKey, nil 164 } 165 166 func (em *ExternalKeypairManager) privateKey(cachedKey *cachedExtKey) PrivateKey { 167 if cachedKey.privKey == nil { 168 extSigner := cachedKey.signer 169 // fill signWith 170 extSigner.signWith = em.signWith 171 signer := packet.NewSignerPrivateKey(v1FixedTimestamp, extSigner) 172 signk := openpgpPrivateKey{privk: signer} 173 extKey := &extPGPPrivateKey{ 174 pubKey: cachedKey.pubKey, 175 from: fmt.Sprintf("external keypair manager %q", em.keyMgrPath), 176 externalID: extSigner.keyName, 177 bitLen: extSigner.rsaPub.N.BitLen(), 178 doSign: signk.sign, 179 } 180 cachedKey.privKey = extKey 181 } 182 return cachedKey.privKey 183 } 184 185 func (em *ExternalKeypairManager) GetByName(keyName string) (PrivateKey, error) { 186 cachedKey, err := em.loadKey(keyName) 187 if err != nil { 188 return nil, err 189 } 190 return em.privateKey(cachedKey), nil 191 } 192 193 // ExternalUnsupportedOpError represents the error situation of operations 194 // that are not supported/mediated via ExternalKeypairManager. 195 type ExternalUnsupportedOpError struct { 196 msg string 197 } 198 199 func (euoe *ExternalUnsupportedOpError) Error() string { 200 return euoe.msg 201 } 202 203 func (em *ExternalKeypairManager) Put(privKey PrivateKey) error { 204 return &ExternalUnsupportedOpError{"cannot import private key into external keypair manager"} 205 } 206 207 func (em *ExternalKeypairManager) Delete(keyName string) error { 208 return &ExternalUnsupportedOpError{"no support to delete external keypair manager keys"} 209 } 210 211 func (em *ExternalKeypairManager) Generate(keyName string) error { 212 return &ExternalUnsupportedOpError{"no support to mediate generating an external keypair manager key"} 213 } 214 215 func (em *ExternalKeypairManager) loadAllKeys() ([]string, error) { 216 names, err := em.keyNames() 217 if err != nil { 218 return nil, err 219 } 220 for _, name := range names { 221 if _, err := em.loadKey(name); err != nil { 222 return nil, err 223 } 224 } 225 return names, nil 226 } 227 228 func (em *ExternalKeypairManager) Get(keyID string) (PrivateKey, error) { 229 cachedKey, ok := em.cache[keyID] 230 if !ok { 231 // try to load all keys 232 if _, err := em.loadAllKeys(); err != nil { 233 return nil, err 234 } 235 cachedKey, ok = em.cache[keyID] 236 if !ok { 237 return nil, fmt.Errorf("cannot find external key with id %q", keyID) 238 } 239 } 240 return em.privateKey(cachedKey), nil 241 } 242 243 func (em *ExternalKeypairManager) List() ([]ExternalKeyInfo, error) { 244 names, err := em.loadAllKeys() 245 if err != nil { 246 return nil, err 247 } 248 res := make([]ExternalKeyInfo, len(names)) 249 for i, name := range names { 250 res[i].Name = name 251 res[i].ID = em.cache[em.nameToID[name]].pubKey.ID() 252 } 253 return res, nil 254 } 255 256 // see https://datatracker.ietf.org/doc/html/rfc2313 and more recently 257 // and more precisely about SHA-512: 258 // https://datatracker.ietf.org/doc/html/rfc3447#section-9.2 Notes 1. 259 var digestInfoSHA512Prefix = []byte{0x30, 0x51, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, 0x05, 0x00, 0x04, 0x40} 260 261 func (em *ExternalKeypairManager) signWith(keyName string, digest []byte) (signature []byte, err error) { 262 // wrap the digest into the needed DigestInfo, the RSA-PKCS 263 // mechanism or equivalent is expected not to do this on its 264 // own 265 toSign := &bytes.Buffer{} 266 toSign.Write(digestInfoSHA512Prefix) 267 toSign.Write(digest) 268 269 err = em.keyMgr("sign", []string{"-m", "RSA-PKCS", "-k", keyName}, toSign.Bytes(), &signature) 270 if err != nil { 271 return nil, err 272 } 273 return signature, nil 274 } 275 276 type cachedExtKey struct { 277 pubKey PublicKey 278 signer *extSigner 279 privKey PrivateKey 280 } 281 282 type extSigner struct { 283 keyName string 284 rsaPub *rsa.PublicKey 285 signWith func(keyName string, digest []byte) (signature []byte, err error) 286 } 287 288 func (es *extSigner) Public() crypto.PublicKey { 289 return es.rsaPub 290 } 291 292 func (es *extSigner) Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) (signature []byte, err error) { 293 if opts.HashFunc() != crypto.SHA512 { 294 return nil, fmt.Errorf("unexpected pgp signature digest") 295 } 296 297 return es.signWith(es.keyName, digest) 298 }