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

     1  package git
     2  
     3  import (
     4  	"fmt"
     5  
     6  	"golang.org/x/crypto/nacl/secretbox"
     7  	"golang.org/x/net/context"
     8  
     9  	"github.com/keybase/client/go/libkb"
    10  	"github.com/keybase/client/go/protocol/keybase1"
    11  	"github.com/keybase/client/go/teams"
    12  )
    13  
    14  // publicCryptKey is a zero key used for public repos
    15  var publicCryptKey keybase1.TeamApplicationKey
    16  
    17  func init() {
    18  	var zero [libkb.NaclDHKeySecretSize]byte
    19  	publicCryptKey = keybase1.TeamApplicationKey{
    20  		Application:   keybase1.TeamApplication_GIT_METADATA,
    21  		KeyGeneration: 1,
    22  		Key:           keybase1.Bytes32(zero),
    23  	}
    24  }
    25  
    26  // Crypto implements Cryptoer interface.
    27  type Crypto struct {
    28  	libkb.Contextified
    29  }
    30  
    31  var _ Cryptoer = &Crypto{}
    32  
    33  // NewCrypto returns a Crypto object.
    34  func NewCrypto(g *libkb.GlobalContext) *Crypto {
    35  	return &Crypto{
    36  		Contextified: libkb.NewContextified(g),
    37  	}
    38  }
    39  
    40  // Box encrypts the plaintext with the most current key for the given team. It yields a NaCl
    41  // ciphertext and nonce, and also says which generation of the key it used.
    42  func (c *Crypto) Box(ctx context.Context, plaintext []byte, teamSpec keybase1.TeamIDWithVisibility) (*keybase1.EncryptedGitMetadata, error) {
    43  	team, err := c.loadTeam(ctx, teamSpec, 0)
    44  	if err != nil {
    45  		return nil, err
    46  	}
    47  
    48  	public := teamSpec.Visibility == keybase1.TLFVisibility_PUBLIC
    49  
    50  	key := publicCryptKey
    51  	if !public {
    52  		key, err = team.GitMetadataKey(ctx)
    53  		if err != nil {
    54  			return nil, err
    55  		}
    56  	}
    57  
    58  	nonce, err := libkb.RandomNaclDHNonce()
    59  	if err != nil {
    60  		return nil, err
    61  	}
    62  
    63  	var encKey [libkb.NaclSecretBoxKeySize]byte = key.Key
    64  	sealed := secretbox.Seal(nil, plaintext, &nonce, &encKey)
    65  
    66  	return &keybase1.EncryptedGitMetadata{
    67  		V:   libkb.CurrentGitMetadataEncryptionVersion,
    68  		E:   sealed,
    69  		N:   nonce,
    70  		Gen: key.KeyGeneration,
    71  	}, nil
    72  }
    73  
    74  // Unbox decrypts the given ciphertext with the given nonce, for the given generation of the
    75  // given team. Can return an error. Will return a non-nil plaintext on success.
    76  func (c *Crypto) Unbox(ctx context.Context, teamSpec keybase1.TeamIDWithVisibility, metadata *keybase1.EncryptedGitMetadata) (plaintext []byte, err error) {
    77  	defer c.G().CTrace(ctx, fmt.Sprintf("git.Crypto#Unbox(%s, vis:%v)", teamSpec.TeamID, teamSpec.Visibility), &err)()
    78  
    79  	if metadata.V != 1 {
    80  		return nil, fmt.Errorf("invalid EncryptedGitMetadata version: %d", metadata.V)
    81  	}
    82  
    83  	public := teamSpec.Visibility == keybase1.TLFVisibility_PUBLIC
    84  	if public != teamSpec.TeamID.IsPublic() {
    85  		return nil, libkb.NewTeamVisibilityError(public, teamSpec.TeamID.IsPublic())
    86  	}
    87  
    88  	key := publicCryptKey
    89  	if !public {
    90  		key, err = c.fastLoadKeyAtGeneration(ctx, teamSpec, metadata)
    91  		if err != nil {
    92  			return nil, err
    93  		}
    94  	}
    95  
    96  	var encKey [libkb.NaclSecretBoxKeySize]byte = key.Key
    97  	var naclNonce [libkb.NaclDHNonceSize]byte = metadata.N
    98  
    99  	plaintext, ok := secretbox.Open(nil, metadata.E, &naclNonce, &encKey)
   100  	if !ok {
   101  		return nil, libkb.DecryptOpenError{}
   102  	}
   103  	return plaintext, nil
   104  }
   105  
   106  func (c *Crypto) fastLoadKeyAtGeneration(ctx context.Context, teamSpec keybase1.TeamIDWithVisibility, metadata *keybase1.EncryptedGitMetadata) (key keybase1.TeamApplicationKey, err error) {
   107  	teamID := teamSpec.TeamID
   108  	arg := keybase1.FastTeamLoadArg{
   109  		ID:                   teamID,
   110  		Public:               false,
   111  		Applications:         []keybase1.TeamApplication{keybase1.TeamApplication_GIT_METADATA},
   112  		KeyGenerationsNeeded: []keybase1.PerTeamKeyGeneration{metadata.Gen},
   113  	}
   114  	mctx := libkb.NewMetaContext(ctx, c.G())
   115  	res, err := mctx.G().GetFastTeamLoader().Load(mctx, arg)
   116  	if err != nil {
   117  		return key, err
   118  	}
   119  	n := len(res.ApplicationKeys)
   120  	if n != 1 {
   121  		return key, fmt.Errorf("wrong number of keys back from FTL; wanted 1 but got %d", n)
   122  	}
   123  	if metadata.Gen > 0 && res.ApplicationKeys[0].KeyGeneration != metadata.Gen {
   124  		return key, fmt.Errorf("wrong generation back from FTL; wanted %d but got %d", metadata.Gen, res.ApplicationKeys[0].KeyGeneration)
   125  	}
   126  
   127  	if res.ApplicationKeys[0].Application != keybase1.TeamApplication_GIT_METADATA {
   128  		return key, fmt.Errorf("wrong application; wanted %d but got %d", keybase1.TeamApplication_GIT_METADATA, res.ApplicationKeys[0].Application)
   129  	}
   130  	return res.ApplicationKeys[0], nil
   131  }
   132  
   133  func (c *Crypto) loadTeam(ctx context.Context, teamSpec keybase1.TeamIDWithVisibility, needKeyGeneration keybase1.PerTeamKeyGeneration) (*teams.Team, error) {
   134  	public := teamSpec.Visibility == keybase1.TLFVisibility_PUBLIC
   135  	arg := keybase1.LoadTeamArg{
   136  		ID:     teamSpec.TeamID,
   137  		Public: public,
   138  	}
   139  	if needKeyGeneration != 0 {
   140  		arg.Refreshers.NeedApplicationsAtGenerations = map[keybase1.PerTeamKeyGeneration][]keybase1.TeamApplication{
   141  			needKeyGeneration: {keybase1.TeamApplication_GIT_METADATA},
   142  		}
   143  	}
   144  	team, err := teams.Load(ctx, c.G(), arg)
   145  	if err != nil {
   146  		return nil, err
   147  	}
   148  
   149  	return team, nil
   150  }