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

     1  // Copyright 2017 Keybase Inc. All rights reserved.
     2  // Use of this source code is governed by a BSD
     3  // license that can be found in the LICENSE file.
     4  
     5  package libkb
     6  
     7  import (
     8  	"crypto/hmac"
     9  	"crypto/sha256"
    10  	"encoding/hex"
    11  	"fmt"
    12  
    13  	"github.com/keybase/go-codec/codec"
    14  
    15  	context "golang.org/x/net/context"
    16  )
    17  
    18  // TLFPseudonym is an identifier for a key in a tlf
    19  type TlfPseudonym [32]byte
    20  
    21  type KeyGen int
    22  type tlfID [16]byte
    23  
    24  const tlfPseudonymVersion = 1
    25  
    26  // TlfPseudonymInfo is what a pseudonym represents
    27  type TlfPseudonymInfo struct {
    28  	// TLF name like: /keybase/private/a,b
    29  	Name string
    30  	// TLF id
    31  	ID      tlfID
    32  	KeyGen  KeyGen
    33  	HmacKey [32]byte
    34  }
    35  
    36  // The server returns the current full name of the TLF, in addition to the TlfPseudonymInfo above.
    37  type TlfPseudonymServerInfo struct {
    38  	TlfPseudonymInfo
    39  
    40  	// Using untrusted data during decryption is safe because we don't rely on
    41  	// TLF keys for sender authenticity. (Note however that senders must not
    42  	// use untrusted keys, or else they'd lose all privacy.)
    43  	UntrustedCurrentName string
    44  }
    45  
    46  func (t TlfPseudonym) String() string {
    47  	return hex.EncodeToString(t[:])
    48  }
    49  
    50  func (t TlfPseudonym) Eq(r TlfPseudonym) bool {
    51  	return hmac.Equal(t[:], r[:])
    52  }
    53  
    54  // tlfPseudonymReq is what the server needs to store a pseudonym
    55  type tlfPseudonymReq struct {
    56  	Pseudonym string `json:"tlf_pseudonym"` // hex
    57  	Name      string `json:"tlf_name"`
    58  	ID        string `json:"tlf_id"` // hex
    59  	KeyGen    int    `json:"tlf_key_gen"`
    60  	HmacKey   string `json:"hmac_key"` // hex
    61  }
    62  
    63  // tlfPseudonymContents is the data packed inside the HMAC
    64  type tlfPseudonymContents struct {
    65  	_struct bool `codec:",toarray"` //nolint
    66  	Version int
    67  	Name    string
    68  	ID      tlfID
    69  	KeyGen  KeyGen
    70  }
    71  
    72  type getTlfPseudonymsRes struct {
    73  	TlfPseudonyms []getTlfPseudonymRes `json:"tlf_pseudonyms"`
    74  	Status        AppStatus            `json:"status"`
    75  }
    76  
    77  func (r *getTlfPseudonymsRes) GetAppStatus() *AppStatus {
    78  	return &r.Status
    79  }
    80  
    81  type getTlfPseudonymRes struct {
    82  	Err  *PseudonymGetError `json:"err"`
    83  	Info *struct {
    84  		Name                 string `json:"tlf_name"`
    85  		UntrustedCurrentName string `json:"untrusted_current_name"`
    86  		ID                   string `json:"tlf_id"` // hex
    87  		KeyGen               int    `json:"tlf_key_gen"`
    88  		HmacKey              string `json:"hmac_key"` // hex
    89  	} `json:"info"`
    90  }
    91  
    92  type GetTlfPseudonymEither struct {
    93  	// Exactly one of these 2 fields is nil.
    94  	Err  error
    95  	Info *TlfPseudonymServerInfo
    96  }
    97  
    98  // MakePseudonym makes a TLF pseudonym from the given input.
    99  func MakePseudonym(info TlfPseudonymInfo) (TlfPseudonym, error) {
   100  	input := tlfPseudonymContents{
   101  		Version: tlfPseudonymVersion,
   102  		Name:    info.Name,
   103  		ID:      info.ID,
   104  		KeyGen:  info.KeyGen,
   105  	}
   106  	mh := codec.MsgpackHandle{WriteExt: true}
   107  	var buf []byte
   108  	enc := codec.NewEncoderBytes(&buf, &mh)
   109  	err := enc.Encode(input)
   110  	if err != nil {
   111  		return [32]byte{}, err
   112  	}
   113  	mac := hmac.New(sha256.New, info.HmacKey[:])
   114  	_, err = mac.Write(buf)
   115  	if err != nil {
   116  		return [32]byte{}, err
   117  	}
   118  	hmac := MakeByte32(mac.Sum(nil))
   119  	return hmac, nil
   120  }
   121  
   122  func PostTlfPseudonyms(ctx context.Context, g *GlobalContext, pnymInfos []TlfPseudonymInfo) ([]TlfPseudonym, error) {
   123  	var pnymReqs []tlfPseudonymReq
   124  	var pnyms []TlfPseudonym
   125  	for _, info := range pnymInfos {
   126  		// Compute the pseudonym
   127  		pn, err := MakePseudonym(info)
   128  		if err != nil {
   129  			return nil, err
   130  		}
   131  		pnyms = append(pnyms, pn)
   132  		pnymReqs = append(pnymReqs, tlfPseudonymReq{
   133  			Pseudonym: pn.String(),
   134  			Name:      info.Name,
   135  			ID:        hex.EncodeToString(info.ID[:]),
   136  			KeyGen:    int(info.KeyGen),
   137  			HmacKey:   hex.EncodeToString(info.HmacKey[:]),
   138  		})
   139  	}
   140  
   141  	payload := make(JSONPayload)
   142  	payload["tlf_pseudonyms"] = pnymReqs
   143  	mctx := NewMetaContext(ctx, g)
   144  
   145  	_, err := g.API.PostJSON(mctx, APIArg{
   146  		Endpoint:    "kbfs/pseudonym/put",
   147  		JSONPayload: payload,
   148  		SessionType: APISessionTypeREQUIRED,
   149  	})
   150  	if err != nil {
   151  		return nil, err
   152  	}
   153  	return pnyms, nil
   154  }
   155  
   156  // GetTlfPseudonyms fetches info for a list of pseudonyms.
   157  // The output structs are returned in the order corresponding to the inputs.
   158  // The top-level error is filled if the entire request fails.
   159  // The each-struct errors may be filled for per-pseudonym errors.
   160  func GetTlfPseudonyms(ctx context.Context, g *GlobalContext, pnyms []TlfPseudonym) ([]GetTlfPseudonymEither, error) {
   161  	var pnymStrings []string
   162  	for _, x := range pnyms {
   163  		pnymStrings = append(pnymStrings, x.String())
   164  	}
   165  
   166  	payload := make(JSONPayload)
   167  	payload["tlf_pseudonyms"] = pnymStrings
   168  
   169  	var res getTlfPseudonymsRes
   170  	mctx := NewMetaContext(ctx, g)
   171  	err := g.API.PostDecode(mctx,
   172  		APIArg{
   173  			Endpoint:    "kbfs/pseudonym/get",
   174  			SessionType: APISessionTypeREQUIRED,
   175  			JSONPayload: payload,
   176  		},
   177  		&res)
   178  	if err != nil {
   179  		return nil, err
   180  	}
   181  
   182  	// Validate the response
   183  	if len(res.TlfPseudonyms) != len(pnyms) {
   184  		return nil, &PseudonymGetError{fmt.Sprintf("invalid server response for pseudonym get: len %v != %v",
   185  			len(res.TlfPseudonyms), len(pnyms))}
   186  	}
   187  	var resList []GetTlfPseudonymEither
   188  	for i, received := range res.TlfPseudonyms {
   189  		resList = append(resList, checkAndConvertTlfPseudonymFromServer(ctx, g, pnyms[i], received))
   190  	}
   191  
   192  	return resList, nil
   193  }
   194  
   195  func checkAndConvertTlfPseudonymFromServer(ctx context.Context, g *GlobalContext, req TlfPseudonym, received getTlfPseudonymRes) GetTlfPseudonymEither {
   196  	mkErr := func(err error) GetTlfPseudonymEither {
   197  		return GetTlfPseudonymEither{
   198  			Info: nil,
   199  			Err:  err,
   200  		}
   201  	}
   202  
   203  	x := GetTlfPseudonymEither{}
   204  
   205  	// This check is necessary because of sneaky typed nil.
   206  	// received.Err's type is lower than x.Err
   207  	// So doing `x.Err = received.Err` is bad if received.Err is nil.
   208  	// https://golang.org/doc/faq#nil_error
   209  	// https://play.golang.org/p/BnjVTGh-gO
   210  	if received.Err != nil {
   211  		x.Err = received.Err
   212  	}
   213  
   214  	if received.Info != nil {
   215  		info := TlfPseudonymServerInfo{}
   216  
   217  		info.Name = received.Info.Name
   218  		info.UntrustedCurrentName = received.Info.UntrustedCurrentName
   219  
   220  		err := DecodeHexFixed(info.ID[:], []byte(received.Info.ID))
   221  		if err != nil {
   222  			return mkErr(err)
   223  		}
   224  
   225  		info.KeyGen = KeyGen(received.Info.KeyGen)
   226  
   227  		err = DecodeHexFixed(info.HmacKey[:], []byte(received.Info.HmacKey))
   228  		if err != nil {
   229  			return mkErr(err)
   230  		}
   231  
   232  		x.Info = &info
   233  	}
   234  
   235  	err := checkTlfPseudonymFromServer(ctx, g, req, x)
   236  	if err != nil {
   237  		return mkErr(err)
   238  	}
   239  	return x
   240  }
   241  
   242  func checkTlfPseudonymFromServer(ctx context.Context, g *GlobalContext, req TlfPseudonym, received GetTlfPseudonymEither) error {
   243  	// Exactly one of Info and Err should exist
   244  	if (received.Info == nil) == (received.Err == nil) {
   245  		return &PseudonymGetError{fmt.Sprintf("invalid server response for pseudonym get: %s", req)}
   246  	}
   247  
   248  	// Errors don't need validation
   249  	if received.Info == nil {
   250  		return nil
   251  	}
   252  
   253  	// Check that the pseudonym info matches the query.
   254  	pn, err := MakePseudonym(received.Info.TlfPseudonymInfo)
   255  	if err != nil {
   256  		// Error creating pseudonym locally
   257  		return &PseudonymGetError{err.Error()}
   258  	}
   259  	if !req.Eq(pn) {
   260  		return &PseudonymGetError{fmt.Sprintf("returned data does not match pseudonym: %s != %s",
   261  			req, pn)}
   262  	}
   263  
   264  	return nil
   265  }
   266  
   267  func RandomHmacKey() [32]byte {
   268  	slice, err := RandBytes(32)
   269  	if err != nil {
   270  		panic(err)
   271  	}
   272  	return MakeByte32(slice)
   273  }