github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/kvstore/boxer.go (about)

     1  package kvstore
     2  
     3  import (
     4  	"encoding/base64"
     5  	"fmt"
     6  
     7  	"github.com/keybase/client/go/chat/signencrypt"
     8  	"github.com/keybase/client/go/kbcrypto"
     9  	"github.com/keybase/client/go/libkb"
    10  	"github.com/keybase/client/go/msgpack"
    11  	"github.com/keybase/client/go/protocol/keybase1"
    12  	"github.com/keybase/go-crypto/ed25519"
    13  )
    14  
    15  type KVStoreBoxer interface {
    16  	Box(mctx libkb.MetaContext, entryID keybase1.KVEntryID, revision int, cleartextValue string) (ciphertext string,
    17  		teamKeyGen keybase1.PerTeamKeyGeneration, ciphertextVersion int, err error)
    18  	Unbox(mctx libkb.MetaContext, entryID keybase1.KVEntryID, revision int, ciphertext string, teamKeyGen keybase1.PerTeamKeyGeneration, formatVersion int,
    19  		senderUID keybase1.UID, senderEldestSeqno keybase1.Seqno, senderDeviceID keybase1.DeviceID) (cleartext string, err error)
    20  }
    21  
    22  var _ KVStoreBoxer = (*KVStoreRealBoxer)(nil)
    23  
    24  type KVStoreRealBoxer struct {
    25  	libkb.Contextified
    26  }
    27  
    28  func NewKVStoreBoxer(g *libkb.GlobalContext) *KVStoreRealBoxer {
    29  	return &KVStoreRealBoxer{
    30  		Contextified: libkb.NewContextified(g),
    31  	}
    32  }
    33  
    34  type kvStoreMetadata struct {
    35  	EntryID           keybase1.KVEntryID `codec:"e" json:"e"`
    36  	Revision          int                `codec:"r" json:"r"`
    37  	EncKey            keybase1.Bytes32   `codec:"k" json:"k"`
    38  	CiphertextVersion int                `codec:"v" json:"v"`
    39  	UID               keybase1.UID       `codec:"u" json:"u"`
    40  	EldestSeqno       keybase1.Seqno     `codec:"s" json:"s"`
    41  	DeviceID          keybase1.DeviceID  `codec:"d" json:"d"`
    42  }
    43  
    44  func newNonce() (ret [signencrypt.NonceSize]byte, err error) {
    45  	randBytes, err := libkb.RandBytes(signencrypt.NonceSize)
    46  	if err != nil {
    47  		return ret, err
    48  	}
    49  	copy(ret[:], randBytes)
    50  	return ret, nil
    51  }
    52  
    53  func (b *KVStoreRealBoxer) fetchEncryptionKey(mctx libkb.MetaContext, entryID keybase1.KVEntryID, generation *keybase1.PerTeamKeyGeneration) (res [signencrypt.SecretboxKeySize]byte, gen keybase1.PerTeamKeyGeneration, err error) {
    54  	// boxing can always use the latest key, unboxing will pass in a team generation to load
    55  	loadArg := keybase1.FastTeamLoadArg{
    56  		ID:           entryID.TeamID,
    57  		Applications: []keybase1.TeamApplication{keybase1.TeamApplication_KVSTORE},
    58  	}
    59  	if generation == nil {
    60  		loadArg.NeedLatestKey = true
    61  	} else {
    62  		loadArg.KeyGenerationsNeeded = []keybase1.PerTeamKeyGeneration{*generation}
    63  	}
    64  	teamLoadRes, err := mctx.G().GetFastTeamLoader().Load(mctx, loadArg)
    65  	if err != nil {
    66  		return res, gen, err
    67  	}
    68  	if len(teamLoadRes.ApplicationKeys) != 1 {
    69  		return res, gen, fmt.Errorf("wrong number of keys from fast-team-loading encryption key; wanted 1, got %d", len(teamLoadRes.ApplicationKeys))
    70  	}
    71  	appKey := teamLoadRes.ApplicationKeys[0]
    72  	if generation != nil && appKey.KeyGeneration != *generation {
    73  		return res, gen, fmt.Errorf("wrong app key generation; wanted %d but got %d", *generation, appKey.KeyGeneration)
    74  	}
    75  	if appKey.Application != keybase1.TeamApplication_KVSTORE {
    76  		return res, gen, fmt.Errorf("wrong app key application; wanted %d but got %d", keybase1.TeamApplication_KVSTORE, appKey.Application)
    77  	}
    78  	var encKey [signencrypt.SecretboxKeySize]byte = appKey.Key
    79  	return encKey, appKey.KeyGeneration, nil
    80  }
    81  
    82  func (b *KVStoreRealBoxer) fetchVerifyKey(mctx libkb.MetaContext, uid keybase1.UID, deviceID keybase1.DeviceID) (ret signencrypt.VerifyKey, err error) {
    83  	upk, err := b.G().GetUPAKLoader().LoadUPAKWithDeviceID(mctx.Ctx(), uid, deviceID)
    84  	if err != nil {
    85  		return nil, err
    86  	}
    87  	verifyKID, _ := upk.Current.FindSigningDeviceKID(deviceID)
    88  	verifyKey := kbcrypto.KIDToNaclSigningKeyPublic(verifyKID.ToBytes())
    89  	if verifyKey == nil {
    90  		return nil, kbcrypto.BadKeyError{}
    91  	}
    92  	var verKey [ed25519.PublicKeySize]byte = *verifyKey
    93  	return &verKey, nil
    94  }
    95  
    96  func (b *KVStoreRealBoxer) Box(mctx libkb.MetaContext, entryID keybase1.KVEntryID, revision int, cleartext string) (
    97  	ciphertext string, teamKeyGen keybase1.PerTeamKeyGeneration, version int, err error) {
    98  
    99  	defer mctx.Trace(fmt.Sprintf("KVStoreRealBoxer#Box: %s, %s, %s", entryID.TeamID, entryID.Namespace, entryID.EntryKey),
   100  		&err)()
   101  
   102  	clearBytes := []byte(cleartext)
   103  	ciphertextVersion := 1
   104  	nonce, err := newNonce()
   105  	if err != nil {
   106  		mctx.Debug("error making a nonce: %v", err)
   107  		return "", keybase1.PerTeamKeyGeneration(0), 0, err
   108  	}
   109  	// get encryption key (and team generation) and this device's signing key
   110  	encKey, teamGen, err := b.fetchEncryptionKey(mctx, entryID, nil)
   111  	if err != nil {
   112  		mctx.Debug("error fetching encryption key for entry %+v: %v", entryID, err)
   113  		return "", keybase1.PerTeamKeyGeneration(0), 0, err
   114  	}
   115  	uv, deviceID, _, signingKey, _ := mctx.G().ActiveDevice.AllFields()
   116  	signingKP, ok := signingKey.(libkb.NaclSigningKeyPair)
   117  	if !ok || signingKP.Private == nil {
   118  		mctx.Debug("error with signing key: %v", err)
   119  		return "", keybase1.PerTeamKeyGeneration(0), 0, libkb.KeyCannotSignError{}
   120  	}
   121  	var signKey [ed25519.PrivateKeySize]byte = *signingKP.Private
   122  	// build associated data
   123  	associatedData := kvStoreMetadata{
   124  		EntryID:           entryID,
   125  		Revision:          revision,
   126  		EncKey:            encKey,
   127  		CiphertextVersion: ciphertextVersion,
   128  		UID:               uv.Uid,
   129  		EldestSeqno:       uv.EldestSeqno,
   130  		DeviceID:          deviceID,
   131  	}
   132  
   133  	// seal it all up
   134  	signEncryptedBytes, err := signencrypt.SealWithAssociatedData(
   135  		clearBytes, associatedData, &encKey, &signKey, kbcrypto.SignaturePrefixTeamStore, &nonce)
   136  	if err != nil {
   137  		mctx.Debug("error sealing message and associated data: %v", err)
   138  		return "", keybase1.PerTeamKeyGeneration(0), 0, err
   139  	}
   140  	boxed := keybase1.EncryptedKVEntry{
   141  		V: 1,
   142  		E: signEncryptedBytes,
   143  		N: nonce[:],
   144  	}
   145  	// pack it, string it, ship it.
   146  	packed, err := msgpack.Encode(boxed)
   147  	if err != nil {
   148  		mctx.Debug("error msgpacking secretbox for entry %+v: %v", entryID, err)
   149  		return "", keybase1.PerTeamKeyGeneration(0), 0, err
   150  	}
   151  	return base64.StdEncoding.EncodeToString(packed), teamGen, ciphertextVersion, nil
   152  }
   153  
   154  func (b *KVStoreRealBoxer) Unbox(mctx libkb.MetaContext, entryID keybase1.KVEntryID, revision int, ciphertext string,
   155  	teamKeyGen keybase1.PerTeamKeyGeneration, formatVersion int, senderUID keybase1.UID, senderEldestSeqno keybase1.Seqno,
   156  	senderDeviceID keybase1.DeviceID) (cleartext string, err error) {
   157  
   158  	defer mctx.Trace(fmt.Sprintf("KVStoreRealBoxer#Unbox: t:%s, n:%s, k:%s", entryID.TeamID, entryID.Namespace, entryID.EntryKey),
   159  		&err)()
   160  
   161  	if formatVersion != 1 {
   162  		return "", fmt.Errorf("unsupported format version %d isn't 1", formatVersion)
   163  	}
   164  	// basic decoding into a not-yet-unsealed box
   165  	decoded, err := base64.StdEncoding.DecodeString(ciphertext)
   166  	if err != nil {
   167  		mctx.Debug("boxed message isn't base64: %v", err)
   168  		return "", err
   169  	}
   170  	var box keybase1.EncryptedKVEntry
   171  	err = msgpack.Decode(&box, decoded)
   172  	if err != nil {
   173  		mctx.Debug("msgpack decode error on boxed message: %v", err)
   174  		return "", err
   175  	}
   176  	if box.V != 1 {
   177  		return "", fmt.Errorf("unsupported secret box version: %v", box.V)
   178  	}
   179  	// fetch encryption and verification keys
   180  	encKey, _, err := b.fetchEncryptionKey(mctx, entryID, &teamKeyGen)
   181  	if err != nil {
   182  		mctx.Debug("error fetching decryption key: %v", err)
   183  		return "", err
   184  	}
   185  	verKey, err := b.fetchVerifyKey(mctx, senderUID, senderDeviceID)
   186  	if err != nil {
   187  		mctx.Debug("error fetching verify key: %v", err)
   188  		return "", err
   189  	}
   190  	var nonce [signencrypt.NonceSize]byte
   191  	if copy(nonce[:], box.N) != signencrypt.NonceSize {
   192  		return "", libkb.DecryptBadNonceError{}
   193  	}
   194  	associatedData := kvStoreMetadata{
   195  		EntryID:           entryID,
   196  		Revision:          revision,
   197  		EncKey:            encKey,
   198  		CiphertextVersion: box.V,
   199  		UID:               senderUID,
   200  		EldestSeqno:       senderEldestSeqno,
   201  		DeviceID:          senderDeviceID,
   202  	}
   203  
   204  	// open it up
   205  	clearBytes, err := signencrypt.OpenWithAssociatedData(box.E, associatedData, &encKey, verKey, kbcrypto.SignaturePrefixTeamStore, &nonce)
   206  	if err != nil {
   207  		return "", err
   208  	}
   209  	return string(clearBytes), nil
   210  }