github.com/blixtra/rkt@v0.8.1-0.20160204105720-ab0d1add1a43/rkt/pubkey/pubkey.go (about) 1 // Copyright 2015 The rkt Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package pubkey 16 17 import ( 18 "bufio" 19 "errors" 20 "fmt" 21 "io" 22 "io/ioutil" 23 "net/http" 24 "net/url" 25 "os" 26 "strings" 27 28 "github.com/coreos/rkt/pkg/keystore" 29 rktlog "github.com/coreos/rkt/pkg/log" 30 "github.com/coreos/rkt/rkt/config" 31 "github.com/hashicorp/errwrap" 32 33 "github.com/appc/spec/discovery" 34 "golang.org/x/crypto/openpgp" 35 "golang.org/x/crypto/ssh/terminal" 36 ) 37 38 type Manager struct { 39 AuthPerHost map[string]config.Headerer 40 InsecureAllowHTTP bool 41 TrustKeysFromHTTPS bool 42 Ks *keystore.Keystore 43 Debug bool 44 } 45 46 type AcceptOption int 47 48 const ( 49 AcceptForce AcceptOption = iota 50 AcceptAsk 51 ) 52 53 var log *rktlog.Logger 54 var stdout *rktlog.Logger = rktlog.New(os.Stdout, "", false) 55 56 func ensureLogger(debug bool) { 57 if log == nil { 58 log = rktlog.New(os.Stderr, "pubkey", debug) 59 } 60 } 61 62 // GetPubKeyLocations discovers one location at prefix 63 func (m *Manager) GetPubKeyLocations(prefix string) ([]string, error) { 64 ensureLogger(m.Debug) 65 if prefix == "" { 66 return nil, fmt.Errorf("empty prefix") 67 } 68 69 kls, err := m.metaDiscoverPubKeyLocations(prefix) 70 if err != nil { 71 return nil, errwrap.Wrap(errors.New("prefix meta discovery error"), err) 72 } 73 74 if len(kls) == 0 { 75 return nil, fmt.Errorf("meta discovery on %s resulted in no keys", prefix) 76 } 77 78 return kls, nil 79 } 80 81 // AddKeys adds the keys listed in pkls at prefix 82 func (m *Manager) AddKeys(pkls []string, prefix string, accept AcceptOption) error { 83 ensureLogger(m.Debug) 84 if m.Ks == nil { 85 return fmt.Errorf("no keystore available to add keys to") 86 } 87 88 for _, pkl := range pkls { 89 u, err := url.Parse(pkl) 90 if err != nil { 91 return err 92 } 93 pk, err := m.getPubKey(u) 94 if err != nil { 95 return errwrap.Wrap(fmt.Errorf("error accessing the key %s", pkl), err) 96 } 97 defer pk.Close() 98 99 err = displayKey(prefix, pkl, pk) 100 if err != nil { 101 return errwrap.Wrap(fmt.Errorf("error displaying the key %s", pkl), err) 102 } 103 104 if m.TrustKeysFromHTTPS && u.Scheme == "https" { 105 accept = AcceptForce 106 } 107 108 if accept == AcceptAsk { 109 if !terminal.IsTerminal(int(os.Stdin.Fd())) || !terminal.IsTerminal(int(os.Stderr.Fd())) { 110 log.Printf("To trust the key for %q, do one of the following:", prefix) 111 log.Printf(" - call rkt with --trust-keys-from-https") 112 log.Printf(" - run: rkt trust --prefix %q", prefix) 113 return fmt.Errorf("error reviewing key: unable to ask user to review fingerprint due to lack of tty") 114 } 115 accepted, err := reviewKey() 116 if err != nil { 117 return errwrap.Wrap(errors.New("error reviewing key"), err) 118 } 119 if !accepted { 120 log.Printf("not trusting %q", pkl) 121 continue 122 } 123 } 124 125 if accept == AcceptForce { 126 stdout.Printf("Trusting %q for prefix %q without fingerprint review.", pkl, prefix) 127 } else { 128 stdout.Printf("Trusting %q for prefix %q after fingerprint review.", pkl, prefix) 129 } 130 131 if prefix == "" { 132 path, err := m.Ks.StoreTrustedKeyRoot(pk) 133 if err != nil { 134 return errwrap.Wrap(errors.New("error adding root key"), err) 135 } 136 stdout.Printf("Added root key at %q", path) 137 } else { 138 path, err := m.Ks.StoreTrustedKeyPrefix(prefix, pk) 139 if err != nil { 140 return errwrap.Wrap(fmt.Errorf("error adding key for prefix %q", prefix), err) 141 } 142 stdout.Printf("Added key for prefix %q at %q", prefix, path) 143 } 144 } 145 return nil 146 } 147 148 // metaDiscoverPubKeyLocations discovers the public key through ACDiscovery by applying prefix as an ACApp 149 func (m *Manager) metaDiscoverPubKeyLocations(prefix string) ([]string, error) { 150 app, err := discovery.NewAppFromString(prefix) 151 if err != nil { 152 return nil, err 153 } 154 155 hostHeaders := config.ResolveAuthPerHost(m.AuthPerHost) 156 insecure := discovery.InsecureNone 157 if m.InsecureAllowHTTP { 158 insecure = insecure | discovery.InsecureHttp 159 } 160 ep, attempts, err := discovery.DiscoverPublicKeys(*app, hostHeaders, insecure) 161 if err != nil { 162 return nil, err 163 } 164 165 if m.Debug { 166 for _, a := range attempts { 167 log.PrintE(fmt.Sprintf("meta tag 'ac-discovery-pubkeys' not found on %s", a.Prefix), a.Error) 168 } 169 } 170 171 return ep.Keys, nil 172 } 173 174 // getPubKey retrieves a public key (if remote), and verifies it's a gpg key 175 func (m *Manager) getPubKey(u *url.URL) (*os.File, error) { 176 switch u.Scheme { 177 case "": 178 return os.Open(u.Path) 179 case "http": 180 if !m.InsecureAllowHTTP { 181 return nil, fmt.Errorf("--insecure-allow-http required for http URLs") 182 } 183 fallthrough 184 case "https": 185 return downloadKey(u) 186 } 187 188 return nil, fmt.Errorf("only local files and http or https URLs supported") 189 } 190 191 // downloadKey retrieves the file, storing it in a deleted tempfile 192 func downloadKey(u *url.URL) (*os.File, error) { 193 tf, err := ioutil.TempFile("", "") 194 if err != nil { 195 return nil, errwrap.Wrap(errors.New("error creating tempfile"), err) 196 } 197 os.Remove(tf.Name()) // no need to keep the tempfile around 198 199 defer func() { 200 if tf != nil { 201 tf.Close() 202 } 203 }() 204 205 // TODO(krnowak): we should probably apply credential headers 206 // from config here 207 res, err := http.Get(u.String()) 208 if err != nil { 209 return nil, errwrap.Wrap(errors.New("error getting key"), err) 210 } 211 defer res.Body.Close() 212 213 if res.StatusCode != http.StatusOK { 214 return nil, fmt.Errorf("bad HTTP status code: %d", res.StatusCode) 215 } 216 217 if _, err := io.Copy(tf, res.Body); err != nil { 218 return nil, errwrap.Wrap(errors.New("error copying key"), err) 219 } 220 221 if _, err = tf.Seek(0, os.SEEK_SET); err != nil { 222 return nil, errwrap.Wrap(errors.New("error seeking"), err) 223 } 224 225 retTf := tf 226 tf = nil 227 return retTf, nil 228 } 229 230 // displayKey shows the key summary 231 func displayKey(prefix, location string, key *os.File) error { 232 defer key.Seek(0, os.SEEK_SET) 233 234 kr, err := openpgp.ReadArmoredKeyRing(key) 235 if err != nil { 236 return errwrap.Wrap(errors.New("error reading key"), err) 237 } 238 239 log.Printf("prefix: %q\nkey: %q", prefix, location) 240 for _, k := range kr { 241 stdout.Printf("gpg key fingerprint is: %s", fingerToString(k.PrimaryKey.Fingerprint)) 242 for _, sk := range k.Subkeys { 243 stdout.Printf(" Subkey fingerprint: %s", fingerToString(sk.PublicKey.Fingerprint)) 244 } 245 for n, _ := range k.Identities { 246 stdout.Printf("\t%s", n) 247 } 248 } 249 return nil 250 } 251 252 // reviewKey asks the user to accept the key 253 func reviewKey() (bool, error) { 254 in := bufio.NewReader(os.Stdin) 255 for { 256 stdout.Printf("Are you sure you want to trust this key (yes/no)?") 257 input, err := in.ReadString('\n') 258 if err != nil { 259 return false, errwrap.Wrap(errors.New("error reading input"), err) 260 } 261 switch input { 262 case "yes\n": 263 return true, nil 264 case "no\n": 265 return false, nil 266 default: 267 stdout.Printf("Please enter 'yes' or 'no'") 268 } 269 } 270 } 271 272 func fingerToString(fpr [20]byte) string { 273 str := "" 274 for i, b := range fpr { 275 if i > 0 && i%2 == 0 { 276 str += " " 277 if i == 10 { 278 str += " " 279 } 280 } 281 str += strings.ToUpper(fmt.Sprintf("%.2x", b)) 282 } 283 return str 284 }