github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/libkb/pdpka.go (about) 1 package libkb 2 3 import ( 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. 17 type PDPKA string 18 19 // PDPKALoginPackage contains all relevant PDPKA versions in use at this 20 // time. For now, versions 4 and 5. 21 type PDPKALoginPackage struct { 22 pdpka5 PDPKA 23 pdpka4 PDPKA 24 } 25 26 type loginIdentifier interface { 27 value() string 28 } 29 30 type loginIdentifierEmail string 31 32 func (l loginIdentifierEmail) value() string { return string(l) } 33 34 type loginIdentifierUsername string 35 36 func (l loginIdentifierUsername) value() string { return string(l) } 37 38 type loginIdentifierUID keybase1.UID 39 40 func (l loginIdentifierUID) value() string { return keybase1.UID(l).String() } 41 42 func (p PDPKA) String() string { return string(p) } 43 44 type 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 65 func 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 74 func 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 83 func 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 131 func computeLoginPackageFromUID(u keybase1.UID, ps *PassphraseStream, loginSession []byte) (ret PDPKALoginPackage, err error) { 132 return computeLoginPackage(loginIdentifierUID(u), ps, loginSession) 133 } 134 135 func 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 147 func 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. 164 func (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 170 func (lp PDPKALoginPackage) PDPKA4() PDPKA { return lp.pdpka4 } 171 172 // PDPKA5 gets the v4 of the PDPKA token for this login package 173 func (lp PDPKALoginPackage) PDPKA5() PDPKA { return lp.pdpka5 }