1// Copyright 2015 Keybase, Inc. All rights reserved. Use of
2// this source code is governed by the included BSD license.
3
4package libkb
5
6import (
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
14const kexPhraseVersion = "four"
15
16type Kex2Secret struct {
17	phrase string
18	secret kex2.Secret
19	typ    Kex2SecretType
20}
21
22type Kex2SecretType int
23
24const (
25	Kex2SecretTypeNone      Kex2SecretType = 0
26	Kex2SecretTypeV1Desktop Kex2SecretType = 1
27	Kex2SecretTypeV1Mobile  Kex2SecretType = 2
28	Kex2SecretTypeV2        Kex2SecretType = 3
29)
30
31func 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
54func 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
64func 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
79func 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
104func (s *Kex2Secret) Secret() kex2.Secret {
105	return s.secret
106}
107
108func (s *Kex2Secret) Phrase() string {
109	return s.phrase
110}
111