1package libkb
2
3import (
4	"crypto/rand"
5	"encoding/base64"
6	"encoding/hex"
7	"encoding/json"
8	"errors"
9	"fmt"
10	"time"
11
12	"github.com/keybase/client/go/protocol/keybase1"
13)
14
15// PDPKA is a "Passphrase-Derived Public Key Authentication". In this case, it's a
16// armored, packed, signature that's been output by our signing interface.
17type PDPKA string
18
19// PDPKALoginPackage contains all relevant PDPKA versions in use at this
20// time. For now, versions 4 and 5.
21type PDPKALoginPackage struct {
22	pdpka5 PDPKA
23	pdpka4 PDPKA
24}
25
26type loginIdentifier interface {
27	value() string
28}
29
30type loginIdentifierEmail string
31
32func (l loginIdentifierEmail) value() string { return string(l) }
33
34type loginIdentifierUsername string
35
36func (l loginIdentifierUsername) value() string { return string(l) }
37
38type loginIdentifierUID keybase1.UID
39
40func (l loginIdentifierUID) value() string { return keybase1.UID(l).String() }
41
42func (p PDPKA) String() string { return string(p) }
43
44type authPayload struct {
45	Body struct {
46		Auth struct {
47			Nonce   string `json:"nonce"`
48			Session string `json:"session"`
49		} `json:"auth"`
50		Key struct {
51			Email    string `json:"email,omitempty"`
52			Host     string `json:"host"`
53			Kid      string `json:"kid"`
54			UID      string `json:"uid,omitempty"`
55			Username string `json:"username,omitempty"`
56		} `json:"key"`
57		Type    string `json:"type"`
58		Version int    `json:"version"`
59	} `json:"body"`
60	Ctime    int    `json:"ctime"`
61	ExpireIn int    `json:"expire_in"`
62	Tag      string `json:"tag"`
63}
64
65func seedToPDPKAKey(seed []byte) (ret NaclSigningKeyPair, err error) {
66	if len(seed) != NaclSigningKeySecretSize {
67		return ret, fmt.Errorf("wrong size secret in computePDPKA (%d != %d)", len(seed), NaclSigningKeySecretSize)
68	}
69	var secret [NaclSigningKeySecretSize]byte
70	copy(secret[:], seed)
71	return MakeNaclSigningKeyPairFromSecret(secret)
72}
73
74func seedToPDPKAKID(seed []byte) (ret keybase1.KID, err error) {
75	var signingKey NaclSigningKeyPair
76	signingKey, err = seedToPDPKAKey(seed)
77	if err != nil {
78		return ret, err
79	}
80	return signingKey.GetKID(), nil
81}
82
83func computePDPKA(li loginIdentifier, seed []byte, loginSession []byte) (ret PDPKA, err error) {
84
85	var nonce [16]byte
86	if _, err = rand.Read(nonce[:]); err != nil {
87		return ret, err
88	}
89
90	var ap authPayload
91	ap.Body.Auth.Nonce = hex.EncodeToString(nonce[:])
92	ap.Body.Auth.Session = base64.StdEncoding.EncodeToString(loginSession)
93
94	var signingKey NaclSigningKeyPair
95	signingKey, err = seedToPDPKAKey(seed)
96	if err != nil {
97		return ret, err
98	}
99
100	ap.Body.Key.Kid = signingKey.GetKID().String()
101	ap.Body.Key.Host = CanonicalHost
102	ap.Body.Type = "auth"
103	ap.Body.Version = 1
104	ap.Tag = "signature"
105	ap.Ctime = int(time.Now().Unix())
106	ap.ExpireIn = 60 * 60 * 24 // good for one day, to deal with clock skew
107
108	switch li.(type) {
109	case loginIdentifierEmail:
110		ap.Body.Key.Email = li.value()
111	case loginIdentifierUsername:
112		ap.Body.Key.Username = li.value()
113	case loginIdentifierUID:
114		ap.Body.Key.UID = li.value()
115	}
116
117	var jsonRaw []byte
118	if jsonRaw, err = json.Marshal(ap); err != nil {
119		return ret, err
120	}
121
122	var sig string
123	if sig, _, err = signingKey.SignToString(jsonRaw); err != nil {
124		return ret, err
125	}
126
127	ret = PDPKA(sig)
128	return ret, nil
129}
130
131func computeLoginPackageFromUID(u keybase1.UID, ps *PassphraseStream, loginSession []byte) (ret PDPKALoginPackage, err error) {
132	return computeLoginPackage(loginIdentifierUID(u), ps, loginSession)
133}
134
135func computeLoginPackageFromEmailOrUsername(eou string, ps *PassphraseStream, loginSession []byte) (ret PDPKALoginPackage, err error) {
136	var li loginIdentifier
137	if CheckUsername.F(eou) {
138		li = loginIdentifierUsername(eou)
139	} else if CheckEmail.F(eou) {
140		li = loginIdentifierEmail(eou)
141	} else {
142		return ret, fmt.Errorf("expected an email or username; got neither (%s)", eou)
143	}
144	return computeLoginPackage(li, ps, loginSession)
145}
146
147func computeLoginPackage(li loginIdentifier, ps *PassphraseStream, loginSession []byte) (ret PDPKALoginPackage, err error) {
148	if ps == nil {
149		return ret, errors.New("computeLoginPackage failed due to nil PassphraseStream")
150	}
151	ret.pdpka5, err = computePDPKA(li, ps.EdDSASeed(), loginSession)
152	if err != nil {
153		return ret, err
154	}
155	ret.pdpka4, err = computePDPKA(li, ps.PWHash(), loginSession)
156	if err != nil {
157		return ret, err
158	}
159	return ret, nil
160}
161
162// PopulateArgs populates the given HTTP args with parameters in this PDPKA package.
163// Right now that includes v4 and v5 of the PDPKA login system.
164func (lp PDPKALoginPackage) PopulateArgs(h *HTTPArgs) {
165	h.Add("pdpka4", S{string(lp.pdpka4)})
166	h.Add("pdpka5", S{string(lp.pdpka5)})
167}
168
169// PDPKA4 gets the v4 of the PDPKA token for this login package
170func (lp PDPKALoginPackage) PDPKA4() PDPKA { return lp.pdpka4 }
171
172// PDPKA5 gets the v4 of the PDPKA token for this login package
173func (lp PDPKALoginPackage) PDPKA5() PDPKA { return lp.pdpka5 }
174