fix(aliyundrive): device session signature error (#3398)

* fix signature

* fix: indent-error-flow [skip ci]
pull/3409/head
Andy Hsu 2023-02-14 19:17:21 +08:00 committed by GitHub
parent 2ca3e0b8bc
commit de66708b24
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 88 additions and 42 deletions

View File

@ -3,7 +3,6 @@ package aliyundrive
import ( import (
"bytes" "bytes"
"context" "context"
"crypto/ecdsa"
"crypto/sha1" "crypto/sha1"
"encoding/base64" "encoding/base64"
"encoding/hex" "encoding/hex"
@ -33,11 +32,6 @@ type AliDrive struct {
cron *cron.Cron cron *cron.Cron
DriveId string DriveId string
UserID string UserID string
signature string
nonce int
privateKey *ecdsa.PrivateKey
cron2 *cron.Cron
} }
func (d *AliDrive) Config() driver.Config { func (d *AliDrive) Config() driver.Config {
@ -69,39 +63,28 @@ func (d *AliDrive) Init(ctx context.Context) error {
log.Errorf("%+v", err) log.Errorf("%+v", err)
} }
}) })
if global.Has(d.UserID) {
// init deviceID return nil
if len(d.DeviceID) < 64 {
d.DeviceID = utils.GetSHA256Encode(d.DeviceID)
} }
// init deviceID
deviceID := utils.GetSHA256Encode(d.UserID)
// init privateKey // init privateKey
d.privateKey, _ = NewPrivateKey() privateKey, _ := NewPrivateKeyFromHex(deviceID)
state := State{
nonce: -1,
privateKey: privateKey,
deviceID: deviceID,
}
// store state
global.Store(d.UserID, &state)
// init signature // init signature
d.sign() return d.reSign()
d.createSession()
d.cron2 = cron.NewCron(time.Minute * 5)
d.cron2.Do(func() {
d.nonce++
d.sign()
err := d.renewSession()
if d.nonce >= 1073741823 || (err != nil && err.Error() == "device session signature error") {
d.nonce = 0
d.sign()
d.createSession()
}
})
return err
} }
func (d *AliDrive) Drop(ctx context.Context) error { func (d *AliDrive) Drop(ctx context.Context) error {
if d.cron != nil { if d.cron != nil {
d.cron.Stop() d.cron.Stop()
} }
if d.cron2 != nil {
d.cron2.Stop()
}
return nil return nil
} }

View File

@ -0,0 +1,16 @@
package aliyundrive
import (
"crypto/ecdsa"
"github.com/alist-org/alist/v3/pkg/generic_sync"
)
type State struct {
deviceID string
signature string
nonce int
privateKey *ecdsa.PrivateKey
}
var global = generic_sync.MapOf[string, *State]{}

View File

