完善不同加密方法的使用
This commit is contained in:
@@ -0,0 +1,604 @@
|
||||
package ed25519x_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/ecdsa"
|
||||
"crypto/ed25519"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"encoding/pem"
|
||||
mrand "math/rand"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"testing"
|
||||
|
||||
"code.yun.ink/pkg/encryptx/ed25519x"
|
||||
)
|
||||
|
||||
// mustGenerateKey 生成密钥并断言长度,供其余用例复用。
|
||||
func mustGenerateKey(t *testing.T) (ed25519.PublicKey, ed25519.PrivateKey) {
|
||||
t.Helper()
|
||||
pub, priv, err := ed25519x.GenerateKey()
|
||||
if err != nil {
|
||||
t.Fatalf("GenerateKey 返回错误: %v", err)
|
||||
}
|
||||
if len(pub) != ed25519x.PublicKeySize {
|
||||
t.Fatalf("公钥长度 = %d, 期望 %d", len(pub), ed25519x.PublicKeySize)
|
||||
}
|
||||
if len(priv) != ed25519x.PrivateKeySize {
|
||||
t.Fatalf("私钥长度 = %d, 期望 %d", len(priv), ed25519x.PrivateKeySize)
|
||||
}
|
||||
return pub, priv
|
||||
}
|
||||
|
||||
// requireError 断言操作失败返回了 error(而非 panic 或静默成功)。
|
||||
func requireError(t *testing.T, name string, err error) {
|
||||
t.Helper()
|
||||
if err == nil {
|
||||
t.Errorf("%s: 期望返回错误, 实际返回 nil", name)
|
||||
}
|
||||
}
|
||||
|
||||
// TestGenerateKeySignVerifyRoundTrip 覆盖:生成密钥 -> 原始字节签名 -> 验签成功。
|
||||
func TestGenerateKeySignVerifyRoundTrip(t *testing.T) {
|
||||
pub, priv := mustGenerateKey(t)
|
||||
|
||||
message := []byte("encryptx ed25519x 往返测试消息")
|
||||
signature, err := ed25519x.Sign(priv, message)
|
||||
if err != nil {
|
||||
t.Fatalf("Sign 返回错误: %v", err)
|
||||
}
|
||||
if len(signature) != ed25519x.SignatureSize {
|
||||
t.Fatalf("签名长度 = %d, 期望 %d", len(signature), ed25519x.SignatureSize)
|
||||
}
|
||||
if err := ed25519x.Verify(pub, message, signature); err != nil {
|
||||
t.Fatalf("Verify 应通过, 实际返回错误: %v", err)
|
||||
}
|
||||
|
||||
// 空消息与较长消息同样应能往返。
|
||||
for _, msg := range [][]byte{nil, {}, bytes.Repeat([]byte("A"), 1<<16)} {
|
||||
sig, err := ed25519x.Sign(priv, msg)
|
||||
if err != nil {
|
||||
t.Fatalf("Sign(len=%d) 返回错误: %v", len(msg), err)
|
||||
}
|
||||
if err := ed25519x.Verify(pub, msg, sig); err != nil {
|
||||
t.Fatalf("Verify(len=%d) 应通过, 实际返回错误: %v", len(msg), err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestSignatureIsDeterministic 覆盖:Ed25519 签名确定,同消息两次签名完全相同。
|
||||
func TestSignatureIsDeterministic(t *testing.T) {
|
||||
pub, priv := mustGenerateKey(t)
|
||||
message := []byte("确定性签名")
|
||||
|
||||
first, err := ed25519x.Sign(priv, message)
|
||||
if err != nil {
|
||||
t.Fatalf("Sign 返回错误: %v", err)
|
||||
}
|
||||
second, err := ed25519x.Sign(priv, message)
|
||||
if err != nil {
|
||||
t.Fatalf("Sign 返回错误: %v", err)
|
||||
}
|
||||
if !bytes.Equal(first, second) {
|
||||
t.Error("Ed25519 签名应为确定性, 但两次结果不同")
|
||||
}
|
||||
if err := ed25519x.Verify(pub, message, first); err != nil {
|
||||
t.Fatalf("Verify 应通过, 实际返回错误: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestPEMRoundTrip 覆盖:GenerateKeyPEM -> 解析 -> 用公钥验签成功。
|
||||
func TestPEMRoundTrip(t *testing.T) {
|
||||
privatePEM, publicPEM, err := ed25519x.GenerateKeyPEM()
|
||||
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 := ed25519x.ParsePrivateKeyPEM(privatePEM)
|
||||
if err != nil {
|
||||
t.Fatalf("ParsePrivateKeyPEM 返回错误: %v", err)
|
||||
}
|
||||
pub, err := ed25519x.ParsePublicKeyPEM(publicPEM)
|
||||
if err != nil {
|
||||
t.Fatalf("ParsePublicKeyPEM 返回错误: %v", err)
|
||||
}
|
||||
if len(priv) != ed25519x.PrivateKeySize {
|
||||
t.Fatalf("解析出的私钥长度 = %d, 期望 %d", len(priv), ed25519x.PrivateKeySize)
|
||||
}
|
||||
if len(pub) != ed25519x.PublicKeySize {
|
||||
t.Fatalf("解析出的公钥长度 = %d, 期望 %d", len(pub), ed25519x.PublicKeySize)
|
||||
}
|
||||
// 私钥内嵌的公钥必须与解析出的公钥一致,否则说明往返过程出错。
|
||||
embedded, ok := priv.Public().(ed25519.PublicKey)
|
||||
if !ok {
|
||||
t.Fatalf("私钥内嵌公钥类型 = %T, 期望 ed25519.PublicKey", priv.Public())
|
||||
}
|
||||
if !bytes.Equal(embedded, pub) {
|
||||
t.Error("私钥内嵌公钥与解析出的公钥不一致")
|
||||
}
|
||||
|
||||
message := []byte("PEM 往返后验签")
|
||||
signature, err := ed25519x.Sign(priv, message)
|
||||
if err != nil {
|
||||
t.Fatalf("Sign 返回错误: %v", err)
|
||||
}
|
||||
if err := ed25519x.Verify(pub, message, signature); err != nil {
|
||||
t.Fatalf("Verify 应通过, 实际返回错误: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestMarshalParsePEMRoundTrip 覆盖:已生成的密钥经 Marshal -> Parse 后字节完全一致。
|
||||
func TestMarshalParsePEMRoundTrip(t *testing.T) {
|
||||
pub, priv := mustGenerateKey(t)
|
||||
|
||||
privatePEM, err := ed25519x.MarshalPrivateKeyPEM(priv)
|
||||
if err != nil {
|
||||
t.Fatalf("MarshalPrivateKeyPEM 返回错误: %v", err)
|
||||
}
|
||||
parsedPriv, err := ed25519x.ParsePrivateKeyPEM(privatePEM)
|
||||
if err != nil {
|
||||
t.Fatalf("ParsePrivateKeyPEM 返回错误: %v", err)
|
||||
}
|
||||
if !bytes.Equal([]byte(priv), []byte(parsedPriv)) {
|
||||
t.Error("私钥 PEM 往返后字节不一致")
|
||||
}
|
||||
|
||||
publicPEM, err := ed25519x.MarshalPublicKeyPEM(pub)
|
||||
if err != nil {
|
||||
t.Fatalf("MarshalPublicKeyPEM 返回错误: %v", err)
|
||||
}
|
||||
parsedPub, err := ed25519x.ParsePublicKeyPEM(publicPEM)
|
||||
if err != nil {
|
||||
t.Fatalf("ParsePublicKeyPEM 返回错误: %v", err)
|
||||
}
|
||||
if !bytes.Equal([]byte(pub), []byte(parsedPub)) {
|
||||
t.Error("公钥 PEM 往返后字节不一致")
|
||||
}
|
||||
}
|
||||
|
||||
// TestBase64RoundTrip 覆盖:Base64 便利函数的正常往返。
|
||||
func TestBase64RoundTrip(t *testing.T) {
|
||||
pub, priv := mustGenerateKey(t)
|
||||
message := []byte("Base64 签名")
|
||||
|
||||
encoded, err := ed25519x.SignToBase64(priv, message)
|
||||
if err != nil {
|
||||
t.Fatalf("SignToBase64 返回错误: %v", err)
|
||||
}
|
||||
if err := ed25519x.VerifyFromBase64(pub, message, encoded); err != nil {
|
||||
t.Fatalf("VerifyFromBase64 应通过, 实际返回错误: %v", err)
|
||||
}
|
||||
// Base64 文本必须能还原成标准长度的原始签名。
|
||||
raw, err := base64.StdEncoding.DecodeString(encoded)
|
||||
if err != nil {
|
||||
t.Fatalf("Base64 解码失败: %v", err)
|
||||
}
|
||||
if len(raw) != ed25519x.SignatureSize {
|
||||
t.Fatalf("解码后签名长度 = %d, 期望 %d", len(raw), ed25519x.SignatureSize)
|
||||
}
|
||||
}
|
||||
|
||||
// TestTamperedMessageFails 覆盖:篡改消息后验签必须失败。
|
||||
func TestTamperedMessageFails(t *testing.T) {
|
||||
pub, priv := mustGenerateKey(t)
|
||||
message := []byte("原始消息 payload")
|
||||
signature, err := ed25519x.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)),
|
||||
append(append([]byte{}, message...), 0x00),
|
||||
}
|
||||
for i, msg := range tampered {
|
||||
requireError(t, "篡改消息后 Verify", ed25519x.Verify(pub, msg, signature))
|
||||
requireError(t, "篡改消息后 VerifyFromBase64", ed25519x.VerifyFromBase64(pub, msg, base64.StdEncoding.EncodeToString(signature)))
|
||||
if i == 0 && bytes.Equal(msg, message) {
|
||||
t.Fatal("用例构造错误: 篡改消息与原始消息相同")
|
||||
}
|
||||
}
|
||||
// 空消息的签名不能通过非空消息的校验。
|
||||
emptySig, err := ed25519x.Sign(priv, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Sign 返回错误: %v", err)
|
||||
}
|
||||
requireError(t, "空消息签名用于非空消息", ed25519x.Verify(pub, message, emptySig))
|
||||
}
|
||||
|
||||
// TestTamperedSignatureFails 覆盖:翻转签名中的任意字节后验签必须失败。
|
||||
func TestTamperedSignatureFails(t *testing.T) {
|
||||
pub, priv := mustGenerateKey(t)
|
||||
message := []byte("签名篡改测试")
|
||||
signature, err := ed25519x.Sign(priv, message)
|
||||
if err != nil {
|
||||
t.Fatalf("Sign 返回错误: %v", err)
|
||||
}
|
||||
|
||||
positions := []int{0, 1, ed25519x.SignatureSize / 2, ed25519x.SignatureSize - 1}
|
||||
for _, pos := range positions {
|
||||
flipped := append([]byte{}, signature...)
|
||||
flipped[pos] ^= 0x01
|
||||
if bytes.Equal(flipped, signature) {
|
||||
t.Fatalf("位置 %d 翻转后签名未变化", pos)
|
||||
}
|
||||
requireError(t, "翻转签名字节后 Verify", ed25519x.Verify(pub, message, flipped))
|
||||
}
|
||||
|
||||
// 截断、加长、全零签名同样必须被拒绝。
|
||||
requireError(t, "截断签名(63 字节)", ed25519x.Verify(pub, message, signature[:ed25519x.SignatureSize-1]))
|
||||
requireError(t, "加长签名(65 字节)", ed25519x.Verify(pub, message, append(append([]byte{}, signature...), 0x00)))
|
||||
requireError(t, "全零签名", ed25519x.Verify(pub, message, make([]byte, ed25519x.SignatureSize)))
|
||||
|
||||
// 合法签名的 Base64 文本被翻转一个字符后也必须失败。
|
||||
encoded := base64.StdEncoding.EncodeToString(signature)
|
||||
chars := []byte(encoded)
|
||||
if chars[0] == 'A' {
|
||||
chars[0] = 'B'
|
||||
} else {
|
||||
chars[0] = 'A'
|
||||
}
|
||||
requireError(t, "Base64 篡改后 VerifyFromBase64", ed25519x.VerifyFromBase64(pub, message, string(chars)))
|
||||
}
|
||||
|
||||
// TestWrongPublicKeyFails 覆盖:用另一对密钥的公钥验签必须失败。
|
||||
func TestWrongPublicKeyFails(t *testing.T) {
|
||||
pubA, privA := mustGenerateKey(t)
|
||||
pubB, _ := mustGenerateKey(t)
|
||||
|
||||
message := []byte("错误公钥测试")
|
||||
signature, err := ed25519x.Sign(privA, message)
|
||||
if err != nil {
|
||||
t.Fatalf("Sign 返回错误: %v", err)
|
||||
}
|
||||
if err := ed25519x.Verify(pubA, message, signature); err != nil {
|
||||
t.Fatalf("使用正确公钥应当通过, 实际返回错误: %v", err)
|
||||
}
|
||||
requireError(t, "使用错误公钥 Verify", ed25519x.Verify(pubB, message, signature))
|
||||
requireError(t, "使用错误公钥 VerifyFromBase64", ed25519x.VerifyFromBase64(pubB, message, base64.StdEncoding.EncodeToString(signature)))
|
||||
}
|
||||
|
||||
// TestInvalidPEMInputs 覆盖:空 PEM、垃圾字节、其他算法/错误类型的合法 PEM 都必须返回 error。
|
||||
func TestInvalidPEMInputs(t *testing.T) {
|
||||
pub, priv := mustGenerateKey(t)
|
||||
validPrivatePEM, err := ed25519x.MarshalPrivateKeyPEM(priv)
|
||||
if err != nil {
|
||||
t.Fatalf("MarshalPrivateKeyPEM 返回错误: %v", err)
|
||||
}
|
||||
validPublicPEM, err := ed25519x.MarshalPublicKeyPEM(pub)
|
||||
if err != nil {
|
||||
t.Fatalf("MarshalPublicKeyPEM 返回错误: %v", err)
|
||||
}
|
||||
privateDER, _ := pem.Decode(validPrivatePEM)
|
||||
publicDER, _ := pem.Decode(validPublicPEM)
|
||||
if privateDER == nil || publicDER == nil {
|
||||
t.Fatal("自产 PEM 无法解码, 测试前提不成立")
|
||||
}
|
||||
|
||||
// 其他算法:ECDSA(P-256) 与 RSA(2048) 的 PKCS#8 私钥、PKIX 公钥。
|
||||
ecKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
if err != nil {
|
||||
t.Fatalf("生成 ECDSA 测试密钥失败: %v", err)
|
||||
}
|
||||
ecPrivateDER, err := x509.MarshalPKCS8PrivateKey(ecKey)
|
||||
if err != nil {
|
||||
t.Fatalf("编码 ECDSA 私钥失败: %v", err)
|
||||
}
|
||||
ecPublicDER, err := x509.MarshalPKIXPublicKey(&ecKey.PublicKey)
|
||||
if err != nil {
|
||||
t.Fatalf("编码 ECDSA 公钥失败: %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)
|
||||
}
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
fn func() error
|
||||
}{
|
||||
{"空 PEM 私钥(nil)", func() error { _, err := ed25519x.ParsePrivateKeyPEM(nil); return err }},
|
||||
{"空 PEM 私钥(空切片)", func() error { _, err := ed25519x.ParsePrivateKeyPEM([]byte{}); return err }},
|
||||
{"空 PEM 私钥(仅空白)", func() error { _, err := ed25519x.ParsePrivateKeyPEM([]byte(" \n\t")); return err }},
|
||||
{"垃圾字节私钥", func() error { _, err := ed25519x.ParsePrivateKeyPEM([]byte("这不是 PEM 数据")); return err }},
|
||||
{"随机字节私钥", func() error { _, err := ed25519x.ParsePrivateKeyPEM(bytes.Repeat([]byte{0xAB}, 256)); return err }},
|
||||
{"无 PEM 头尾的裸 DER 私钥", func() error { _, err := ed25519x.ParsePrivateKeyPEM(privateDER.Bytes); return err }},
|
||||
{"块类型为 CERTIFICATE 的私钥", func() error {
|
||||
_, err := ed25519x.ParsePrivateKeyPEM(pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: privateDER.Bytes}))
|
||||
return err
|
||||
}},
|
||||
{"块内容为垃圾的 PKCS#8 私钥", func() error {
|
||||
_, err := ed25519x.ParsePrivateKeyPEM(pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: []byte("garbage")}))
|
||||
return err
|
||||
}},
|
||||
{"ECDSA 私钥 PEM", func() error {
|
||||
_, err := ed25519x.ParsePrivateKeyPEM(pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: ecPrivateDER}))
|
||||
return err
|
||||
}},
|
||||
{"RSA 私钥 PEM", func() error {
|
||||
_, err := ed25519x.ParsePrivateKeyPEM(pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: rsaPrivateDER}))
|
||||
return err
|
||||
}},
|
||||
{"公钥 PEM 传给 ParsePrivateKeyPEM", func() error { _, err := ed25519x.ParsePrivateKeyPEM(validPublicPEM); return err }},
|
||||
{"空 PEM 公钥(nil)", func() error { _, err := ed25519x.ParsePublicKeyPEM(nil); return err }},
|
||||
{"垃圾字节公钥", func() error {
|
||||
_, err := ed25519x.ParsePublicKeyPEM([]byte("-----BEGIN PUBLIC KEY-----\n不是Base64\n-----END PUBLIC KEY-----"))
|
||||
return err
|
||||
}},
|
||||
{"块类型为 PRIVATE KEY 的公钥", func() error {
|
||||
_, err := ed25519x.ParsePublicKeyPEM(pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: publicDER.Bytes}))
|
||||
return err
|
||||
}},
|
||||
{"块内容为垃圾的 PKIX 公钥", func() error {
|
||||
_, err := ed25519x.ParsePublicKeyPEM(pem.EncodeToMemory(&pem.Block{Type: "PUBLIC KEY", Bytes: []byte("garbage")}))
|
||||
return err
|
||||
}},
|
||||
{"ECDSA 公钥 PEM", func() error {
|
||||
_, err := ed25519x.ParsePublicKeyPEM(pem.EncodeToMemory(&pem.Block{Type: "PUBLIC KEY", Bytes: ecPublicDER}))
|
||||
return err
|
||||
}},
|
||||
{"RSA 公钥 PEM", func() error {
|
||||
_, err := ed25519x.ParsePublicKeyPEM(pem.EncodeToMemory(&pem.Block{Type: "PUBLIC KEY", Bytes: rsaPublicDER}))
|
||||
return err
|
||||
}},
|
||||
{"私钥 PEM 传给 ParsePublicKeyPEM", func() error { _, err := ed25519x.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) {
|
||||
pub, priv := mustGenerateKey(t)
|
||||
message := []byte("非法输入测试")
|
||||
validSignature, err := ed25519x.Sign(priv, message)
|
||||
if err != nil {
|
||||
t.Fatalf("Sign 返回错误: %v", err)
|
||||
}
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
fn func() error
|
||||
}{
|
||||
{"Sign 私钥为 nil", func() error { _, err := ed25519x.Sign(nil, message); return err }},
|
||||
{"Sign 私钥长度为 32(仅种子)", func() error { _, err := ed25519x.Sign(make([]byte, 32), message); return err }},
|
||||
{"Sign 私钥长度为 63", func() error { _, err := ed25519x.Sign(make([]byte, 63), message); return err }},
|
||||
{"Sign 私钥长度为 65", func() error { _, err := ed25519x.Sign(make([]byte, 65), message); return err }},
|
||||
{"SignToBase64 私钥为 nil", func() error { _, err := ed25519x.SignToBase64(nil, message); return err }},
|
||||
{"Verify 公钥为 nil", func() error { return ed25519x.Verify(nil, message, validSignature) }},
|
||||
{"Verify 公钥长度为 31", func() error { return ed25519x.Verify(make([]byte, 31), message, validSignature) }},
|
||||
{"Verify 公钥长度为 33", func() error { return ed25519x.Verify(make([]byte, 33), message, validSignature) }},
|
||||
{"Verify 签名为 nil", func() error { return ed25519x.Verify(pub, message, nil) }},
|
||||
{"Verify 签名为空切片", func() error { return ed25519x.Verify(pub, message, []byte{}) }},
|
||||
{"Verify 签名长度为 63", func() error { return ed25519x.Verify(pub, message, validSignature[:63]) }},
|
||||
{"Verify 签名长度为 128", func() error { return ed25519x.Verify(pub, message, make([]byte, 128)) }},
|
||||
{"Verify 公钥与签名均为 nil", func() error { return ed25519x.Verify(nil, message, nil) }},
|
||||
{"VerifyFromBase64 非法 Base64", func() error { return ed25519x.VerifyFromBase64(pub, message, "!!!not-base64!!!") }},
|
||||
{"VerifyFromBase64 空字符串", func() error { return ed25519x.VerifyFromBase64(pub, message, "") }},
|
||||
{"VerifyFromBase64 公钥为 nil", func() error {
|
||||
return ed25519x.VerifyFromBase64(nil, message, base64.StdEncoding.EncodeToString(validSignature))
|
||||
}},
|
||||
{"MarshalPrivateKeyPEM 私钥为 nil", func() error { _, err := ed25519x.MarshalPrivateKeyPEM(nil); return err }},
|
||||
{"MarshalPrivateKeyPEM 长度为 32", func() error { _, err := ed25519x.MarshalPrivateKeyPEM(make([]byte, 32)); return err }},
|
||||
{"MarshalPublicKeyPEM 公钥为 nil", func() error { _, err := ed25519x.MarshalPublicKeyPEM(nil); return err }},
|
||||
{"MarshalPublicKeyPEM 长度为 31", func() error { _, err := ed25519x.MarshalPublicKeyPEM(make([]byte, 31)); return err }},
|
||||
}
|
||||
|
||||
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) {
|
||||
dir := t.TempDir()
|
||||
privatePath := filepath.Join(dir, "ed25519_private.pem")
|
||||
publicPath := filepath.Join(dir, "ed25519_public.pem")
|
||||
|
||||
if err := ed25519x.SaveKeyPair(privatePath, publicPath); err != nil {
|
||||
t.Fatalf("SaveKeyPair 返回错误: %v", err)
|
||||
}
|
||||
|
||||
priv, err := ed25519x.LoadPrivateKey(privatePath)
|
||||
if err != nil {
|
||||
t.Fatalf("LoadPrivateKey 返回错误: %v", err)
|
||||
}
|
||||
pub, err := ed25519x.LoadPublicKey(publicPath)
|
||||
if err != nil {
|
||||
t.Fatalf("LoadPublicKey 返回错误: %v", err)
|
||||
}
|
||||
if len(priv) != ed25519x.PrivateKeySize {
|
||||
t.Fatalf("加载的私钥长度 = %d, 期望 %d", len(priv), ed25519x.PrivateKeySize)
|
||||
}
|
||||
if len(pub) != ed25519x.PublicKeySize {
|
||||
t.Fatalf("加载的公钥长度 = %d, 期望 %d", len(pub), ed25519x.PublicKeySize)
|
||||
}
|
||||
|
||||
message := []byte("落盘往返测试")
|
||||
signature, err := ed25519x.Sign(priv, message)
|
||||
if err != nil {
|
||||
t.Fatalf("Sign 返回错误: %v", err)
|
||||
}
|
||||
if err := ed25519x.Verify(pub, message, signature); err != nil {
|
||||
t.Fatalf("Verify 应通过, 实际返回错误: %v", err)
|
||||
}
|
||||
|
||||
// 覆盖写:再次调用不应报错,且新密钥与旧密钥不同。
|
||||
previousPriv := append([]byte{}, priv...)
|
||||
if err := ed25519x.SaveKeyPair(privatePath, publicPath); err != nil {
|
||||
t.Fatalf("第二次 SaveKeyPair 返回错误: %v", err)
|
||||
}
|
||||
regenerated, err := ed25519x.LoadPrivateKey(privatePath)
|
||||
if err != nil {
|
||||
t.Fatalf("LoadPrivateKey 返回错误: %v", err)
|
||||
}
|
||||
if bytes.Equal(previousPriv, []byte(regenerated)) {
|
||||
t.Error("两次 SaveKeyPair 应生成不同密钥")
|
||||
}
|
||||
|
||||
// 权限位只在类 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 := ed25519x.LoadPrivateKey(filepath.Join(dir, "missing.pem"))
|
||||
return err
|
||||
}())
|
||||
requireError(t, "LoadPublicKey 路径不存在", func() error {
|
||||
_, err := ed25519x.LoadPublicKey(filepath.Join(dir, "missing.pem"))
|
||||
return err
|
||||
}())
|
||||
requireError(t, "LoadPrivateKey 路径为目录", func() error {
|
||||
_, err := ed25519x.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 := ed25519x.LoadPrivateKey(junkPath)
|
||||
return err
|
||||
}())
|
||||
requireError(t, "LoadPublicKey 内容非法", func() error {
|
||||
_, err := ed25519x.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 := ed25519x.GenerateKeyPEM()
|
||||
if err != nil {
|
||||
t.Fatalf("GenerateKeyPEM 返回错误: %v", err)
|
||||
}
|
||||
priv, err := ed25519x.ParsePrivateKeyPEM(privatePEM)
|
||||
if err != nil {
|
||||
t.Fatalf("ParsePrivateKeyPEM 返回错误: %v", err)
|
||||
}
|
||||
pub, err := ed25519x.ParsePublicKeyPEM(publicPEM)
|
||||
if err != nil {
|
||||
t.Fatalf("ParsePublicKeyPEM 返回错误: %v", err)
|
||||
}
|
||||
message := []byte("畸形输入测试")
|
||||
signature, err := ed25519x.Sign(priv, message)
|
||||
if err != nil {
|
||||
t.Fatalf("Sign 返回错误: %v", err)
|
||||
}
|
||||
|
||||
rng := mrand.New(mrand.NewSource(20240517))
|
||||
for i := 0; i < 300; i++ {
|
||||
// 完全随机的字节串。
|
||||
raw := make([]byte, rng.Intn(256))
|
||||
for j := range raw {
|
||||
raw[j] = byte(rng.Intn(256))
|
||||
}
|
||||
_, _ = ed25519x.ParsePrivateKeyPEM(raw)
|
||||
_, _ = ed25519x.ParsePublicKeyPEM(raw)
|
||||
_ = ed25519x.Verify(pub, message, raw)
|
||||
_ = ed25519x.VerifyFromBase64(pub, message, string(raw))
|
||||
|
||||
// 合法长度但内容随机的密钥与签名,必须返回 error 而不是 panic。
|
||||
_, _ = ed25519x.MarshalPrivateKeyPEM(randomBytes(rng, ed25519x.PrivateKeySize))
|
||||
_, _ = ed25519x.MarshalPublicKeyPEM(randomBytes(rng, ed25519x.PublicKeySize))
|
||||
_, _ = ed25519x.Sign(randomBytes(rng, ed25519x.PrivateKeySize), message)
|
||||
_ = ed25519x.Verify(randomBytes(rng, ed25519x.PublicKeySize), message, randomBytes(rng, ed25519x.SignatureSize))
|
||||
|
||||
// 在合法 PEM 上随机翻转比特位,更容易命中解析器的深层分支。
|
||||
_, _ = ed25519x.ParsePrivateKeyPEM(flipRandomBits(rng, privatePEM))
|
||||
_, _ = ed25519x.ParsePublicKeyPEM(flipRandomBits(rng, publicPEM))
|
||||
|
||||
// 在合法签名上随机翻转比特位。
|
||||
_ = ed25519x.Verify(pub, message, flipRandomBits(rng, signature))
|
||||
_ = ed25519x.VerifyFromBase64(pub, message, base64.StdEncoding.EncodeToString(flipRandomBits(rng, signature)))
|
||||
}
|
||||
}
|
||||
|
||||
// TestSaveKeyPairErrors 覆盖:写入路径非法时返回 error 而不是 panic。
|
||||
func TestSaveKeyPairErrors(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
// 私钥路径指向一个已存在的目录,写入必然失败。
|
||||
requireError(t, "SaveKeyPair 私钥路径为目录", ed25519x.SaveKeyPair(dir, filepath.Join(dir, "pub.pem")))
|
||||
// 公钥路径指向不存在的目录,私钥会先写入成功,随后应回滚。
|
||||
privatePath := filepath.Join(dir, "priv.pem")
|
||||
requireError(t, "SaveKeyPair 公钥路径目录不存在", ed25519x.SaveKeyPair(privatePath, filepath.Join(dir, "no-such-dir", "pub.pem")))
|
||||
if _, err := os.Stat(privatePath); err == nil {
|
||||
t.Error("公钥写入失败后应删除已写入的私钥文件")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user