package ecdsax_test import ( "bytes" "crypto/ecdsa" "crypto/ed25519" "crypto/elliptic" "crypto/rand" "crypto/rsa" "crypto/x509" "encoding/base64" "encoding/pem" "math/big" mrand "math/rand" "os" "path/filepath" "runtime" "testing" "code.yun.ink/pkg/encryptx/ecdsax" ) // curveCase 描述一条受支持的曲线,便于表驱动覆盖 P-256/P-384/P-521。 type curveCase struct { name string curve elliptic.Curve } func supportedCurves() []curveCase { return []curveCase{ {"P-256", elliptic.P256()}, {"P-384", elliptic.P384()}, {"P-521", elliptic.P521()}, } } // requireError 断言操作失败返回了 error(而非 panic 或静默成功)。 func requireError(t *testing.T, name string, err error) { t.Helper() if err == nil { t.Errorf("%s: 期望返回错误, 实际返回 nil", name) } } // mustGenerateKey 生成指定曲线的密钥,供其余用例复用。 func mustGenerateKey(t *testing.T, curve elliptic.Curve) *ecdsa.PrivateKey { t.Helper() priv, err := ecdsax.GenerateKey(curve) if err != nil { t.Fatalf("GenerateKey 返回错误: %v", err) } if priv == nil { t.Fatal("GenerateKey 返回 nil 私钥且无错误") } if priv.Curve != curve { t.Fatalf("生成的私钥曲线 = %v, 期望 %v", priv.Curve, curve) } if priv.D == nil || priv.X == nil || priv.Y == nil { t.Fatal("生成的私钥缺少 D 或 X/Y 字段") } return priv } // TestGenerateKeyRejectsUnsupportedCurves 覆盖:nil 与 P-224 必须被拒绝。 func TestGenerateKeyRejectsUnsupportedCurves(t *testing.T) { cases := []struct { name string curve elliptic.Curve }{ {"nil 曲线", nil}, {"P-224 曲线", elliptic.P224()}, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { priv, err := ecdsax.GenerateKey(tc.curve) requireError(t, "GenerateKey("+tc.name+")", err) if priv != nil { t.Error("GenerateKey 失败时应返回 nil 私钥") } privatePEM, publicPEM, err := ecdsax.GenerateKeyPEM(tc.curve) requireError(t, "GenerateKeyPEM("+tc.name+")", err) if privatePEM != nil || publicPEM != nil { t.Error("GenerateKeyPEM 失败时应返回 nil PEM") } dir := t.TempDir() requireError(t, "SaveKeyPair("+tc.name+")", ecdsax.SaveKeyPair( filepath.Join(dir, "priv.pem"), filepath.Join(dir, "pub.pem"), tc.curve)) }) } } // TestSignVerifyRoundTrip 覆盖:P-256/P-384/P-521 三条曲线均能完成签名/验签往返。 func TestSignVerifyRoundTrip(t *testing.T) { for _, tc := range supportedCurves() { t.Run(tc.name, func(t *testing.T) { priv := mustGenerateKey(t, tc.curve) pub := &priv.PublicKey message := []byte("encryptx ecdsax 往返测试消息 / " + tc.name) signature, err := ecdsax.Sign(priv, message) if err != nil { t.Fatalf("Sign 返回错误: %v", err) } if len(signature) == 0 { t.Fatal("Sign 返回空签名") } if err := ecdsax.Verify(pub, message, signature); err != nil { t.Fatalf("Verify 应通过, 实际返回错误: %v", err) } // ECDSA 每次签名使用新的随机 nonce,两次结果都应能通过校验。 second, err := ecdsax.Sign(priv, message) if err != nil { t.Fatalf("第二次 Sign 返回错误: %v", err) } if err := ecdsax.Verify(pub, message, second); err != nil { t.Fatalf("第二次签名的 Verify 应通过, 实际返回错误: %v", err) } if bytes.Equal(signature, second) { t.Log("提示: 两次 ECDSA 签名完全相同, 概率极低, 请确认随机源正常") } // 空消息与较长消息同样应能往返。 for _, msg := range [][]byte{nil, {}, bytes.Repeat([]byte("B"), 1<<16)} { sig, err := ecdsax.Sign(priv, msg) if err != nil { t.Fatalf("Sign(len=%d) 返回错误: %v", len(msg), err) } if err := ecdsax.Verify(pub, msg, sig); err != nil { t.Fatalf("Verify(len=%d) 应通过, 实际返回错误: %v", len(msg), err) } } }) } } // TestPEMRoundTrip 覆盖:GenerateKeyPEM -> 解析 -> 用公钥验签成功,且曲线保持不变。 func TestPEMRoundTrip(t *testing.T) { for _, tc := range supportedCurves() { t.Run(tc.name, func(t *testing.T) { privatePEM, publicPEM, err := ecdsax.GenerateKeyPEM(tc.curve) if err != nil { t.Fatalf("GenerateKeyPEM 返回错误: %v", err) } privateBlock, _ := pem.Decode(privatePEM) if privateBlock == nil { t.Fatal("私钥 PEM 无法解码") } if privateBlock.Type != "PRIVATE KEY" { t.Errorf("私钥 PEM 块类型 = %q, 期望 %q", privateBlock.Type, "PRIVATE KEY") } publicBlock, _ := pem.Decode(publicPEM) if publicBlock == nil { t.Fatal("公钥 PEM 无法解码") } if publicBlock.Type != "PUBLIC KEY" { t.Errorf("公钥 PEM 块类型 = %q, 期望 %q", publicBlock.Type, "PUBLIC KEY") } priv, err := ecdsax.ParsePrivateKeyPEM(privatePEM) if err != nil { t.Fatalf("ParsePrivateKeyPEM 返回错误: %v", err) } pub, err := ecdsax.ParsePublicKeyPEM(publicPEM) if err != nil { t.Fatalf("ParsePublicKeyPEM 返回错误: %v", err) } if priv.Curve != tc.curve { t.Errorf("解析出的私钥曲线 = %v, 期望 %v", priv.Curve, tc.curve) } if pub.Curve != tc.curve { t.Errorf("解析出的公钥曲线 = %v, 期望 %v", pub.Curve, tc.curve) } if !priv.PublicKey.Equal(pub) { t.Error("私钥内嵌公钥与解析出的公钥不一致") } message := []byte("PEM 往返后验签 / " + tc.name) signature, err := ecdsax.Sign(priv, message) if err != nil { t.Fatalf("Sign 返回错误: %v", err) } if err := ecdsax.Verify(pub, message, signature); err != nil { t.Fatalf("Verify 应通过, 实际返回错误: %v", err) } }) } } // TestMarshalParsePEMRoundTrip 覆盖:Marshal -> Parse 后私钥标量与公钥坐标完全一致。 func TestMarshalParsePEMRoundTrip(t *testing.T) { for _, tc := range supportedCurves() { t.Run(tc.name, func(t *testing.T) { priv := mustGenerateKey(t, tc.curve) privatePEM, err := ecdsax.MarshalPrivateKeyPEM(priv) if err != nil { t.Fatalf("MarshalPrivateKeyPEM 返回错误: %v", err) } parsedPriv, err := ecdsax.ParsePrivateKeyPEM(privatePEM) if err != nil { t.Fatalf("ParsePrivateKeyPEM 返回错误: %v", err) } if parsedPriv.D.Cmp(priv.D) != 0 { t.Error("私钥 PEM 往返后 D 不一致") } if !parsedPriv.PublicKey.Equal(&priv.PublicKey) { t.Error("私钥 PEM 往返后公钥不一致") } publicPEM, err := ecdsax.MarshalPublicKeyPEM(&priv.PublicKey) if err != nil { t.Fatalf("MarshalPublicKeyPEM 返回错误: %v", err) } parsedPub, err := ecdsax.ParsePublicKeyPEM(publicPEM) if err != nil { t.Fatalf("ParsePublicKeyPEM 返回错误: %v", err) } if !parsedPub.Equal(&priv.PublicKey) { t.Error("公钥 PEM 往返后不一致") } }) } } // TestBase64RoundTrip 覆盖:Base64 便利函数的正常往返。 func TestBase64RoundTrip(t *testing.T) { for _, tc := range supportedCurves() { t.Run(tc.name, func(t *testing.T) { priv := mustGenerateKey(t, tc.curve) message := []byte("Base64 签名 / " + tc.name) encoded, err := ecdsax.SignToBase64(priv, message) if err != nil { t.Fatalf("SignToBase64 返回错误: %v", err) } if err := ecdsax.VerifyFromBase64(&priv.PublicKey, message, encoded); err != nil { t.Fatalf("VerifyFromBase64 应通过, 实际返回错误: %v", err) } raw, err := base64.StdEncoding.DecodeString(encoded) if err != nil { t.Fatalf("Base64 解码失败: %v", err) } if len(raw) == 0 { t.Fatal("Base64 解码后签名为空") } }) } } // TestTamperedMessageFails 覆盖:篡改消息后验签必须失败。 func TestTamperedMessageFails(t *testing.T) { for _, tc := range supportedCurves() { t.Run(tc.name, func(t *testing.T) { priv := mustGenerateKey(t, tc.curve) pub := &priv.PublicKey message := []byte("原始消息 payload") signature, err := ecdsax.Sign(priv, message) if err != nil { t.Fatalf("Sign 返回错误: %v", err) } tampered := [][]byte{ []byte("原始消息 payloa"), // 少一个字节 []byte("原始消息 payload "), // 多一个空格 []byte("篡改消息 payload"), // 同长度改写 bytes.Repeat([]byte{0x00}, len(message)), } for _, msg := range tampered { if bytes.Equal(msg, message) { t.Fatal("用例构造错误: 篡改消息与原始消息相同") } requireError(t, "篡改消息后 Verify", ecdsax.Verify(pub, msg, signature)) requireError(t, "篡改消息后 VerifyFromBase64", ecdsax.VerifyFromBase64(pub, msg, base64.StdEncoding.EncodeToString(signature))) } // 空消息的签名不能通过非空消息的校验。 emptySignature, err := ecdsax.Sign(priv, nil) if err != nil { t.Fatalf("Sign 返回错误: %v", err) } requireError(t, "空消息签名用于非空消息", ecdsax.Verify(pub, message, emptySignature)) }) } } // TestTamperedSignatureFails 覆盖:翻转签名字节、截断、全零签名后验签必须失败。 func TestTamperedSignatureFails(t *testing.T) { for _, tc := range supportedCurves() { t.Run(tc.name, func(t *testing.T) { priv := mustGenerateKey(t, tc.curve) pub := &priv.PublicKey message := []byte("签名篡改测试") signature, err := ecdsax.Sign(priv, message) if err != nil { t.Fatalf("Sign 返回错误: %v", err) } for _, pos := range []int{0, 1, len(signature) / 2, len(signature) - 1} { flipped := append([]byte{}, signature...) flipped[pos] ^= 0x01 if bytes.Equal(flipped, signature) { t.Fatalf("位置 %d 翻转后签名未变化", pos) } requireError(t, "翻转签名字节后 Verify", ecdsax.Verify(pub, message, flipped)) } requireError(t, "截断签名", ecdsax.Verify(pub, message, signature[:len(signature)-1])) requireError(t, "全零签名", ecdsax.Verify(pub, message, make([]byte, len(signature)))) requireError(t, "全 0xFF 签名", ecdsax.Verify(pub, message, bytes.Repeat([]byte{0xFF}, len(signature)))) // 合法签名的 Base64 文本被破坏后必须失败。 encoded := []byte(base64.StdEncoding.EncodeToString(signature)) if encoded[0] == 'A' { encoded[0] = 'B' } else { encoded[0] = 'A' } requireError(t, "Base64 破坏后 VerifyFromBase64", ecdsax.VerifyFromBase64(pub, message, string(encoded))) }) } } // TestWrongPublicKeyFails 覆盖:用另一对密钥的公钥验签必须失败。 func TestWrongPublicKeyFails(t *testing.T) { for _, tc := range supportedCurves() { t.Run(tc.name, func(t *testing.T) { privA := mustGenerateKey(t, tc.curve) privB := mustGenerateKey(t, tc.curve) if privA.PublicKey.Equal(&privB.PublicKey) { t.Fatal("两次生成的密钥不应相同") } message := []byte("错误公钥测试 / " + tc.name) signature, err := ecdsax.Sign(privA, message) if err != nil { t.Fatalf("Sign 返回错误: %v", err) } if err := ecdsax.Verify(&privA.PublicKey, message, signature); err != nil { t.Fatalf("使用正确公钥应当通过, 实际返回错误: %v", err) } requireError(t, "使用错误公钥 Verify", ecdsax.Verify(&privB.PublicKey, message, signature)) requireError(t, "使用错误公钥 VerifyFromBase64", ecdsax.VerifyFromBase64(&privB.PublicKey, message, base64.StdEncoding.EncodeToString(signature))) }) } } // TestCrossCurveVerifyFails 覆盖:跨曲线的公钥不能验证另一条曲线产生的签名。 func TestCrossCurveVerifyFails(t *testing.T) { signer := mustGenerateKey(t, elliptic.P256()) message := []byte("跨曲线验签测试") signature, err := ecdsax.Sign(signer, message) if err != nil { t.Fatalf("Sign 返回错误: %v", err) } for _, tc := range supportedCurves() { if tc.curve == elliptic.P256() { continue } t.Run(tc.name, func(t *testing.T) { other := mustGenerateKey(t, tc.curve) requireError(t, "跨曲线 Verify", ecdsax.Verify(&other.PublicKey, message, signature)) }) } } // TestSEC1PrivateKeyPEM 覆盖:兼容 OpenSSL 的 SEC1("EC PRIVATE KEY")私钥解析。 func TestSEC1PrivateKeyPEM(t *testing.T) { priv := mustGenerateKey(t, elliptic.P256()) der, err := x509.MarshalECPrivateKey(priv) if err != nil { t.Fatalf("MarshalECPrivateKey 返回错误: %v", err) } sec1PEM := pem.EncodeToMemory(&pem.Block{Type: "EC PRIVATE KEY", Bytes: der}) parsed, err := ecdsax.ParsePrivateKeyPEM(sec1PEM) if err != nil { t.Fatalf("ParsePrivateKeyPEM(SEC1) 返回错误: %v", err) } if parsed.D.Cmp(priv.D) != 0 { t.Error("SEC1 私钥往返后 D 不一致") } message := []byte("SEC1 私钥签名") signature, err := ecdsax.Sign(parsed, message) if err != nil { t.Fatalf("Sign 返回错误: %v", err) } if err := ecdsax.Verify(&priv.PublicKey, message, signature); err != nil { t.Fatalf("Verify 应通过, 实际返回错误: %v", err) } } // TestInvalidPEMInputs 覆盖:空 PEM、垃圾字节、其他算法、P-224 与错误块类型都必须返回 error。 func TestInvalidPEMInputs(t *testing.T) { priv := mustGenerateKey(t, elliptic.P256()) validPrivatePEM, err := ecdsax.MarshalPrivateKeyPEM(priv) if err != nil { t.Fatalf("MarshalPrivateKeyPEM 返回错误: %v", err) } validPublicPEM, err := ecdsax.MarshalPublicKeyPEM(&priv.PublicKey) if err != nil { t.Fatalf("MarshalPublicKeyPEM 返回错误: %v", err) } privateBlock, _ := pem.Decode(validPrivatePEM) publicBlock, _ := pem.Decode(validPublicPEM) if privateBlock == nil || publicBlock == nil { t.Fatal("自产 PEM 无法解码, 测试前提不成立") } // 其他算法:Ed25519 与 RSA 的 PKCS#8 私钥、PKIX 公钥。 edPub, edPriv, err := ed25519.GenerateKey(rand.Reader) if err != nil { t.Fatalf("生成 Ed25519 测试密钥失败: %v", err) } edPrivateDER, err := x509.MarshalPKCS8PrivateKey(edPriv) if err != nil { t.Fatalf("编码 Ed25519 私钥失败: %v", err) } edPublicDER, err := x509.MarshalPKIXPublicKey(edPub) if err != nil { t.Fatalf("编码 Ed25519 公钥失败: %v", err) } rsaKey, err := rsa.GenerateKey(rand.Reader, 2048) if err != nil { t.Fatalf("生成 RSA 测试密钥失败: %v", err) } rsaPrivateDER, err := x509.MarshalPKCS8PrivateKey(rsaKey) if err != nil { t.Fatalf("编码 RSA 私钥失败: %v", err) } rsaPublicDER, err := x509.MarshalPKIXPublicKey(&rsaKey.PublicKey) if err != nil { t.Fatalf("编码 RSA 公钥失败: %v", err) } // P-224 密钥:标准库仍能解析,但本包必须因曲线不受支持而拒绝。 p224Priv, err := ecdsa.GenerateKey(elliptic.P224(), rand.Reader) if err != nil { t.Fatalf("生成 P-224 测试密钥失败: %v", err) } p224PrivateDER, err := x509.MarshalPKCS8PrivateKey(p224Priv) if err != nil { t.Fatalf("编码 P-224 私钥失败: %v", err) } p224PublicDER, err := x509.MarshalPKIXPublicKey(&p224Priv.PublicKey) if err != nil { t.Fatalf("编码 P-224 公钥失败: %v", err) } cases := []struct { name string fn func() error }{ {"空 PEM 私钥(nil)", func() error { _, err := ecdsax.ParsePrivateKeyPEM(nil); return err }}, {"空 PEM 私钥(空切片)", func() error { _, err := ecdsax.ParsePrivateKeyPEM([]byte{}); return err }}, {"空 PEM 私钥(仅空白)", func() error { _, err := ecdsax.ParsePrivateKeyPEM([]byte(" \n\t")); return err }}, {"垃圾字节私钥", func() error { _, err := ecdsax.ParsePrivateKeyPEM([]byte("这不是 PEM 数据")); return err }}, {"随机字节私钥", func() error { _, err := ecdsax.ParsePrivateKeyPEM(bytes.Repeat([]byte{0xCD}, 256)) return err }}, {"无 PEM 头尾的裸 DER 私钥", func() error { _, err := ecdsax.ParsePrivateKeyPEM(privateBlock.Bytes); return err }}, {"块类型为 CERTIFICATE 的私钥", func() error { _, err := ecdsax.ParsePrivateKeyPEM(pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: privateBlock.Bytes})) return err }}, {"块内容为垃圾的 PKCS#8 私钥", func() error { _, err := ecdsax.ParsePrivateKeyPEM(pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: []byte("garbage")})) return err }}, {"块内容为垃圾的 SEC1 私钥", func() error { _, err := ecdsax.ParsePrivateKeyPEM(pem.EncodeToMemory(&pem.Block{Type: "EC PRIVATE KEY", Bytes: []byte("garbage")})) return err }}, {"Ed25519 私钥 PEM", func() error { _, err := ecdsax.ParsePrivateKeyPEM(pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: edPrivateDER})) return err }}, {"RSA 私钥 PEM", func() error { _, err := ecdsax.ParsePrivateKeyPEM(pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: rsaPrivateDER})) return err }}, {"P-224 私钥 PEM", func() error { _, err := ecdsax.ParsePrivateKeyPEM(pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: p224PrivateDER})) return err }}, {"公钥 PEM 传给 ParsePrivateKeyPEM", func() error { _, err := ecdsax.ParsePrivateKeyPEM(validPublicPEM); return err }}, {"空 PEM 公钥(nil)", func() error { _, err := ecdsax.ParsePublicKeyPEM(nil); return err }}, {"垃圾字节公钥", func() error { _, err := ecdsax.ParsePublicKeyPEM([]byte("-----BEGIN PUBLIC KEY-----\n不是Base64\n-----END PUBLIC KEY-----")) return err }}, {"块类型为 PRIVATE KEY 的公钥", func() error { _, err := ecdsax.ParsePublicKeyPEM(pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: publicBlock.Bytes})) return err }}, {"块内容为垃圾的 PKIX 公钥", func() error { _, err := ecdsax.ParsePublicKeyPEM(pem.EncodeToMemory(&pem.Block{Type: "PUBLIC KEY", Bytes: []byte("garbage")})) return err }}, {"Ed25519 公钥 PEM", func() error { _, err := ecdsax.ParsePublicKeyPEM(pem.EncodeToMemory(&pem.Block{Type: "PUBLIC KEY", Bytes: edPublicDER})) return err }}, {"RSA 公钥 PEM", func() error { _, err := ecdsax.ParsePublicKeyPEM(pem.EncodeToMemory(&pem.Block{Type: "PUBLIC KEY", Bytes: rsaPublicDER})) return err }}, {"P-224 公钥 PEM", func() error { _, err := ecdsax.ParsePublicKeyPEM(pem.EncodeToMemory(&pem.Block{Type: "PUBLIC KEY", Bytes: p224PublicDER})) return err }}, {"私钥 PEM 传给 ParsePublicKeyPEM", func() error { _, err := ecdsax.ParsePublicKeyPEM(validPrivatePEM); return err }}, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { requireError(t, tc.name, tc.fn()) }) } } // TestInvalidKeyAndSignatureInputs 覆盖:nil/残缺密钥、空或畸形签名、非法 Base64 都必须返回 error 且不 panic。 func TestInvalidKeyAndSignatureInputs(t *testing.T) { priv := mustGenerateKey(t, elliptic.P256()) message := []byte("非法输入测试") validSignature, err := ecdsax.Sign(priv, message) if err != nil { t.Fatalf("Sign 返回错误: %v", err) } // 手工构造的残缺密钥:用于证明入口处的校验能挡住会让标准库 panic 的输入。 nilCurveKey := &ecdsa.PrivateKey{} nilScalarKey := &ecdsa.PrivateKey{PublicKey: ecdsa.PublicKey{Curve: elliptic.P256()}} missingPointKey := &ecdsa.PrivateKey{ PublicKey: ecdsa.PublicKey{Curve: elliptic.P256()}, D: big.NewInt(1), } offCurveKey := &ecdsa.PrivateKey{ PublicKey: ecdsa.PublicKey{Curve: elliptic.P256(), X: big.NewInt(1), Y: big.NewInt(1)}, D: big.NewInt(1), } nilCurvePub := &ecdsa.PublicKey{} missingPointPub := &ecdsa.PublicKey{Curve: elliptic.P256()} offCurvePub := &ecdsa.PublicKey{Curve: elliptic.P256(), X: big.NewInt(1), Y: big.NewInt(1)} infinityPub := &ecdsa.PublicKey{Curve: elliptic.P256(), X: big.NewInt(0), Y: big.NewInt(0)} cases := []struct { name string fn func() error }{ {"Sign 私钥为 nil", func() error { _, err := ecdsax.Sign(nil, message); return err }}, {"Sign 私钥曲线为 nil", func() error { _, err := ecdsax.Sign(nilCurveKey, message); return err }}, {"Sign 私钥缺少标量 D", func() error { _, err := ecdsax.Sign(nilScalarKey, message); return err }}, {"Sign 私钥缺少坐标", func() error { _, err := ecdsax.Sign(missingPointKey, message); return err }}, {"Sign 私钥公钥点不在曲线上", func() error { _, err := ecdsax.Sign(offCurveKey, message); return err }}, {"SignToBase64 私钥为 nil", func() error { _, err := ecdsax.SignToBase64(nil, message); return err }}, {"MarshalPrivateKeyPEM 私钥为 nil", func() error { _, err := ecdsax.MarshalPrivateKeyPEM(nil); return err }}, {"MarshalPrivateKeyPEM 私钥缺少标量 D", func() error { _, err := ecdsax.MarshalPrivateKeyPEM(nilScalarKey); return err }}, {"Verify 公钥为 nil", func() error { return ecdsax.Verify(nil, message, validSignature) }}, {"Verify 公钥曲线为 nil", func() error { return ecdsax.Verify(nilCurvePub, message, validSignature) }}, {"Verify 公钥缺少坐标", func() error { return ecdsax.Verify(missingPointPub, message, validSignature) }}, {"Verify 公钥点不在曲线上", func() error { return ecdsax.Verify(offCurvePub, message, validSignature) }}, {"Verify 公钥为无穷远点", func() error { return ecdsax.Verify(infinityPub, message, validSignature) }}, {"MarshalPublicKeyPEM 公钥为 nil", func() error { _, err := ecdsax.MarshalPublicKeyPEM(nil); return err }}, {"MarshalPublicKeyPEM 公钥缺少坐标", func() error { _, err := ecdsax.MarshalPublicKeyPEM(missingPointPub); return err }}, {"Verify 签名为 nil", func() error { return ecdsax.Verify(&priv.PublicKey, message, nil) }}, {"Verify 签名为空切片", func() error { return ecdsax.Verify(&priv.PublicKey, message, []byte{}) }}, {"Verify 签名被截断", func() error { return ecdsax.Verify(&priv.PublicKey, message, validSignature[:len(validSignature)-1]) }}, {"Verify 签名非 DER", func() error { return ecdsax.Verify(&priv.PublicKey, message, []byte("not-a-der-signature")) }}, {"VerifyFromBase64 非法 Base64", func() error { return ecdsax.VerifyFromBase64(&priv.PublicKey, message, "!!!not-base64!!!") }}, {"VerifyFromBase64 空字符串", func() error { return ecdsax.VerifyFromBase64(&priv.PublicKey, message, "") }}, {"VerifyFromBase64 公钥为 nil", func() error { return ecdsax.VerifyFromBase64(nil, message, base64.StdEncoding.EncodeToString(validSignature)) }}, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { requireError(t, tc.name, tc.fn()) }) } } // TestSaveLoadKeyPair 覆盖:三条曲线的 SaveKeyPair -> LoadPrivateKey/LoadPublicKey 往返。 func TestSaveLoadKeyPair(t *testing.T) { for _, tc := range supportedCurves() { t.Run(tc.name, func(t *testing.T) { dir := t.TempDir() privatePath := filepath.Join(dir, "ec_private.pem") publicPath := filepath.Join(dir, "ec_public.pem") if err := ecdsax.SaveKeyPair(privatePath, publicPath, tc.curve); err != nil { t.Fatalf("SaveKeyPair 返回错误: %v", err) } priv, err := ecdsax.LoadPrivateKey(privatePath) if err != nil { t.Fatalf("LoadPrivateKey 返回错误: %v", err) } pub, err := ecdsax.LoadPublicKey(publicPath) if err != nil { t.Fatalf("LoadPublicKey 返回错误: %v", err) } if priv.Curve != tc.curve { t.Errorf("加载的私钥曲线 = %v, 期望 %v", priv.Curve, tc.curve) } if !priv.PublicKey.Equal(pub) { t.Error("加载的私钥与公钥不匹配") } message := []byte("落盘往返测试 / " + tc.name) signature, err := ecdsax.Sign(priv, message) if err != nil { t.Fatalf("Sign 返回错误: %v", err) } if err := ecdsax.Verify(pub, message, signature); err != nil { t.Fatalf("Verify 应通过, 实际返回错误: %v", err) } // 权限位只在类 Unix 系统上有意义,Windows 只用 mode 表示只读属性。 if runtime.GOOS != "windows" { privateInfo, err := os.Stat(privatePath) if err != nil { t.Fatalf("Stat 私钥文件失败: %v", err) } if perm := privateInfo.Mode().Perm(); perm != 0o600 { t.Errorf("私钥文件权限 = %o, 期望 600", perm) } publicInfo, err := os.Stat(publicPath) if err != nil { t.Fatalf("Stat 公钥文件失败: %v", err) } if perm := publicInfo.Mode().Perm(); perm != 0o644 { t.Errorf("公钥文件权限 = %o, 期望 644", perm) } } }) } } // TestLoadKeyErrors 覆盖:路径不存在、路径是目录、文件内容非法时都必须返回 error。 func TestLoadKeyErrors(t *testing.T) { dir := t.TempDir() requireError(t, "LoadPrivateKey 路径不存在", func() error { _, err := ecdsax.LoadPrivateKey(filepath.Join(dir, "missing.pem")) return err }()) requireError(t, "LoadPublicKey 路径不存在", func() error { _, err := ecdsax.LoadPublicKey(filepath.Join(dir, "missing.pem")) return err }()) requireError(t, "LoadPrivateKey 路径为目录", func() error { _, err := ecdsax.LoadPrivateKey(dir) return err }()) junkPath := filepath.Join(dir, "junk.pem") if err := os.WriteFile(junkPath, []byte("这不是 PEM"), 0o600); err != nil { t.Fatalf("写入测试文件失败: %v", err) } requireError(t, "LoadPrivateKey 内容非法", func() error { _, err := ecdsax.LoadPrivateKey(junkPath) return err }()) requireError(t, "LoadPublicKey 内容非法", func() error { _, err := ecdsax.LoadPublicKey(junkPath) return err }()) } // flipRandomBits 复制一份数据并随机翻转若干位,用于构造逼近真实攻击面的畸形输入。 func flipRandomBits(rng *mrand.Rand, data []byte) []byte { out := append([]byte{}, data...) for i := 0; i < 1+rng.Intn(4); i++ { out[rng.Intn(len(out))] ^= byte(1 << rng.Intn(8)) } return out } // randomBytes 生成指定长度的随机字节串。 func randomBytes(rng *mrand.Rand, size int) []byte { out := make([]byte, size) for i := range out { out[i] = byte(rng.Intn(256)) } return out } // TestMalformedInputsDoNotPanic 覆盖:随机字节、随机位翻转的 PEM/签名,以及坐标随机的公钥, // 都不会触发 panic。这是对"任何情况下不得 panic"这一硬性要求的直接验证, // 因此只要求不 panic,不约束具体错误内容。 func TestMalformedInputsDoNotPanic(t *testing.T) { privatePEM, publicPEM, err := ecdsax.GenerateKeyPEM(elliptic.P256()) if err != nil { t.Fatalf("GenerateKeyPEM 返回错误: %v", err) } priv, err := ecdsax.ParsePrivateKeyPEM(privatePEM) if err != nil { t.Fatalf("ParsePrivateKeyPEM 返回错误: %v", err) } pub, err := ecdsax.ParsePublicKeyPEM(publicPEM) if err != nil { t.Fatalf("ParsePublicKeyPEM 返回错误: %v", err) } message := []byte("畸形输入测试") signature, err := ecdsax.Sign(priv, message) if err != nil { t.Fatalf("Sign 返回错误: %v", err) } rng := mrand.New(mrand.NewSource(20240517)) for i := 0; i < 200; i++ { // 完全随机的字节串。 raw := make([]byte, rng.Intn(256)) for j := range raw { raw[j] = byte(rng.Intn(256)) } _, _ = ecdsax.ParsePrivateKeyPEM(raw) _, _ = ecdsax.ParsePublicKeyPEM(raw) _ = ecdsax.Verify(pub, message, raw) _ = ecdsax.VerifyFromBase64(pub, message, string(raw)) // 在合法 PEM 上随机翻转比特位,更容易命中解析器的深层分支。 _, _ = ecdsax.ParsePrivateKeyPEM(flipRandomBits(rng, privatePEM)) _, _ = ecdsax.ParsePublicKeyPEM(flipRandomBits(rng, publicPEM)) // 在合法签名上随机翻转比特位。 _ = ecdsax.Verify(pub, message, flipRandomBits(rng, signature)) _ = ecdsax.VerifyFromBase64(pub, message, base64.StdEncoding.EncodeToString(flipRandomBits(rng, signature))) // 坐标与标量完全是随机数的密钥:标准库对畸形点会报错, // 本包必须在进入标准库之前就拦住,不能 panic。 randomPub := &ecdsa.PublicKey{ Curve: elliptic.P256(), X: new(big.Int).SetBytes(randomBytes(rng, 32)), Y: new(big.Int).SetBytes(randomBytes(rng, 32)), } randomPriv := &ecdsa.PrivateKey{ PublicKey: *randomPub, D: new(big.Int).SetBytes(randomBytes(rng, 32)), } _ = ecdsax.Verify(randomPub, message, signature) _, _ = ecdsax.Sign(randomPriv, message) _, _ = ecdsax.MarshalPublicKeyPEM(randomPub) _, _ = ecdsax.MarshalPrivateKeyPEM(randomPriv) } } // TestSaveKeyPairErrors 覆盖:写入路径非法时返回 error 而不是 panic。 func TestSaveKeyPairErrors(t *testing.T) { dir := t.TempDir() requireError(t, "SaveKeyPair 私钥路径为目录", ecdsax.SaveKeyPair(dir, filepath.Join(dir, "pub.pem"), elliptic.P256())) privatePath := filepath.Join(dir, "priv.pem") requireError(t, "SaveKeyPair 公钥路径目录不存在", ecdsax.SaveKeyPair(privatePath, filepath.Join(dir, "no-such-dir", "pub.pem"), elliptic.P256())) if _, err := os.Stat(privatePath); err == nil { t.Error("公钥写入失败后应删除已写入的私钥文件") } }