hydroxide-push/protonmail/auth.go

242 lines
4.8 KiB
Go
Raw Normal View History

2017-08-22 01:04:16 +03:00
package protonmail
import (
"encoding/base64"
2017-08-22 11:36:43 +03:00
"errors"
2017-08-22 01:04:16 +03:00
"net/http"
2017-08-22 11:36:43 +03:00
"strings"
"time"
2017-08-22 01:04:16 +03:00
2017-08-22 11:36:43 +03:00
"golang.org/x/crypto/openpgp"
2017-08-22 01:04:16 +03:00
)
type authInfoReq struct {
2017-08-22 11:16:20 +03:00
ClientID string
2017-08-22 01:04:16 +03:00
ClientSecret string
2017-08-22 11:16:20 +03:00
Username string
2017-08-22 01:04:16 +03:00
}
2017-08-22 10:21:50 +03:00
type AuthInfo struct {
2017-08-22 11:16:20 +03:00
TwoFactor int
version int
modulus string
2017-08-22 10:21:50 +03:00
serverEphemeral string
2017-08-22 11:16:20 +03:00
salt string
srpSession string
2017-08-22 10:21:50 +03:00
}
2017-08-22 01:04:16 +03:00
type AuthInfoResp struct {
resp
2017-08-22 10:21:50 +03:00
AuthInfo
2017-08-22 11:16:20 +03:00
Version int
Modulus string
2017-08-22 01:04:16 +03:00
ServerEphemeral string
2017-08-22 11:16:20 +03:00
Salt string
SRPSession string
2017-08-22 01:04:16 +03:00
}
2017-08-22 10:21:50 +03:00
func (resp *AuthInfoResp) authInfo() *AuthInfo {
info := &resp.AuthInfo
info.version = resp.Version
info.modulus = resp.Modulus
info.serverEphemeral = resp.ServerEphemeral
info.salt = resp.Salt
info.srpSession = resp.SRPSession
return info
2017-08-22 01:04:16 +03:00
}
func (c *Client) AuthInfo(username string) (*AuthInfo, error) {
reqData := &authInfoReq{
2017-08-22 11:16:20 +03:00
ClientID: c.ClientID,
2017-08-22 01:04:16 +03:00
ClientSecret: c.ClientSecret,
2017-08-22 11:16:20 +03:00
Username: username,
2017-08-22 01:04:16 +03:00
}
req, err := c.newJSONRequest(http.MethodPost, "/auth/info", reqData)
if err != nil {
return nil, err
}
var respData AuthInfoResp
if err := c.doJSON(req, &respData); err != nil {
return nil, err
}
2017-08-22 10:21:50 +03:00
return respData.authInfo(), nil
2017-08-22 01:04:16 +03:00
}
type authReq struct {
2017-08-22 11:16:20 +03:00
ClientID string
ClientSecret string
Username string
SRPSession string
2017-08-22 01:04:16 +03:00
ClientEphemeral string
2017-08-22 11:16:20 +03:00
ClientProof string
TwoFactorCode string
2017-08-22 01:04:16 +03:00
}
type PasswordMode int
const (
PasswordSingle PasswordMode = 1
2017-08-22 11:16:20 +03:00
PasswordTwo = 2
2017-08-22 01:04:16 +03:00
)
2017-08-22 10:41:47 +03:00
type Auth struct {
ExpiresAt time.Time
2017-08-22 11:16:20 +03:00
Scope string
UID string `json:"Uid"`
2017-08-22 01:04:16 +03:00
RefreshToken string
2017-08-22 11:16:20 +03:00
EventID string
2017-08-22 01:04:16 +03:00
PasswordMode PasswordMode
2017-08-22 10:41:47 +03:00
2017-08-22 11:36:43 +03:00
accessToken string
privateKey string
keySalt string
2017-08-22 10:41:47 +03:00
}
type authResp struct {
resp
Auth
ExpiresIn int
2017-08-22 11:36:43 +03:00
AccessToken string
TokenType string
2017-08-22 10:41:47 +03:00
ServerProof string
2017-08-22 11:16:20 +03:00
PrivateKey string
KeySalt string
2017-08-22 01:04:16 +03:00
}
2017-08-22 10:41:47 +03:00
func (resp *authResp) auth() *Auth {
auth := &resp.Auth
auth.ExpiresAt = time.Now().Add(time.Duration(resp.ExpiresIn) * time.Second)
2017-08-22 11:36:43 +03:00
auth.accessToken = resp.AccessToken
2017-08-22 10:41:47 +03:00
auth.privateKey = resp.PrivateKey
auth.keySalt = resp.KeySalt
return auth
}
func (c *Client) Auth(username, password, twoFactorCode string, info *AuthInfo) (*Auth, error) {
2017-08-22 01:04:16 +03:00
if info == nil {
var err error
if info, err = c.AuthInfo(username); err != nil {
2017-08-22 10:41:47 +03:00
return nil, err
2017-08-22 01:04:16 +03:00
}
}
proofs, err := srp([]byte(password), info)
if err != nil {
2017-08-22 10:41:47 +03:00
return nil, err
2017-08-22 01:04:16 +03:00
}
reqData := &authReq{
2017-08-22 11:16:20 +03:00
ClientID: c.ClientID,
ClientSecret: c.ClientSecret,
Username: username,
SRPSession: info.srpSession,
2017-08-22 01:04:16 +03:00
ClientEphemeral: base64.StdEncoding.EncodeToString(proofs.clientEphemeral),
2017-08-22 11:16:20 +03:00
ClientProof: base64.StdEncoding.EncodeToString(proofs.clientProof),
TwoFactorCode: twoFactorCode,
2017-08-22 01:04:16 +03:00
}
req, err := c.newJSONRequest(http.MethodPost, "/auth", reqData)
if err != nil {
2017-08-22 10:41:47 +03:00
return nil, err
2017-08-22 01:04:16 +03:00
}
var respData authResp
if err := c.doJSON(req, &respData); err != nil {
2017-08-22 10:41:47 +03:00
return nil, err
2017-08-22 01:04:16 +03:00
}
if err := proofs.VerifyServerProof(respData.ServerProof); err != nil {
2017-08-22 10:41:47 +03:00
return nil, err
2017-08-22 01:04:16 +03:00
}
2017-08-22 10:41:47 +03:00
return respData.auth(), nil
2017-08-22 01:04:16 +03:00
}
2017-08-22 11:36:43 +03:00
2017-08-22 11:51:48 +03:00
type authRefreshReq struct {
2017-08-22 14:22:27 +03:00
ClientID string
UID string `json:"Uid"`
2017-08-22 11:51:48 +03:00
RefreshToken string
2017-08-22 13:07:31 +03:00
// Unused but required
ResponseType string
2017-08-22 14:22:27 +03:00
GrantType string
RedirectURI string
State string
2017-08-22 11:51:48 +03:00
}
func (c *Client) AuthRefresh(expiredAuth *Auth) (*Auth, error) {
2017-08-22 11:51:48 +03:00
reqData := &authRefreshReq{
2017-08-22 14:22:27 +03:00
ClientID: c.ClientID,
UID: expiredAuth.UID,
RefreshToken: expiredAuth.RefreshToken,
2017-08-22 11:51:48 +03:00
}
req, err := c.newJSONRequest(http.MethodPost, "/auth/refresh", reqData)
if err != nil {
return nil, err
}
var respData authResp
if err := c.doJSON(req, &respData); err != nil {
return nil, err
}
auth := respData.auth()
//auth.EventID = expiredAuth.EventID
auth.PasswordMode = expiredAuth.PasswordMode
return auth, nil
2017-08-22 11:51:48 +03:00
}
func (c *Client) Unlock(auth *Auth, passphrase string) (openpgp.EntityList, error) {
passphraseBytes := []byte(passphrase)
2017-08-22 11:36:43 +03:00
if auth.PasswordMode == PasswordSingle {
keySalt, err := base64.StdEncoding.DecodeString(auth.keySalt)
if err != nil {
return nil, err
}
2017-08-22 11:51:48 +03:00
passphraseBytes, err = computeKeyPassword(passphraseBytes, keySalt)
2017-08-22 11:36:43 +03:00
if err != nil {
return nil, err
}
}
keyRing, err := openpgp.ReadArmoredKeyRing(strings.NewReader(auth.privateKey))
if err != nil {
return nil, err
}
if len(keyRing) == 0 {
return nil, errors.New("auth key ring is empty")
}
for _, e := range keyRing {
2017-08-22 11:51:48 +03:00
if err := e.PrivateKey.Decrypt(passphraseBytes); err != nil {
2017-08-22 11:36:43 +03:00
return nil, err
}
}
c.uid = auth.UID
c.accessToken = auth.accessToken
c.keyRing = keyRing
return keyRing, nil
}
2017-08-22 12:19:21 +03:00
func (c *Client) Logout() error {
req, err := c.newRequest(http.MethodDelete, "/auth", nil)
if err != nil {
return err
}
if err := c.doJSON(req, nil); err != nil {
return err
}
c.uid = ""
c.accessToken = ""
c.keyRing = nil
return nil
}