@ -7,8 +7,8 @@ import (
type Addition struct { type Addition struct {
driver.RootID driver.RootID
RefreshToken string `json:"refresh_token" required:"true"` RefreshToken string `json:"refresh_token" required:"true"`
DeviceID string `json:"device_id" required:"true"` //DeviceID string `json:"device_id" required:"true"`
OrderBy string `json:"order_by" type:"select" options:"name,size,updated_at,created_at"` OrderBy string `json:"order_by" type:"select" options:"name,size,updated_at,created_at"`
OrderDirection string `json:"order_direction" type:"select" options:"ASC,DESC"` OrderDirection string `json:"order_direction" type:"select" options:"ASC,DESC"`
RapidUpload bool `json:"rapid_upload"` RapidUpload bool `json:"rapid_upload"`

View File

@ -13,15 +13,20 @@ import (
"github.com/dustinxie/ecc" "github.com/dustinxie/ecc"
"github.com/go-resty/resty/v2" "github.com/go-resty/resty/v2"
"github.com/google/uuid" "github.com/google/uuid"
log "github.com/sirupsen/logrus"
) )
func (d *AliDrive) createSession() error { func (d *AliDrive) createSession() error {
state, ok := global.Load(d.UserID)
if !ok {
return fmt.Errorf("can't load user state, user_id: %s", d.UserID)
}
_, err, _ := d.request("https://api.aliyundrive.com/users/v1/users/device/create_session", http.MethodPost, func(req *resty.Request) { _, err, _ := d.request("https://api.aliyundrive.com/users/v1/users/device/create_session", http.MethodPost, func(req *resty.Request) {
req.SetBody(base.Json{ req.SetBody(base.Json{
"deviceName": "samsung", "deviceName": "samsung",
"modelName": "SM-G9810", "modelName": "SM-G9810",
"nonce": d.nonce, "nonce": state.nonce,
"pubKey": PublicKeyToHex(&d.privateKey.PublicKey), "pubKey": PublicKeyToHex(&state.privateKey.PublicKey),
"refreshToken": d.RefreshToken, "refreshToken": d.RefreshToken,
}) })
}, nil) }, nil)
@ -34,11 +39,38 @@ func (d *AliDrive) renewSession() error {
} }
func (d *AliDrive) sign() { func (d *AliDrive) sign() {
state, ok := global.Load(d.UserID)
if !ok {
log.Errorf("can't load user state, user_id: %s", d.UserID)
return
}
secpAppID := "5dde4e1bdf9e4966b387ba58f4b3fdc3" secpAppID := "5dde4e1bdf9e4966b387ba58f4b3fdc3"
singdata := fmt.Sprintf("%s:%s:%s:%d", secpAppID, d.DeviceID, d.UserID, d.nonce) singdata := fmt.Sprintf("%s:%s:%s:%d", secpAppID, state.deviceID, d.UserID, state.nonce)
hash := sha256.Sum256([]byte(singdata)) hash := sha256.Sum256([]byte(singdata))
data, _ := ecc.SignBytes(d.privateKey, hash[:], ecc.RecID|ecc.LowerS) data, _ := ecc.SignBytes(state.privateKey, hash[:], ecc.RecID|ecc.LowerS)
d.signature = hex.EncodeToString(data) state.signature = hex.EncodeToString(data)
}
func (d *AliDrive) reSign() error {
state, ok := global.Load(d.UserID)
if !ok {
return fmt.Errorf("can't load user state, user_id: %s", d.UserID)
}
state.nonce++
if state.nonce >= 1073741823 {
state.nonce = 0
}
d.sign()
if state.nonce == 0 {
return d.createSession()
}
err := d.renewSession()
if err != nil && err.Error() == "device session signature error" {
state.nonce = 0
d.sign()
return d.createSession()
}
return nil
} }
// do others that not defined in Driver interface // do others that not defined in Driver interface
@ -69,15 +101,23 @@ func (d *AliDrive) refreshToken() error {
func (d *AliDrive) request(url, method string, callback base.ReqCallback, resp interface{}) ([]byte, error, RespErr) { func (d *AliDrive) request(url, method string, callback base.ReqCallback, resp interface{}) ([]byte, error, RespErr) {
req := base.RestyClient.R() req := base.RestyClient.R()
state, ok := global.Load(d.UserID)
if !ok {
if url == "https://api.aliyundrive.com/v2/user/get" {
state = &State{}
} else {
return nil, fmt.Errorf("can't load user state, user_id: %s", d.UserID), RespErr{}
}
}
req.SetHeaders(map[string]string{ req.SetHeaders(map[string]string{
"Authorization": "Bearer\t" + d.AccessToken, "Authorization": "Bearer\t" + d.AccessToken,
"content-type": "application/json", "content-type": "application/json",
"origin": "https://www.aliyundrive.com", "origin": "https://www.aliyundrive.com",
"Referer": "https://aliyundrive.com/", "Referer": "https://aliyundrive.com/",
"X-Signature": d.signature, "X-Signature": state.signature,
"x-request-id": uuid.NewString(), "x-request-id": uuid.NewString(),
"X-Canary": "client=Android,app=adrive,version=v4.1.0", "X-Canary": "client=Android,app=adrive,version=v4.1.0",
"X-Device-Id": d.DeviceID, "X-Device-Id": state.deviceID,
}) })
if callback != nil { if callback != nil {
callback(req) callback(req)
@ -94,14 +134,21 @@ func (d *AliDrive) request(url, method string, callback base.ReqCallback, resp i
return nil, err, e return nil, err, e
} }
if e.Code != "" { if e.Code != "" {
if e.Code == "AccessTokenInvalid" { switch e.Code {
case "AccessTokenInvalid":
err = d.refreshToken() err = d.refreshToken()
if err != nil { if err != nil {
return nil, err, e return nil, err, e
} }
return d.request(url, method, callback, resp) case "DeviceSessionSignatureInvalid":
err = d.reSign()
if err != nil {
return nil, err, e
}
default:
return nil, errors.New(e.Message), e
} }
return nil, errors.New(e.Message), e return d.request(url, method, callback, resp)
} else if res.IsError() { } else if res.IsError() {
return nil, errors.New("bad status code " + res.Status()), e return nil, errors.New("bad status code " + res.Status()), e
} }