From 72eecbe356066c689db0c1ab18888242c231e974 Mon Sep 17 00:00:00 2001 From: Sydonian <794346190@qq.com> Date: Thu, 26 Jun 2025 09:58:33 +0800 Subject: [PATCH] =?UTF-8?q?=E8=B0=83=E8=AF=95=E7=AD=BE=E5=90=8D=E6=A0=A1?= =?UTF-8?q?=E9=AA=8C=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/internal/http/auth/auth.go | 1 + client/internal/http/types/config.go | 7 ++-- client/sdk/api/config.go | 33 ++++++++++++++++++ client/sdk/api/v1/client.go | 7 ++++ client/sdk/api/v1/presigned.go | 52 ++++++++++++---------------- client/sdk/api/v1/presigned_test.go | 16 +++++++-- client/sdk/signer/signer.go | 17 +++++++++ common/pkgs/storage/obs/obs_test.go | 2 +- 8 files changed, 97 insertions(+), 38 deletions(-) diff --git a/client/internal/http/auth/auth.go b/client/internal/http/auth/auth.go index 324da75..a65a053 100644 --- a/client/internal/http/auth/auth.go +++ b/client/internal/http/auth/auth.go @@ -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 { diff --git a/client/internal/http/types/config.go b/client/internal/http/types/config.go index 5e94f2f..f7cbb11 100644 --- a/client/internal/http/types/config.go +++ b/client/internal/http/types/config.go @@ -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), } diff --git a/client/sdk/api/config.go b/client/sdk/api/config.go index 389b2fc..5fe2dbc 100644 --- a/client/sdk/api/config.go +++ b/client/sdk/api/config.go @@ -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 diff --git a/client/sdk/api/v1/client.go b/client/sdk/api/v1/client.go index 6b4d24a..1385902 100644 --- a/client/sdk/api/v1/client.go +++ b/client/sdk/api/v1/client.go @@ -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), } } diff --git a/client/sdk/api/v1/presigned.go b/client/sdk/api/v1/presigned.go index e98deb6..82dcecf 100644 --- a/client/sdk/api/v1/presigned.go +++ b/client/sdk/api/v1/presigned.go @@ -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 } diff --git a/client/sdk/api/v1/presigned_test.go b/client/sdk/api/v1/presigned_test.go index cd5ea13..8dc3f48 100644 --- a/client/sdk/api/v1/presigned_test.go +++ b/client/sdk/api/v1/presigned_test.go @@ -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() diff --git a/client/sdk/signer/signer.go b/client/sdk/signer/signer.go index 78bb955..7e5c09a 100644 --- a/client/sdk/signer/signer.go +++ b/client/sdk/signer/signer.go @@ -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 { diff --git a/common/pkgs/storage/obs/obs_test.go b/common/pkgs/storage/obs/obs_test.go index 5b35554..85aeb67 100644 --- a/common/pkgs/storage/obs/obs_test.go +++ b/common/pkgs/storage/obs/obs_test.go @@ -33,7 +33,7 @@ func Test_S2S(t *testing.T) { Bucket: "pcm2-bucket2", ProjectID: "", }, - Credential: cortypes.OBSCred{ + Credential: &cortypes.OBSCred{ AK: "", SK: "", },