github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/libkb/kex2_secret.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  	"errors"
     8  	"github.com/keybase/client/go/kex2"
     9  	keybase1 "github.com/keybase/client/go/protocol/keybase1"
    10  	"golang.org/x/crypto/scrypt"
    11  	"strings"
    12  )
    13  
    14  const kexPhraseVersion = "four"
    15  
    16  type Kex2Secret struct {
    17  	phrase string
    18  	secret kex2.Secret
    19  	typ    Kex2SecretType
    20  }
    21  
    22  type Kex2SecretType int
    23  
    24  const (
    25  	Kex2SecretTypeNone      Kex2SecretType = 0
    26  	Kex2SecretTypeV1Desktop Kex2SecretType = 1
    27  	Kex2SecretTypeV1Mobile  Kex2SecretType = 2
    28  	Kex2SecretTypeV2        Kex2SecretType = 3
    29  )
    30  
    31  func NewKex2SecretFromTypeAndUID(typ Kex2SecretType, uid keybase1.UID) (*Kex2Secret, error) {
    32  
    33  	entropy := Kex2PhraseEntropy
    34  	if typ == Kex2SecretTypeV2 {
    35  		entropy = Kex2PhraseEntropy2
    36  	}
    37  
    38  	words, err := SecWordList(entropy)
    39  	if err != nil {
    40  		return nil, err
    41  	}
    42  
    43  	phrase := strings.Join(words, " ")
    44  	// If we are provisioning a mobile device, we want to use an easier to compute secret. In order to
    45  	// communicate that to the two devices involved in kex without breaking the existing protocol,
    46  	// we have added an extra word that is not in the dictionary. Up to date clients can see this
    47  	// word and use the lighter version of scrypt.
    48  	if typ == Kex2SecretTypeV1Mobile {
    49  		phrase += " " + kexPhraseVersion
    50  	}
    51  	return newKex2SecretFromTypeUIDAndPhrase(typ, uid, phrase)
    52  }
    53  
    54  func NewKex2SecretFromUIDAndPhrase(uid keybase1.UID, phrase string) (*Kex2Secret, error) {
    55  
    56  	typ, err := kex2TypeFromPhrase(phrase)
    57  	if err != nil {
    58  		return nil, err
    59  	}
    60  
    61  	return newKex2SecretFromTypeUIDAndPhrase(typ, uid, phrase)
    62  }
    63  
    64  func kex2TypeFromPhrase(phrase string) (typ Kex2SecretType, err error) {
    65  
    66  	words := strings.Split(phrase, " ")
    67  	if len(words) == 8 {
    68  		return Kex2SecretTypeV1Desktop, nil
    69  	}
    70  	if len(words) != 9 {
    71  		return Kex2SecretTypeNone, errors.New("wrong number of words in passphrase; wanted 8 or 9")
    72  	}
    73  	if words[len(words)-1] == kexPhraseVersion {
    74  		return Kex2SecretTypeV1Mobile, nil
    75  	}
    76  	return Kex2SecretTypeV2, nil
    77  }
    78  
    79  func newKex2SecretFromTypeUIDAndPhrase(typ Kex2SecretType, uid keybase1.UID, phrase string) (*Kex2Secret, error) {
    80  
    81  	var cost int
    82  	var salt []byte
    83  	switch typ {
    84  	case Kex2SecretTypeV1Mobile:
    85  		cost = Kex2ScryptLiteCost
    86  	case Kex2SecretTypeV1Desktop:
    87  		cost = Kex2ScryptCost
    88  	case Kex2SecretTypeV2:
    89  		cost = Kex2ScryptLiteCost
    90  		salt = uid.ToBytes()
    91  	default:
    92  		return nil, errors.New("unknown kex2 secret type")
    93  	}
    94  
    95  	key, err := scrypt.Key([]byte(phrase), salt, cost, Kex2ScryptR, Kex2ScryptP, Kex2ScryptKeylen)
    96  	if err != nil {
    97  		return nil, err
    98  	}
    99  	res := &Kex2Secret{phrase: phrase, typ: typ}
   100  	copy(res.secret[:], key)
   101  	return res, nil
   102  }
   103  
   104  func (s *Kex2Secret) Secret() kex2.Secret {
   105  	return s.secret
   106  }
   107  
   108  func (s *Kex2Secret) Phrase() string {
   109  	return s.phrase
   110  }