优化结构

This commit is contained in:
Yvan
2026-08-27 22:33:43 +08:00
parent 5052835fcb
commit e407d3ac10
86 changed files with 3151 additions and 726 deletions
+15 -5
View File
@@ -89,15 +89,23 @@ func (s *Store) Delete(ctx context.Context, key string) error {
func (s *Store) Increment(ctx context.Context, key string, expiration time.Duration) (int64, error) {
if client := s.client(); client != nil {
if expiration > 0 {
expirationMS := expiration.Milliseconds()
if expirationMS < 1 {
expirationMS = 1
}
const incrementWithTTL = `
local value = redis.call('INCR', KEYS[1])
if redis.call('PTTL', KEYS[1]) < 0 then
redis.call('PEXPIRE', KEYS[1], ARGV[1])
end
return value`
return client.Eval(ctx, incrementWithTTL, []string{key}, expirationMS).Int64()
}
value, err := client.Incr(ctx, key).Result()
if err != nil {
return 0, err
}
if value == 1 && expiration > 0 {
if err := client.Expire(ctx, key, expiration).Err(); err != nil {
return 0, err
}
}
return value, nil
}
s.mu.Lock()
@@ -120,6 +128,8 @@ func (s *Store) Increment(ctx context.Context, key string, expiration time.Durat
} else {
entry.expiresAt = time.Time{}
}
} else if entry.expiresAt.IsZero() && expiration > 0 {
entry.expiresAt = time.Now().Add(expiration)
}
s.memory[key] = entry
return value, nil
+15
View File
@@ -40,3 +40,18 @@ func TestStoreIncrementRecreatesExpiredKeyWithoutStaleExpiry(t *testing.T) {
t.Fatalf("Get() after recreation = %q, %v, %v", value, ok, err)
}
}
func TestStoreIncrementAddsTTLToExistingPersistentCounter(t *testing.T) {
store := New(nil)
ctx := context.Background()
if err := store.Set(ctx, "counter", "5", 0); err != nil {
t.Fatal(err)
}
if value, err := store.Increment(ctx, "counter", time.Millisecond); err != nil || value != 6 {
t.Fatalf("Increment() = %d, %v", value, err)
}
time.Sleep(5 * time.Millisecond)
if _, ok, err := store.Get(ctx, "counter"); err != nil || ok {
t.Fatalf("persistent counter did not expire: ok=%v err=%v", ok, err)
}
}
+42 -16
View File
@@ -1,12 +1,17 @@
package payment
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/sha256"
"crypto/x509"
"encoding/asn1"
"encoding/base64"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"math/big"
"strings"
"time"
)
@@ -20,12 +25,16 @@ const (
var (
appleJWSLeafExtensionOID = asn1.ObjectIdentifier{1, 2, 840, 113635, 100, 6, 11, 1}
appleJWSIntermediateExtensionOID = asn1.ObjectIdentifier{1, 2, 840, 113635, 100, 6, 2, 1}
appleTrustedRootFingerprints = map[string]struct{}{
"63343abfb89a6a03ebb57e9b3f5fa7be7c4f5c756f3017b3a8c488c3653e9179": {},
}
)
// validateAppleJWSChain binds the leaf certificate used by GoPay for JWS
// signature verification to the intermediate and root certificates that
// GoPay validates against its embedded Apple trust anchor.
func validateAppleJWSChain(signedPayload string) error {
return validateAppleJWSChainWithRoots(signedPayload, appleTrustedRootFingerprints)
}
func validateAppleJWSChainWithRoots(signedPayload string, trustedRoots map[string]struct{}) error {
headerSegment, payloadSegment, signatureSegment, err := splitAppleJWS(signedPayload)
if err != nil {
return err
@@ -73,6 +82,10 @@ func validateAppleJWSChain(signedPayload string) error {
}
certificates = append(certificates, certificate)
}
rootFingerprint := sha256.Sum256(certificates[2].Raw)
if _, trusted := trustedRoots[hex.EncodeToString(rootFingerprint[:])]; !trusted {
return errors.New("Apple JWS 根证书不受信任")
}
if err = certificates[0].CheckSignatureFrom(certificates[1]); err != nil {
return fmt.Errorf("Apple JWS 叶子证书不属于声明的证书链: %w", err)
}
@@ -101,19 +114,20 @@ func validateAppleJWSChain(signedPayload string) error {
return errors.New("Apple JWS 中间证书缺少 Apple 签名扩展")
}
if hasVerificationTime {
roots := x509.NewCertPool()
roots.AddCert(certificates[2])
intermediates := x509.NewCertPool()
intermediates.AddCert(certificates[1])
if _, err = certificates[0].Verify(x509.VerifyOptions{
Roots: roots,
Intermediates: intermediates,
KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageAny},
CurrentTime: verificationTime,
}); err != nil {
return fmt.Errorf("验证 Apple JWS 证书链: %w", err)
}
if !hasVerificationTime {
verificationTime = time.Now()
}
roots := x509.NewCertPool()
roots.AddCert(certificates[2])
intermediates := x509.NewCertPool()
intermediates.AddCert(certificates[1])
if _, err = certificates[0].Verify(x509.VerifyOptions{
Roots: roots,
Intermediates: intermediates,
KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageAny},
CurrentTime: verificationTime,
}); err != nil {
return fmt.Errorf("验证 Apple JWS 证书链: %w", err)
}
if certificates[0].IsCA {
return errors.New("Apple JWS 叶子证书不能是 CA")
@@ -121,6 +135,18 @@ func validateAppleJWSChain(signedPayload string) error {
if certificates[0].KeyUsage != 0 && certificates[0].KeyUsage&x509.KeyUsageDigitalSignature == 0 {
return errors.New("Apple JWS 叶子证书不允许数字签名")
}
publicKey, ok := certificates[0].PublicKey.(*ecdsa.PublicKey)
if !ok || publicKey.Curve != elliptic.P256() {
return errors.New("Apple JWS 叶子证书不是 P-256 ECDSA 公钥")
}
signature, err := base64.RawURLEncoding.DecodeString(signatureSegment)
if err != nil || len(signature) != 64 {
return errors.New("Apple JWS ES256 签名格式无效")
}
digest := sha256.Sum256([]byte(headerSegment + "." + payloadSegment))
if !ecdsa.Verify(publicKey, digest[:], new(big.Int).SetBytes(signature[:32]), new(big.Int).SetBytes(signature[32:])) {
return errors.New("Apple JWS 签名校验失败")
}
return nil
}
+65 -16
View File
@@ -4,10 +4,12 @@ import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/sha256"
"crypto/x509"
"crypto/x509/pkix"
"encoding/asn1"
"encoding/base64"
"encoding/hex"
"encoding/json"
"math/big"
"testing"
@@ -16,7 +18,8 @@ import (
func TestValidateAppleJWSChain(t *testing.T) {
chain := testAppleCertificateChain(t)
if err := validateAppleJWSChain(testAppleJWS(t, "ES256", chain, time.Now())); err != nil {
jws := testAppleJWS(t, "ES256", chain, time.Now())
if err := validateAppleJWSChainWithRoots(jws, testAppleTrustedRoots(t, chain)); err != nil {
t.Fatalf("validateAppleJWSChain() error = %v", err)
}
}
@@ -25,17 +28,30 @@ func TestValidateAppleJWSChainRejectsUnrelatedLeaf(t *testing.T) {
chain := testAppleCertificateChain(t)
attackerChain := testAppleCertificateChain(t)
chain[0] = attackerChain[0]
if err := validateAppleJWSChain(testAppleJWS(t, "ES256", chain, time.Now())); err == nil {
if err := validateAppleJWSChainWithRoots(testAppleJWS(t, "ES256", chain, time.Now()), testAppleTrustedRoots(t, chain)); err == nil {
t.Fatal("validateAppleJWSChain() accepted a leaf outside the declared chain")
}
}
func TestValidateAppleJWSChainRejectsInvalidSignature(t *testing.T) {
chain := testAppleCertificateChain(t)
jws := testAppleJWS(t, "ES256", chain, time.Now())
header, payload, _, err := splitAppleJWS(jws)
if err != nil {
t.Fatal(err)
}
tampered := header + "." + payload + "." + base64.RawURLEncoding.EncodeToString(make([]byte, 64))
if err = validateAppleJWSChainWithRoots(tampered, testAppleTrustedRoots(t, chain)); err == nil {
t.Fatal("validateAppleJWSChain accepted an invalid ES256 signature")
}
}
func TestValidateAppleJWSChainRejectsUnexpectedAlgorithmAndShape(t *testing.T) {
chain := testAppleCertificateChain(t)
if err := validateAppleJWSChain(testAppleJWS(t, "ES384", chain, time.Now())); err == nil {
if err := validateAppleJWSChainWithRoots(testAppleJWS(t, "ES384", chain, time.Now()), testAppleTrustedRoots(t, chain)); err == nil {
t.Fatal("validateAppleJWSChain() accepted a non-ES256 algorithm")
}
if err := validateAppleJWSChain(testAppleJWS(t, "ES256", chain[:2], time.Now())); err == nil {
if err := validateAppleJWSChainWithRoots(testAppleJWS(t, "ES256", chain[:2], time.Now()), nil); err == nil {
t.Fatal("validateAppleJWSChain() accepted an incomplete x5c chain")
}
}
@@ -43,14 +59,14 @@ func TestValidateAppleJWSChainRejectsUnexpectedAlgorithmAndShape(t *testing.T) {
func TestValidateAppleJWSChainRejectsAdditionalCertificates(t *testing.T) {
chain := testAppleCertificateChain(t)
chain = append(chain, chain[2])
if err := validateAppleJWSChain(testAppleJWS(t, "ES256", chain, time.Now())); err == nil {
if err := validateAppleJWSChainWithRoots(testAppleJWS(t, "ES256", chain, time.Now()), testAppleTrustedRoots(t, chain[:3])); err == nil {
t.Fatal("validateAppleJWSChain() accepted additional x5c certificates")
}
}
func TestValidateAppleJWSChainRejectsNonAppleSigningCertificates(t *testing.T) {
chain := testAppleCertificateChainWithoutAppleExtensions(t)
if err := validateAppleJWSChain(testAppleJWS(t, "ES256", chain, time.Now())); err == nil {
if err := validateAppleJWSChainWithRoots(testAppleJWS(t, "ES256", chain, time.Now()), testAppleTrustedRoots(t, chain)); err == nil {
t.Fatal("validateAppleJWSChain() accepted certificates without Apple signing extensions")
}
}
@@ -58,7 +74,7 @@ func TestValidateAppleJWSChainRejectsNonAppleSigningCertificates(t *testing.T) {
func TestValidateAppleJWSChainUsesSignedDateForHistoricalPayload(t *testing.T) {
signedAt := time.Now().AddDate(-2, 0, 0)
chain := testAppleCertificateChainAt(t, signedAt, true)
if err := validateAppleJWSChain(testAppleJWS(t, "ES256", chain, signedAt)); err != nil {
if err := validateAppleJWSChainWithRoots(testAppleJWS(t, "ES256", chain, signedAt), testAppleTrustedRoots(t, chain)); err != nil {
t.Fatalf("validateAppleJWSChain() rejected historical payload: %v", err)
}
}
@@ -78,17 +94,17 @@ func TestValidateAppleJWSChainUsesNestedSignedDateForNotification(t *testing.T)
if err != nil {
t.Fatal(err)
}
outer := base64.RawURLEncoding.EncodeToString(header) + "." + base64.RawURLEncoding.EncodeToString(payload) + ".signature"
if err := validateAppleJWSChain(outer); err != nil {
outer := testAppleSignedJWS(t, header, payload, chain)
if err := validateAppleJWSChainWithRoots(outer, testAppleTrustedRoots(t, chain)); err != nil {
t.Fatalf("validateAppleJWSChain() rejected historical notification: %v", err)
}
}
func TestValidateAppleJWSChainAcceptsExpiredChainWithoutSignedDate(t *testing.T) {
func TestValidateAppleJWSChainRejectsExpiredChainWithoutSignedDate(t *testing.T) {
signedAt := time.Now().AddDate(-2, 0, 0)
chain := testAppleCertificateChainAt(t, signedAt, true)
if err := validateAppleJWSChain(testAppleJWSWithoutDate(t, chain)); err != nil {
t.Fatalf("validateAppleJWSChain() rejected an expired chain without signedDate: %v", err)
if err := validateAppleJWSChainWithRoots(testAppleJWSWithoutDate(t, chain), testAppleTrustedRoots(t, chain)); err == nil {
t.Fatal("validateAppleJWSChain() accepted an expired chain without signedDate")
}
}
@@ -96,7 +112,7 @@ func TestValidateAppleJWSChainRejectsBrokenChainWithoutSignedDate(t *testing.T)
chain := testAppleCertificateChain(t)
attackerChain := testAppleCertificateChain(t)
chain[0] = attackerChain[0]
if err := validateAppleJWSChain(testAppleJWSWithoutDate(t, chain)); err == nil {
if err := validateAppleJWSChainWithRoots(testAppleJWSWithoutDate(t, chain), testAppleTrustedRoots(t, chain)); err == nil {
t.Fatal("validateAppleJWSChain() accepted a broken chain without signedDate")
}
}
@@ -169,11 +185,13 @@ func testAppleCertificateChainAt(t *testing.T, validAt time.Time, includeAppleEx
}}
}
leafDER := testAppleCertificate(t, leafTemplate, intermediate, &leafKey.PublicKey, intermediateKey)
return []string{
chain := []string{
base64.StdEncoding.EncodeToString(leafDER),
base64.StdEncoding.EncodeToString(intermediateDER),
base64.StdEncoding.EncodeToString(rootDER),
}
testAppleLeafKeys[chain[0]] = leafKey
return chain
}
func testAppleECDSAKey(t *testing.T) *ecdsa.PrivateKey {
@@ -204,7 +222,7 @@ func testAppleJWS(t *testing.T, algorithm string, chain []string, signedAt time.
if err != nil {
t.Fatal(err)
}
return base64.RawURLEncoding.EncodeToString(header) + "." + base64.RawURLEncoding.EncodeToString(payload) + ".signature"
return testAppleSignedJWS(t, header, payload, chain)
}
func testAppleJWSWithoutDate(t *testing.T, chain []string) string {
@@ -217,5 +235,36 @@ func testAppleJWSWithoutDate(t *testing.T, chain []string) string {
if err != nil {
t.Fatal(err)
}
return base64.RawURLEncoding.EncodeToString(header) + "." + base64.RawURLEncoding.EncodeToString(payload) + ".signature"
return testAppleSignedJWS(t, header, payload, chain)
}
var testAppleLeafKeys = map[string]*ecdsa.PrivateKey{}
func testAppleSignedJWS(t *testing.T, header, payload []byte, chain []string) string {
t.Helper()
headerSegment := base64.RawURLEncoding.EncodeToString(header)
payloadSegment := base64.RawURLEncoding.EncodeToString(payload)
key := testAppleLeafKeys[chain[0]]
if key == nil {
t.Fatal("missing test Apple leaf key")
}
digest := sha256.Sum256([]byte(headerSegment + "." + payloadSegment))
r, s, err := ecdsa.Sign(rand.Reader, key, digest[:])
if err != nil {
t.Fatal(err)
}
signature := make([]byte, 64)
r.FillBytes(signature[:32])
s.FillBytes(signature[32:])
return headerSegment + "." + payloadSegment + "." + base64.RawURLEncoding.EncodeToString(signature)
}
func testAppleTrustedRoots(t *testing.T, chain []string) map[string]struct{} {
t.Helper()
rootDER, err := base64.StdEncoding.DecodeString(chain[2])
if err != nil {
t.Fatal(err)
}
fingerprint := sha256.Sum256(rootDER)
return map[string]struct{}{hex.EncodeToString(fingerprint[:]): {}}
}
+2 -8
View File
@@ -56,17 +56,11 @@ func first(values map[string]string, keys ...string) string {
}
func text(values map[string]any, key string) string {
value, _ := values[key].(string)
return value
return paymentutil.Text(values, key)
}
func firstAny(values map[string]any, keys ...string) string {
for _, key := range keys {
if value, ok := values[key].(string); ok && value != "" {
return value
}
}
return ""
return paymentutil.FirstText(values, keys...)
}
// Compatibility shims keep channel adapters focused on protocol and
+29
View File
@@ -133,6 +133,35 @@ func (a *vendorPaymentAdapter) call(ctx context.Context, endpointKey string, pay
}
result := &bizpayment.PaymentResult{Provider: a.provider, Status: "created", TradeNo: tradeNo, Payload: ensureJSON(body)}
if endpointKey != "query_url" {
if object := jsonObject(body); object != nil {
operation := strings.TrimSuffix(endpointKey, "_url")
statusField := text(c, operation+"_status_field")
if statusField == "" {
statusField = text(c, "query_status_field")
}
state := ""
if statusField != "" {
state = stringAtPath(object, statusField)
}
if state == "" {
for _, key := range []string{"status", "trade_status", "order_status", "pay_status", "refund_status"} {
if value := stringAtPath(object, key); value != "" {
state = value
break
}
}
}
if state != "" {
switch {
case containsFold(configuredValues(c, operation+"_failure_values"), state):
result.Status = "failed"
case containsFold(configuredValues(c, operation+"_success_values"), state):
result.Status = "created"
default:
result.Status = normalizePaymentStatus(state, "created")
}
}
}
return result, nil
}
object := jsonObject(body)
@@ -1,10 +1,15 @@
package payment
import (
"context"
"crypto/sha256"
"encoding/hex"
"net/http"
"net/http/httptest"
"strings"
"testing"
bizpayment "kra/internal/biz/payment"
)
func TestChinaumsCallbackAuthorizationVerification(t *testing.T) {
@@ -31,3 +36,23 @@ func TestChinaumsCallbackAuthorizationVerification(t *testing.T) {
t.Fatalf("mismatched Chinaums AppId error = %v", err)
}
}
func TestVendorRefundMapsBusinessFailureStatus(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(`{"status":"FAIL","message":"rejected"}`))
}))
defer server.Close()
adapter := &vendorPaymentAdapter{provider: "vendor", profile: vendorSFT}
result, err := adapter.Refund(context.Background(), &bizpayment.PaymentRefundRequest{TradeNo: "trade-1", RefundNo: "refund-1", Amount: 100, TotalAmount: 100, Currency: "CNY"}, map[string]any{
"refund_url": server.URL,
"app_key": "secret",
})
if err != nil {
t.Fatalf("Refund() error = %v", err)
}
if result == nil || result.Status != "failed" {
t.Fatalf("Refund() result = %#v, want failed status", result)
}
}