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  }