| @@ -68,6 +68,7 @@ func (a *Auth) Presigned(c *gin.Context) { | |||
| c.AbortWithStatusJSON(401, types.Failed(ecode.Unauthorized, "client cert not found for access key id %s", accID)) | |||
| return | |||
| } | |||
| c.Request.URL.Host = c.Request.Host | |||
| err := signer.VerifyPresigned(cliCert.VerifyKey, c.Request.Method, c.Request.URL) | |||
| if err != nil { | |||
| @@ -2,10 +2,8 @@ package types | |||
| import ( | |||
| "crypto/ecdsa" | |||
| "crypto/sha256" | |||
| "crypto/tls" | |||
| "crypto/x509" | |||
| "encoding/hex" | |||
| "encoding/pem" | |||
| "fmt" | |||
| "os" | |||
| @@ -74,9 +72,8 @@ func (c *ConfigJSON) Build() (Config, error) { | |||
| return Config{}, fmt.Errorf("invalid client cert %v: not an ECDSA public key", p) | |||
| } | |||
| pubKeyDer, _ := x509.MarshalPKIXPublicKey(pubKey) | |||
| pubHash := sha256.Sum256(pubKeyDer) | |||
| clientCerts[hex.EncodeToString(pubHash[:])] = &ClientCert{ | |||
| accKeyID := signer.CalcAccessKeyIDFromPublicKey(pubKey) | |||
| clientCerts[accKeyID] = &ClientCert{ | |||
| Cert: cert, | |||
| VerifyKey: signer.NewVerifyKey(pubKey), | |||
| } | |||
| @@ -3,8 +3,41 @@ package api | |||
| import ( | |||
| "crypto/tls" | |||
| "crypto/x509" | |||
| "fmt" | |||
| "os" | |||
| ) | |||
| type ConfigJSON struct { | |||
| EndPoint string `json:"endpoint"` | |||
| RootCA string `json:"rootCA"` | |||
| ClientCert string `json:"clientCert"` | |||
| ClientKey string `json:"clientKey"` | |||
| } | |||
| func (c *ConfigJSON) Build() (Config, error) { | |||
| rootCAPool := x509.NewCertPool() | |||
| rootCAPem, err := os.ReadFile(c.RootCA) | |||
| if err != nil { | |||
| return Config{}, fmt.Errorf("reading root CA: %w", err) | |||
| } | |||
| if !rootCAPool.AppendCertsFromPEM(rootCAPem) { | |||
| return Config{}, fmt.Errorf("parsing root CA failed") | |||
| } | |||
| cliCert, err := tls.LoadX509KeyPair(c.ClientCert, c.ClientKey) | |||
| if err != nil { | |||
| return Config{}, fmt.Errorf("loading client cert: %w", err) | |||
| } | |||
| return Config{ | |||
| EndPoint: c.EndPoint, | |||
| RootCA: rootCAPool, | |||
| Cert: cliCert, | |||
| }, nil | |||
| } | |||
| type Config struct { | |||
| EndPoint string | |||
| RootCA *x509.CertPool | |||
| @@ -1,12 +1,14 @@ | |||
| package api | |||
| import ( | |||
| "crypto/ecdsa" | |||
| "crypto/tls" | |||
| "net/http" | |||
| "gitlink.org.cn/cloudream/common/sdks" | |||
| "gitlink.org.cn/cloudream/jcs-pub/client/internal/http/auth" | |||
| "gitlink.org.cn/cloudream/jcs-pub/client/sdk/api" | |||
| "gitlink.org.cn/cloudream/jcs-pub/client/sdk/signer" | |||
| "golang.org/x/net/http2" | |||
| ) | |||
| @@ -26,6 +28,7 @@ func (r *response[T]) ToError() *sdks.CodeMessageError { | |||
| type Client struct { | |||
| cfg api.Config | |||
| httpCli *http.Client | |||
| signKey *signer.SignKey | |||
| } | |||
| func NewClient(cfg api.Config) *Client { | |||
| @@ -39,8 +42,12 @@ func NewClient(cfg api.Config) *Client { | |||
| }, | |||
| }, | |||
| } | |||
| privKey := cfg.Cert.PrivateKey.(*ecdsa.PrivateKey) | |||
| return &Client{ | |||
| cfg: cfg, | |||
| httpCli: httpCli, | |||
| signKey: signer.NewSignKey(privKey), | |||
| } | |||
| } | |||
| @@ -2,7 +2,11 @@ package api | |||
| import ( | |||
| "net/http" | |||
| "net/url" | |||
| "time" | |||
| "github.com/google/go-querystring/query" | |||
| "gitlink.org.cn/cloudream/jcs-pub/client/sdk/signer" | |||
| clitypes "gitlink.org.cn/cloudream/jcs-pub/client/types" | |||
| ) | |||
| @@ -113,33 +117,23 @@ func (c *PresignedService) ObjectCompleteMultipartUpload(req PresignedObjectComp | |||
| } | |||
| func (c *PresignedService) presign(req any, path string, method string, expireIn int) (string, error) { | |||
| // u, err := url.Parse(c.cfg.EndPoint) | |||
| // if err != nil { | |||
| // return "", err | |||
| // } | |||
| // u = u.JoinPath(path) | |||
| // us, err := query.Values(req) | |||
| // if err != nil { | |||
| // return "", err | |||
| // } | |||
| // us.Add("X-Expires", fmt.Sprintf("%v", expireIn)) | |||
| // u.RawQuery = us.Encode() | |||
| // prod := credentials.NewStaticCredentialsProvider(c.cfg.AccessKey, c.cfg.SecretKey, "") | |||
| // cred, err := prod.Retrieve(context.TODO()) | |||
| // if err != nil { | |||
| // return "", err | |||
| // } | |||
| // r, err := http.NewRequest(method, u.String(), nil) | |||
| // if err != nil { | |||
| // return "", err | |||
| // } | |||
| // signer := v4.NewSigner() | |||
| // signedURL, _, err := signer.PresignHTTP(context.Background(), cred, r, "", AuthService, AuthRegion, time.Now()) | |||
| // return signedURL, err | |||
| return "", nil | |||
| u, err := url.Parse(c.cfg.EndPoint) | |||
| if err != nil { | |||
| return "", err | |||
| } | |||
| u = u.JoinPath("/v1", path) | |||
| us, err := query.Values(req) | |||
| if err != nil { | |||
| return "", err | |||
| } | |||
| u.RawQuery = us.Encode() | |||
| err = signer.PresignURL(c.signKey, method, u, time.Now(), expireIn) | |||
| if err != nil { | |||
| return "", err | |||
| } | |||
| return u.String(), nil | |||
| } | |||
| @@ -9,9 +9,19 @@ import ( | |||
| ) | |||
| func Test_Presigned(t *testing.T) { | |||
| cli := NewClient(api.Config{ | |||
| EndPoint: "http://localhost:7890", | |||
| }) | |||
| cfg := api.ConfigJSON{ | |||
| EndPoint: "https://localhost:7890", | |||
| RootCA: ``, | |||
| ClientCert: ``, | |||
| ClientKey: ``, | |||
| } | |||
| c, err := cfg.Build() | |||
| if err != nil { | |||
| panic(err) | |||
| } | |||
| cli := NewClient(c) | |||
| Convey("下载文件", t, func() { | |||
| pre := cli.Presigned() | |||
| @@ -4,6 +4,7 @@ import ( | |||
| "crypto/ecdsa" | |||
| "crypto/rand" | |||
| "crypto/sha256" | |||
| "crypto/x509" | |||
| "encoding/hex" | |||
| "fmt" | |||
| "net/url" | |||
| @@ -33,6 +34,13 @@ type SignKey struct { | |||
| accessKeyID string | |||
| } | |||
| func NewSignKey(privKey *ecdsa.PrivateKey) *SignKey { | |||
| return &SignKey{ | |||
| privateKey: privKey, | |||
| accessKeyID: CalcAccessKeyIDFromPublicKey(&privKey.PublicKey), | |||
| } | |||
| } | |||
| func PresignURL(key *SignKey, method string, u *url.URL, signingTime time.Time, expiresSeconds int) error { | |||
| date := signingTime.Format(DateFormat) | |||
| @@ -111,6 +119,12 @@ func VerifyPresigned(key *VerifyKey, method string, u *url.URL) error { | |||
| return nil | |||
| } | |||
| func CalcAccessKeyIDFromPublicKey(publicKey *ecdsa.PublicKey) string { | |||
| pubKeyDer, _ := x509.MarshalPKIXPublicKey(publicKey) | |||
| pubHash := sha256.Sum256(pubKeyDer) | |||
| return hex.EncodeToString(pubHash[:]) | |||
| } | |||
| func makeStringToSign(method string, host string, path string, queries url.Values) string { | |||
| type queryKv struct { | |||
| Key string | |||
| @@ -132,6 +146,9 @@ func makeStringToSign(method string, host string, path string, queries url.Value | |||
| str.WriteByte('\n') | |||
| str.WriteString(host) | |||
| str.WriteByte('\n') | |||
| if !strings.HasPrefix(path, "/") { | |||
| str.WriteByte('/') | |||
| } | |||
| str.WriteString(path) | |||
| str.WriteByte('\n') | |||
| for _, kv := range queryKvs { | |||
| @@ -33,7 +33,7 @@ func Test_S2S(t *testing.T) { | |||
| Bucket: "pcm2-bucket2", | |||
| ProjectID: "", | |||
| }, | |||
| Credential: cortypes.OBSCred{ | |||
| Credential: &cortypes.OBSCred{ | |||
| AK: "", | |||
| SK: "", | |||
| }, | |||