github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/libkb/gpg_index.go (about)

     1  // Copyright 2015 Keybase, Inc. All rights reserved. Use of
     2  // this source code is governed by the included BSD license.
     3  
     4  package libkb
     5  
     6  import (
     7  	"bufio"
     8  	"fmt"
     9  	"io"
    10  	"sort"
    11  	"strconv"
    12  	"strings"
    13  	"time"
    14  
    15  	keybase1 "github.com/keybase/client/go/protocol/keybase1"
    16  	"github.com/keybase/go-crypto/openpgp/packet"
    17  )
    18  
    19  // =============================================================================
    20  
    21  type BucketDict struct {
    22  	d map[string][]*GpgPrimaryKey
    23  }
    24  
    25  func NewBuckDict() *BucketDict {
    26  	return &BucketDict{
    27  		d: make(map[string][]*GpgPrimaryKey),
    28  	}
    29  }
    30  
    31  func (bd *BucketDict) Add(k string, v *GpgPrimaryKey) {
    32  	k = strings.ToLower(k)
    33  	bd.d[k] = append(bd.d[k], v)
    34  }
    35  
    36  func (bd BucketDict) Get(k string) []*GpgPrimaryKey {
    37  	k = strings.ToLower(k)
    38  	ret, found := bd.d[k]
    39  	if !found {
    40  		ret = nil
    41  	}
    42  	return ret
    43  }
    44  
    45  func (bd BucketDict) Get0Or1(k string) (ret *GpgPrimaryKey, err error) {
    46  	v := bd.Get(k)
    47  	if len(v) > 1 {
    48  		err = GpgError{fmt.Sprintf("Wanted a unique lookup but got %d objects for key %s", len(v), k)}
    49  	} else if len(v) == 1 {
    50  		ret = v[0]
    51  	}
    52  	return
    53  }
    54  
    55  // =============================================================================
    56  
    57  func Uniquify(inp []string) []string {
    58  	m := make(map[string]bool)
    59  	for _, s := range inp {
    60  		m[strings.ToLower(s)] = true
    61  	}
    62  	ret := make([]string, 0, len(m))
    63  	for k := range m {
    64  		ret = append(ret, k)
    65  	}
    66  	return ret
    67  }
    68  
    69  // =============================================================================
    70  
    71  type GpgBaseKey struct {
    72  	Type        string
    73  	Trust       string
    74  	Bits        int
    75  	Algo        int
    76  	ID64        string
    77  	Created     int64
    78  	Expires     int64
    79  	fingerprint *PGPFingerprint
    80  }
    81  
    82  func (k GpgBaseKey) AlgoString() string {
    83  	switch packet.PublicKeyAlgorithm(k.Algo) {
    84  	case packet.PubKeyAlgoDSA:
    85  		return "D"
    86  	case packet.PubKeyAlgoRSA:
    87  		return "R"
    88  	case packet.PubKeyAlgoECDSA:
    89  		return "E"
    90  	default:
    91  		return "?"
    92  	}
    93  }
    94  
    95  func (k GpgBaseKey) ExpirationString() string {
    96  	if k.Expires == 0 {
    97  		return "never"
    98  	}
    99  	layout := "2006-01-02"
   100  	return time.Unix(k.Expires, 0).Format(layout)
   101  }
   102  
   103  func (k GpgBaseKey) CreatedString() string {
   104  	layout := "2006-01-02"
   105  	return time.Unix(k.Created, 0).Format(layout)
   106  }
   107  
   108  func (k *GpgBaseKey) ParseBase(line *GpgIndexLine) (err error) {
   109  	if line.Len() < 12 {
   110  		err = GpgIndexError{line.lineno, "Not enough fields (need 12)"}
   111  		return
   112  	}
   113  
   114  	k.Type = line.At(0)
   115  	k.Trust = line.At(1)
   116  	k.ID64 = line.At(4)
   117  
   118  	parseTimeStamp := func(s string) (ret int64, err error) {
   119  		// No date was specified
   120  		if len(s) == 0 {
   121  			return
   122  		}
   123  		// GPG 2.0+ format
   124  		if ret, err = strconv.ParseInt(s, 10, 0); err == nil {
   125  			return
   126  		}
   127  		var tmp time.Time
   128  		if tmp, err = time.Parse("2006-01-02", s); err != nil {
   129  			return
   130  		}
   131  		ret = tmp.Unix()
   132  		return
   133  	}
   134  
   135  	if k.Bits, err = strconv.Atoi(line.At(2)); err != nil {
   136  		return
   137  	}
   138  	if k.Algo, err = strconv.Atoi(line.At(3)); err != nil {
   139  		return
   140  	}
   141  	if k.Created, err = parseTimeStamp(line.At(5)); err != nil {
   142  		return
   143  	}
   144  	if k.Expires, err = parseTimeStamp(line.At(6)); err != nil {
   145  		return
   146  	}
   147  
   148  	return
   149  }
   150  
   151  // =============================================================================
   152  
   153  type GpgFingerprinter interface {
   154  	SetFingerprint(pgp *PGPFingerprint)
   155  }
   156  
   157  type GpgPrimaryKey struct {
   158  	Contextified
   159  	GpgBaseKey
   160  	subkeys    []*GpgSubKey
   161  	identities []*Identity
   162  	top        GpgFingerprinter
   163  }
   164  
   165  func (k *GpgPrimaryKey) IsValid(mctx MetaContext) bool {
   166  	if k == nil {
   167  		return false
   168  	}
   169  	if k.Trust == "r" {
   170  		return false
   171  	} else if k.Expires == 0 {
   172  		return true
   173  	}
   174  	expiresAt := time.Unix(k.Expires, 0)
   175  	expired := time.Now().After(expiresAt)
   176  	if expired {
   177  		var fp string
   178  		if k.fingerprint != nil {
   179  			fp = " (" + k.fingerprint.ToQuads() + ")"
   180  		}
   181  		mctx.Warning("Skipping expired primary key%s, expiration: %s", fp, expiresAt)
   182  	}
   183  	return !expired
   184  }
   185  
   186  func (k *GpgPrimaryKey) ToRow(i int) []string {
   187  	v := []string{
   188  		fmt.Sprintf("(%d)", i),
   189  		fmt.Sprintf("%d%s", k.Bits, k.AlgoString()),
   190  		k.fingerprint.ToKeyID(),
   191  		k.ExpirationString(),
   192  	}
   193  	for _, i := range k.identities {
   194  		v = append(v, i.Email)
   195  	}
   196  	return v
   197  }
   198  
   199  func (k *GpgBaseKey) SetFingerprint(pgp *PGPFingerprint) {
   200  	k.fingerprint = pgp
   201  }
   202  
   203  func (k *GpgPrimaryKey) Parse(l *GpgIndexLine) error {
   204  	if err := k.ParseBase(l); err != nil {
   205  		return err
   206  	}
   207  	return k.AddUID(l)
   208  }
   209  
   210  func NewGpgPrimaryKey(g *GlobalContext) *GpgPrimaryKey {
   211  	ret := &GpgPrimaryKey{Contextified: NewContextified(g)}
   212  	ret.top = ret
   213  	return ret
   214  }
   215  
   216  func ParseGpgPrimaryKey(g *GlobalContext, l *GpgIndexLine) (key *GpgPrimaryKey, err error) {
   217  	key = NewGpgPrimaryKey(g)
   218  	err = key.Parse(l)
   219  	return
   220  }
   221  
   222  func (k *GpgPrimaryKey) AddUID(l *GpgIndexLine) (err error) {
   223  	var id *Identity
   224  	if f := l.At(9); len(f) == 0 {
   225  	} else if id, err = ParseIdentity(f); err != nil {
   226  	} else if l.At(1) != "r" { // is not revoked
   227  		k.identities = append(k.identities, id)
   228  	}
   229  	if err != nil {
   230  		err = ErrorToGpgIndexError(l.lineno, err)
   231  	}
   232  	return
   233  }
   234  
   235  func (k *GpgPrimaryKey) AddFingerprint(l *GpgIndexLine) (err error) {
   236  	var fp *PGPFingerprint
   237  	if f := l.At(9); len(f) == 0 {
   238  		err = fmt.Errorf("no fingerprint given")
   239  	} else if fp, err = PGPFingerprintFromHex(f); err == nil {
   240  		k.top.SetFingerprint(fp)
   241  	}
   242  	if err != nil {
   243  		err = ErrorToGpgIndexError(l.lineno, err)
   244  	}
   245  	return
   246  }
   247  
   248  func (k *GpgPrimaryKey) GetFingerprint() *PGPFingerprint {
   249  	return k.fingerprint
   250  }
   251  
   252  func (k *GpgPrimaryKey) GetPGPIdentities() []keybase1.PGPIdentity {
   253  	ret := make([]keybase1.PGPIdentity, len(k.identities))
   254  	for i, ident := range k.identities {
   255  		ret[i] = ident.Export()
   256  	}
   257  	return ret
   258  }
   259  
   260  func (k *GpgPrimaryKey) GetEmails() []string {
   261  	ret := make([]string, len(k.identities))
   262  	for i, id := range k.identities {
   263  		ret[i] = id.Email
   264  	}
   265  	return ret
   266  }
   267  
   268  func (k *GpgPrimaryKey) GetAllID64s() []string {
   269  	var ret []string
   270  	add := func(fp *PGPFingerprint) {
   271  		if fp != nil {
   272  			ret = append(ret, fp.ToKeyID())
   273  		}
   274  	}
   275  	add(k.GetFingerprint())
   276  	for _, sk := range k.subkeys {
   277  		add(sk.fingerprint)
   278  	}
   279  	return ret
   280  }
   281  
   282  func (k *GpgPrimaryKey) AddSubkey(l *GpgIndexLine) (err error) {
   283  	var sk *GpgSubKey
   284  	if sk, err = ParseGpgSubKey(l); err == nil {
   285  		k.subkeys = append(k.subkeys, sk)
   286  		k.top = sk
   287  	}
   288  	return
   289  }
   290  
   291  func (k *GpgPrimaryKey) ToKey() *GpgPrimaryKey { return k }
   292  
   293  func (k *GpgPrimaryKey) AddLine(l *GpgIndexLine) (err error) {
   294  	if l.Len() < 2 {
   295  		err = GpgIndexError{l.lineno, "too few fields"}
   296  	} else {
   297  		f := l.At(0)
   298  		switch f {
   299  		case "fpr":
   300  			err = k.AddFingerprint(l)
   301  		case "uid":
   302  			err = k.AddUID(l)
   303  		case "uat", "grp": // ignore
   304  		case "sub", "ssb":
   305  			err = k.AddSubkey(l)
   306  		case "rvk": // designated revoker (ignore)
   307  		default:
   308  			err = GpgIndexError{l.lineno, fmt.Sprintf("Unknown subfield: %s", f)}
   309  		}
   310  
   311  	}
   312  	return err
   313  }
   314  
   315  // =============================================================================
   316  
   317  type GpgSubKey struct {
   318  	GpgBaseKey
   319  }
   320  
   321  func ParseGpgSubKey(l *GpgIndexLine) (sk *GpgSubKey, err error) {
   322  	sk = &GpgSubKey{}
   323  	err = sk.ParseBase(l)
   324  	return
   325  }
   326  
   327  // =============================================================================
   328  
   329  type GpgIndexElement interface {
   330  	ToKey() *GpgPrimaryKey
   331  }
   332  
   333  type GpgKeyIndex struct {
   334  	Keys                        []*GpgPrimaryKey
   335  	Emails, Fingerprints, ID64s *BucketDict
   336  }
   337  
   338  func (ki *GpgKeyIndex) Len() int {
   339  	return len(ki.Keys)
   340  }
   341  func (ki *GpgKeyIndex) Swap(i, j int) {
   342  	ki.Keys[i], ki.Keys[j] = ki.Keys[j], ki.Keys[i]
   343  }
   344  func (ki *GpgKeyIndex) Less(i, j int) bool {
   345  	a, b := ki.Keys[i], ki.Keys[j]
   346  	if len(a.identities) > len(b.identities) {
   347  		return true
   348  	}
   349  	if len(a.identities) < len(b.identities) {
   350  		return false
   351  	}
   352  	if a.Expires == 0 {
   353  		return true
   354  	}
   355  	if b.Expires == 0 {
   356  		return false
   357  	}
   358  	if a.Expires > b.Expires {
   359  		return true
   360  	}
   361  	return false
   362  }
   363  
   364  func (ki *GpgKeyIndex) GetRowFunc() func() []string {
   365  	i := 0
   366  	return func() []string {
   367  		if i >= len(ki.Keys) {
   368  			return nil
   369  		}
   370  		ret := ki.Keys[i].ToRow(i + 1)
   371  		i++
   372  		return ret
   373  	}
   374  }
   375  
   376  func (ki *GpgKeyIndex) Sort() {
   377  	sort.Sort(ki)
   378  }
   379  
   380  func NewGpgKeyIndex() *GpgKeyIndex {
   381  	return &GpgKeyIndex{
   382  		Emails:       NewBuckDict(),
   383  		Fingerprints: NewBuckDict(),
   384  		ID64s:        NewBuckDict(),
   385  	}
   386  }
   387  
   388  func (ki *GpgKeyIndex) IndexKey(k *GpgPrimaryKey) {
   389  	ki.Keys = append(ki.Keys, k)
   390  	if fp := k.GetFingerprint(); fp != nil {
   391  		ki.Fingerprints.Add(fp.String(), k)
   392  	}
   393  	for _, e := range Uniquify(k.GetEmails()) {
   394  		ki.Emails.Add(e, k)
   395  	}
   396  	for _, i := range Uniquify(k.GetAllID64s()) {
   397  		ki.ID64s.Add(i, k)
   398  	}
   399  }
   400  
   401  func (ki *GpgKeyIndex) PushElement(mctx MetaContext, e GpgIndexElement) {
   402  	if key := e.ToKey(); key.IsValid(mctx) {
   403  		ki.IndexKey(key)
   404  	}
   405  }
   406  
   407  func (ki *GpgKeyIndex) AllFingerprints() []PGPFingerprint {
   408  	var ret []PGPFingerprint
   409  	for _, k := range ki.Keys {
   410  		if fp := k.GetFingerprint(); fp != nil {
   411  			ret = append(ret, *fp)
   412  		}
   413  	}
   414  	return ret
   415  }
   416  
   417  // =============================================================================
   418  
   419  type GpgIndexLine struct {
   420  	v      []string
   421  	lineno int
   422  }
   423  
   424  func (g GpgIndexLine) Len() int        { return len(g.v) }
   425  func (g GpgIndexLine) At(i int) string { return g.v[i] }
   426  
   427  func ParseLine(s string, i int) (ret *GpgIndexLine, err error) {
   428  	s = strings.TrimSpace(s)
   429  	v := strings.Split(s, ":")
   430  	if v == nil {
   431  		err = GpgError{fmt.Sprintf("%d: Bad line; split failed", i)}
   432  	} else {
   433  		ret = &GpgIndexLine{v, i}
   434  	}
   435  	return
   436  }
   437  
   438  func (g GpgIndexLine) IsNewKey() bool {
   439  	return len(g.v) > 0 && (g.v[0] == "sec" || g.v[0] == "pub")
   440  }
   441  
   442  // =============================================================================
   443  
   444  type GpgIndexParser struct {
   445  	Contextified
   446  	warnings Warnings
   447  	putback  *GpgIndexLine
   448  	src      *bufio.Reader
   449  	eof      bool
   450  	lineno   int
   451  }
   452  
   453  func NewGpgIndexParser(g *GlobalContext) *GpgIndexParser {
   454  	return &GpgIndexParser{
   455  		Contextified: NewContextified(g),
   456  		eof:          false,
   457  		lineno:       0,
   458  		putback:      nil,
   459  	}
   460  }
   461  
   462  func (p *GpgIndexParser) Warn(w Warning) {
   463  	p.warnings.Push(w)
   464  }
   465  
   466  func (p *GpgIndexParser) ParseElement() (ret GpgIndexElement, err error) {
   467  	var line *GpgIndexLine
   468  	line, err = p.GetLine()
   469  	if err != nil || line == nil {
   470  	} else if line.IsNewKey() {
   471  		ret, err = p.ParseKey(line)
   472  	}
   473  	return
   474  }
   475  
   476  func (p *GpgIndexParser) ParseKey(l *GpgIndexLine) (ret *GpgPrimaryKey, err error) {
   477  	var line *GpgIndexLine
   478  	ret, err = ParseGpgPrimaryKey(p.G(), l)
   479  	done := false
   480  	for !done && err == nil && !p.isEOF() {
   481  		if line, err = p.GetLine(); line == nil || err != nil {
   482  		} else if line.IsNewKey() {
   483  			p.PutbackLine(line)
   484  			done = true
   485  		} else if e2 := ret.AddLine(line); e2 == nil {
   486  		} else {
   487  			p.warnings.Push(ErrorToWarning(e2))
   488  		}
   489  	}
   490  	return
   491  }
   492  
   493  func (p *GpgIndexParser) GetLine() (ret *GpgIndexLine, err error) {
   494  	if p.putback != nil {
   495  		ret = p.putback
   496  		p.putback = nil
   497  		return
   498  	}
   499  
   500  	if p.isEOF() {
   501  		return
   502  	}
   503  
   504  	s, e2 := p.src.ReadString(byte('\n'))
   505  	if e2 == io.EOF {
   506  		p.eof = true
   507  		return
   508  	}
   509  	if e2 != nil {
   510  		return nil, e2
   511  	}
   512  
   513  	p.lineno++
   514  	return ParseLine(s, p.lineno)
   515  }
   516  
   517  func (p *GpgIndexParser) PutbackLine(line *GpgIndexLine) {
   518  	p.putback = line
   519  }
   520  
   521  func (p GpgIndexParser) isEOF() bool { return p.eof }
   522  
   523  func (p *GpgIndexParser) Parse(mctx MetaContext, stream io.Reader) (ki *GpgKeyIndex, err error) {
   524  	p.src = bufio.NewReader(stream)
   525  	ki = NewGpgKeyIndex()
   526  	for err == nil && !p.isEOF() {
   527  		var el GpgIndexElement
   528  		if el, err = p.ParseElement(); err == nil && el != nil {
   529  			ki.PushElement(mctx, el)
   530  		}
   531  	}
   532  	ki.Sort()
   533  	return
   534  }
   535  
   536  // =============================================================================
   537  
   538  func ParseGpgIndexStream(mctx MetaContext, stream io.Reader) (ki *GpgKeyIndex, w Warnings, err error) {
   539  	eng := NewGpgIndexParser(mctx.G())
   540  	ki, err = eng.Parse(mctx, stream)
   541  	w = eng.warnings
   542  	return
   543  }
   544  
   545  // =============================================================================
   546  
   547  func (g *GpgCLI) Index(mctx MetaContext, secret bool, query string) (ki *GpgKeyIndex, w Warnings, err error) {
   548  	var k string
   549  	if secret {
   550  		k = "-K"
   551  	} else {
   552  		k = "-k"
   553  	}
   554  	args := []string{"--with-colons", "--fingerprint", k}
   555  	if len(query) > 0 {
   556  		args = append(args, query)
   557  	}
   558  	garg := RunGpg2Arg{
   559  		Arguments: args,
   560  		Stdout:    true,
   561  	}
   562  	res := g.Run2(mctx, garg)
   563  	if res.Err != nil {
   564  		err = res.Err
   565  		return
   566  	}
   567  	if ki, w, err = ParseGpgIndexStream(mctx, res.Stdout); err != nil {
   568  		return
   569  	}
   570  	err = res.Wait()
   571  	return
   572  }
   573  
   574  // =============================================================================