github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/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 }