package rsax_test import ( "bytes" "crypto/ecdsa" "crypto/elliptic" "crypto/rand" "crypto/rsa" "crypto/sha256" "crypto/x509" "encoding/pem" "errors" "math/big" "os" "path/filepath" "runtime" "sync" "testing" "code.yun.ink/pkg/encryptx/rsax" ) // testKeyBits 是测试用密钥长度,取安全下限以缩短密钥生成时间。 const testKeyBits = rsax.MinKeyBits var ( sharedKeyOnce sync.Once sharedKey *rsa.PrivateKey sharedKeyErr error otherKeyOnce sync.Once otherKey *rsa.PrivateKey otherKeyErr error ) // testKey 返回测试共享的 RSA 私钥(进程内只生成一次,避免重复的密钥生成开销)。 // 测试辅助函数不会 panic:失败通过 t.Fatalf 报告。 func testKey(t *testing.T) *rsa.PrivateKey { t.Helper() sharedKeyOnce.Do(func() { sharedKey, sharedKeyErr = rsax.GenerateKey(testKeyBits) }) if sharedKeyErr != nil { t.Fatalf("生成测试密钥失败: %v", sharedKeyErr) } return sharedKey } // otherTestKey 返回另一把与 testKey 无关的 RSA 私钥,用于“错误密钥”负向用例。 func otherTestKey(t *testing.T) *rsa.PrivateKey { t.Helper() otherKeyOnce.Do(func() { otherKey, otherKeyErr = rsax.GenerateKey(testKeyBits) }) if otherKeyErr != nil { t.Fatalf("生成第二把测试密钥失败: %v", otherKeyErr) } return otherKey } // requireNoPanic 断言 fn 在失败路径上只返回 error,绝不 panic。 func requireNoPanic(t *testing.T, what string, fn func()) { t.Helper() defer func() { if r := recover(); r != nil { t.Fatalf("%s 发生 panic: %v", what, r) } }() fn() } // randomBytes 生成指定长度的随机数据作为测试明文。 func randomBytes(t *testing.T, n int) []byte { t.Helper() buf := make([]byte, n) if _, err := rand.Read(buf); err != nil { t.Fatalf("生成随机测试数据失败: %v", err) } return buf } // ecdsaTestKey 生成一把 ECDSA 密钥,用于构造“非 RSA 密钥”的负向用例。 func ecdsaTestKey(t *testing.T) *ecdsa.PrivateKey { t.Helper() key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) if err != nil { t.Fatalf("生成 ECDSA 测试密钥失败: %v", err) } return key } // xorByteAt 翻转 data[pos] 的一个比特位,用于篡改密文/签名。 func xorByteAt(t *testing.T, data []byte, pos int) []byte { t.Helper() if pos < 0 || pos >= len(data) { t.Fatalf("篡改位置 %d 越界(长度 %d)", pos, len(data)) } out := make([]byte, len(data)) copy(out, data) out[pos] ^= 0x01 return out } // TestGenerateKeyAndPEMRoundTrip 覆盖密钥生成、PEM 往返与公私钥一致性。 func TestGenerateKeyAndPEMRoundTrip(t *testing.T) { priv, err := rsax.GenerateKey(rsax.DefaultKeyBits) if err != nil { t.Fatalf("GenerateKey(%d) 失败: %v", rsax.DefaultKeyBits, err) } if got := priv.N.BitLen(); got != rsax.DefaultKeyBits { t.Fatalf("密钥位宽 = %d, 期望 %d", got, rsax.DefaultKeyBits) } privatePEM, err := rsax.MarshalPrivateKeyPEM(priv) if err != nil { t.Fatalf("MarshalPrivateKeyPEM 失败: %v", err) } publicPEM, err := rsax.MarshalPublicKeyPEM(&priv.PublicKey) if err != nil { t.Fatalf("MarshalPublicKeyPEM 失败: %v", err) } if block, _ := pem.Decode(privatePEM); block == nil || block.Type != "PRIVATE KEY" { t.Fatalf("私钥 PEM 标签不是 PRIVATE KEY: %#v", block) } if block, _ := pem.Decode(publicPEM); block == nil || block.Type != "PUBLIC KEY" { t.Fatalf("公钥 PEM 标签不是 PUBLIC KEY: %#v", block) } parsedPriv, err := rsax.ParsePrivateKeyPEM(privatePEM) if err != nil { t.Fatalf("ParsePrivateKeyPEM 失败: %v", err) } if parsedPriv.D.Cmp(priv.D) != 0 || parsedPriv.N.Cmp(priv.N) != 0 { t.Fatal("私钥 PEM 往返后参数不一致") } parsedPub, err := rsax.ParsePublicKeyPEM(publicPEM) if err != nil { t.Fatalf("ParsePublicKeyPEM 失败: %v", err) } if !parsedPub.Equal(&priv.PublicKey) { t.Fatal("公钥 PEM 往返后与私钥内公钥不一致") } if !parsedPub.Equal(&parsedPriv.PublicKey) { t.Fatal("公钥 PEM 与私钥 PEM 导出的公钥不一致") } // GenerateKeyPairPEM 直接产出的 PEM 也必须可解析且互相匹配。 genPrivatePEM, genPublicPEM, err := rsax.GenerateKeyPairPEM(testKeyBits) if err != nil { t.Fatalf("GenerateKeyPairPEM(%d) 失败: %v", testKeyBits, err) } genPriv, err := rsax.ParsePrivateKeyPEM(genPrivatePEM) if err != nil { t.Fatalf("解析 GenerateKeyPairPEM 私钥失败: %v", err) } genPub, err := rsax.ParsePublicKeyPEM(genPublicPEM) if err != nil { t.Fatalf("解析 GenerateKeyPairPEM 公钥失败: %v", err) } if !genPub.Equal(&genPriv.PublicKey) { t.Fatal("GenerateKeyPairPEM 的公私钥不匹配") } } // TestParsePrivateKeyPEMAcceptsPKCS1 覆盖 PKCS#1 私钥的兼容解析。 func TestParsePrivateKeyPEMAcceptsPKCS1(t *testing.T) { priv := testKey(t) pkcs1PEM := pem.EncodeToMemory(&pem.Block{ Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv), }) parsed, err := rsax.ParsePrivateKeyPEM(pkcs1PEM) if err != nil { t.Fatalf("解析 PKCS#1 私钥失败: %v", err) } if parsed.D.Cmp(priv.D) != 0 { t.Fatal("PKCS#1 私钥往返后 D 不一致") } } // TestParsePublicKeyPEMAcceptsPKCS1 覆盖 PKCS#1 公钥的兼容解析。 func TestParsePublicKeyPEMAcceptsPKCS1(t *testing.T) { priv := testKey(t) pkcs1PEM := pem.EncodeToMemory(&pem.Block{ Type: "RSA PUBLIC KEY", Bytes: x509.MarshalPKCS1PublicKey(&priv.PublicKey), }) parsed, err := rsax.ParsePublicKeyPEM(pkcs1PEM) if err != nil { t.Fatalf("解析 PKCS#1 公钥失败: %v", err) } if !parsed.Equal(&priv.PublicKey) { t.Fatal("PKCS#1 公钥往返后不一致") } } // TestGenerateKeyRejectsInvalidBits 覆盖密钥长度下限/上限及非 RSA 尺寸的拒绝逻辑。 func TestGenerateKeyRejectsInvalidBits(t *testing.T) { for _, bits := range []int{-1024, -1, 0, 1, 512, 1024, 2047, rsax.MinKeyBits - 1, rsax.MaxKeyBits + 1, 16384} { if _, err := rsax.GenerateKey(bits); err == nil { t.Fatalf("GenerateKey(%d) 应当返回错误,实际返回 nil", bits) } } // 弱密钥同样不能进入 PEM 生成路径。 for _, bits := range []int{0, 1024} { if _, _, err := rsax.GenerateKeyPairPEM(bits); err == nil { t.Fatalf("GenerateKeyPairPEM(%d) 应当返回错误,实际返回 nil", bits) } } } // TestOAEPRoundTripAndTamper 覆盖 OAEP 往返、错误私钥解密与密文篡改检测。 func TestOAEPRoundTripAndTamper(t *testing.T) { priv := testKey(t) message := []byte("RSA-OAEP 往返测试 with ASCII + 中文 + 符号 !@#$%^&*()") ciphertext, err := rsax.EncryptOAEP(&priv.PublicKey, message) if err != nil { t.Fatalf("EncryptOAEP 失败: %v", err) } if len(ciphertext) != priv.Size() { t.Fatalf("单块密文长度 = %d, 期望 %d", len(ciphertext), priv.Size()) } plaintext, err := rsax.DecryptOAEP(priv, ciphertext) if err != nil { t.Fatalf("DecryptOAEP 失败: %v", err) } if !bytes.Equal(plaintext, message) { t.Fatalf("解密结果 = %q, 期望 %q", plaintext, message) } // 用另一把密钥解密必须失败。 if _, err := rsax.DecryptOAEP(otherTestKey(t), ciphertext); err == nil { t.Fatal("使用错误私钥解密应当失败,实际成功") } // OAEP 有严格的填充校验:逐字节篡改后解密必须全部失败。 k := priv.Size() for _, pos := range []int{0, k / 3, k / 2, k - 3, k - 2, k - 1} { tampered := xorByteAt(t, ciphertext, pos) if _, err := rsax.DecryptOAEP(priv, tampered); err == nil { t.Fatalf("篡改密文第 %d 字节后解密应当失败,实际成功", pos) } } // 长度不合法的密文必须被拒绝。 for _, bad := range [][]byte{{}, ciphertext[:k-1], append(append([]byte{}, ciphertext...), 0x00)} { if _, err := rsax.DecryptOAEP(priv, bad); err == nil { t.Fatalf("长度 %d 的密文应当被拒绝", len(bad)) } } } // TestEncryptOAEPLong 覆盖远超单块上限的分块加解密往返与分块错误处理。 func TestEncryptOAEPLong(t *testing.T) { priv := testKey(t) pub := &priv.PublicKey k := priv.Size() maxSingle := k - 2*sha256.Size - 2 // 分块路径:5000 字节远超单块上限,覆盖“满块 + 末块更短”的组合。 message := randomBytes(t, 5000) ciphertext, err := rsax.EncryptOAEPLong(pub, message) if err != nil { t.Fatalf("EncryptOAEPLong(5000) 失败: %v", err) } expectedBlocks := (len(message) + maxSingle - 1) / maxSingle if len(ciphertext) != expectedBlocks*k { t.Fatalf("分块密文长度 = %d, 期望 %d(%d 块 × %d)", len(ciphertext), expectedBlocks*k, expectedBlocks, k) } if len(ciphertext) <= k { t.Fatal("5000 字节明文应当产生多块密文") } plaintext, err := rsax.DecryptOAEPLong(priv, ciphertext) if err != nil { t.Fatalf("DecryptOAEPLong 失败: %v", err) } if !bytes.Equal(plaintext, message) { t.Fatalf("分块往返结果不一致: 长度 %d vs %d", len(plaintext), len(message)) } // 单块路径:短明文同样走 EncryptOAEPLong 时只有一块。 short := []byte("short") shortCipher, err := rsax.EncryptOAEPLong(pub, short) if err != nil { t.Fatalf("EncryptOAEPLong(short) 失败: %v", err) } if len(shortCipher) != k { t.Fatalf("短明文分块密文长度 = %d, 期望单块 %d", len(shortCipher), k) } shortPlain, err := rsax.DecryptOAEPLong(priv, shortCipher) if err != nil { t.Fatalf("DecryptOAEPLong(short) 失败: %v", err) } if !bytes.Equal(shortPlain, short) { t.Fatalf("短明文分块往返结果 = %q, 期望 %q", shortPlain, short) } // 单块上限边界:恰好等于上限可以加密,恰好超过上限必须报错并提示改用 Long 版本。 exact := randomBytes(t, maxSingle) if _, err := rsax.EncryptOAEP(pub, exact); err != nil { t.Fatalf("恰好 %d 字节的明文应当可以单块加密: %v", maxSingle, err) } if _, err := rsax.EncryptOAEP(pub, randomBytes(t, maxSingle+1)); err == nil { t.Fatal("超过单块上限的明文应当返回错误") } if _, err := rsax.EncryptOAEPLong(pub, exact); err != nil { t.Fatalf("Long 版本加密边界长度失败: %v", err) } // 密文长度不是 k 的整数倍时 DecryptOAEPLong 必须报错。 if _, err := rsax.DecryptOAEPLong(priv, ciphertext[:len(ciphertext)-1]); err == nil { t.Fatal("非 k 整数倍长度的密文应当返回错误") } if _, err := rsax.DecryptOAEPLong(priv, nil); err == nil { t.Fatal("空密文应当返回错误") } // 篡改分块密文(最后一字节)后整串解密必须失败。 if _, err := rsax.DecryptOAEPLong(priv, xorByteAt(t, ciphertext, len(ciphertext)-1)); err == nil { t.Fatal("篡改分块密文后解密应当失败") } // 空明文往返为空密文。 emptyCipher, err := rsax.EncryptOAEPLong(pub, nil) if err != nil { t.Fatalf("EncryptOAEPLong(nil) 失败: %v", err) } if len(emptyCipher) != 0 { t.Fatalf("空明文密文长度 = %d, 期望 0", len(emptyCipher)) } } // TestEncryptOAEPRejectsOversizedPlaintext 单独固化“单块超长必须报错”的行为。 func TestEncryptOAEPRejectsOversizedPlaintext(t *testing.T) { priv := testKey(t) maxSingle := priv.Size() - 2*sha256.Size - 2 _, err := rsax.EncryptOAEP(&priv.PublicKey, randomBytes(t, maxSingle+1)) if err == nil { t.Fatal("超过单块上限的明文应当返回错误") } if !bytes.Contains([]byte(err.Error()), []byte("EncryptOAEPLong")) { t.Fatalf("错误信息应提示改用 EncryptOAEPLong, 实际: %v", err) } } // TestPKCS1v15EncryptRoundTrip 覆盖兼容用 PKCS#1 v1.5 加解密路径。 func TestPKCS1v15EncryptRoundTrip(t *testing.T) { priv := testKey(t) message := []byte("PKCS#1 v1.5 legacy path") ciphertext, err := rsax.EncryptPKCS1v15(&priv.PublicKey, message) if err != nil { t.Fatalf("EncryptPKCS1v15 失败: %v", err) } plaintext, err := rsax.DecryptPKCS1v15(priv, ciphertext) if err != nil { t.Fatalf("DecryptPKCS1v15 失败: %v", err) } if !bytes.Equal(plaintext, message) { t.Fatalf("解密结果 = %q, 期望 %q", plaintext, message) } if _, err := rsax.DecryptPKCS1v15(otherTestKey(t), ciphertext); err == nil { t.Fatal("使用错误私钥解密 PKCS#1 v1.5 密文应当失败") } if _, err := rsax.DecryptPKCS1v15(priv, xorByteAt(t, ciphertext, priv.Size()-1)); err == nil { t.Fatal("篡改 PKCS#1 v1.5 密文后解密应当失败") } // 明文超过 len(pub)-11 时底层实现应返回错误而非 panic。 if _, err := rsax.EncryptPKCS1v15(&priv.PublicKey, randomBytes(t, priv.Size()-10)); err == nil { t.Fatal("超过 PKCS#1 v1.5 长度上限的明文应当返回错误") } } // TestSignVerifyPSS 覆盖 PSS 签名/验签往返与篡改检测。 func TestSignVerifyPSS(t *testing.T) { priv := testKey(t) message := []byte("需要签名的消息 with 中文") signature, err := rsax.SignPSS(priv, message) if err != nil { t.Fatalf("SignPSS 失败: %v", err) } if len(signature) != priv.Size() { t.Fatalf("PSS 签名长度 = %d, 期望 %d", len(signature), priv.Size()) } if err := rsax.VerifyPSS(&priv.PublicKey, message, signature); err != nil { t.Fatalf("VerifyPSS 对合法签名返回错误: %v", err) } if err := rsax.VerifyPSS(&priv.PublicKey, []byte("被篡改的消息"), signature); err == nil { t.Fatal("消息被篡改后验签应当失败") } if err := rsax.VerifyPSS(&priv.PublicKey, message, xorByteAt(t, signature, len(signature)/2)); err == nil { t.Fatal("签名被篡改后验签应当失败") } if err := rsax.VerifyPSS(&priv.PublicKey, message, signature[:len(signature)-1]); err == nil { t.Fatal("签名长度不合法时应当返回错误") } if err := rsax.VerifyPSS(&otherTestKey(t).PublicKey, message, signature); err == nil { t.Fatal("使用错误公钥验签应当失败") } } // TestSignVerifyPKCS1v15 覆盖 PKCS#1 v1.5 签名/验签往返与篡改检测。 func TestSignVerifyPKCS1v15(t *testing.T) { priv := testKey(t) message := []byte("PKCS#1 v1.5 signed message") signature, err := rsax.SignPKCS1v15(priv, message) if err != nil { t.Fatalf("SignPKCS1v15 失败: %v", err) } if err := rsax.VerifyPKCS1v15(&priv.PublicKey, message, signature); err != nil { t.Fatalf("VerifyPKCS1v15 对合法签名返回错误: %v", err) } if err := rsax.VerifyPKCS1v15(&priv.PublicKey, []byte("tampered message"), signature); err == nil { t.Fatal("消息被篡改后验签应当失败") } if err := rsax.VerifyPKCS1v15(&priv.PublicKey, message, xorByteAt(t, signature, 0)); err == nil { t.Fatal("签名被篡改后验签应当失败") } if err := rsax.VerifyPKCS1v15(&otherTestKey(t).PublicKey, message, signature); err == nil { t.Fatal("使用错误公钥验签应当失败") } } // TestVerifyWrapsErrVerification 确认验签失败包装了 rsa.ErrVerification,便于 errors.Is 判断。 func TestVerifyWrapsErrVerification(t *testing.T) { priv := testKey(t) message := []byte("errors.Is 检查") pssSig, err := rsax.SignPSS(priv, message) if err != nil { t.Fatalf("SignPSS 失败: %v", err) } if err := rsax.VerifyPSS(&priv.PublicKey, []byte("other"), pssSig); !errors.Is(err, rsa.ErrVerification) { t.Fatalf("PSS 失败应包装 rsa.ErrVerification, 实际: %v", err) } // 长度非法的签名属于输入错误,不应伪装成 rsa.ErrVerification。 if err := rsax.VerifyPSS(&priv.PublicKey, message, []byte("short")); errors.Is(err, rsa.ErrVerification) { t.Fatalf("长度非法的签名不应返回 rsa.ErrVerification, 实际: %v", err) } v15Sig, err := rsax.SignPKCS1v15(priv, message) if err != nil { t.Fatalf("SignPKCS1v15 失败: %v", err) } if err := rsax.VerifyPKCS1v15(&priv.PublicKey, []byte("other"), v15Sig); !errors.Is(err, rsa.ErrVerification) { t.Fatalf("PKCS#1 v1.5 失败应包装 rsa.ErrVerification, 实际: %v", err) } if err := rsax.VerifyPKCS1v15(&priv.PublicKey, message, []byte("short")); errors.Is(err, rsa.ErrVerification) { t.Fatalf("长度非法的签名不应返回 rsa.ErrVerification, 实际: %v", err) } } // TestParsePEMRejectsBadInput 覆盖垃圾输入、空输入、合法 PEM 但内容错误等场景。 func TestParsePEMRejectsBadInput(t *testing.T) { priv := testKey(t) validPublicPEM, err := rsax.MarshalPublicKeyPEM(&priv.PublicKey) if err != nil { t.Fatalf("准备合法公钥 PEM 失败: %v", err) } // 其他算法生成的 PKIX 公钥(ECDSA)必须被明确拒绝,而不是类型断言 panic。 ecdsaDER, err := x509.MarshalPKIXPublicKey(&ecdsaTestKey(t).PublicKey) if err != nil { t.Fatalf("编码 ECDSA 公钥失败: %v", err) } badInputs := []struct { name string data []byte }{ {"nil 输入", nil}, {"空切片", []byte{}}, {"纯文本垃圾", []byte("this is not a pem file at all")}, {"只有 BEGIN 行", []byte("-----BEGIN RSA PRIVATE KEY-----\n")}, {"PEM 边界不完整", []byte("-----BEGIN PUBLIC KEY-----\nQUJD\n")}, {"私钥标签但内容是随机字节", pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: []byte("garbage bytes here")})}, {"私钥标签但内容是公钥 DER", func() []byte { block, _ := pem.Decode(validPublicPEM) return pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: block.Bytes}) }()}, {"PKCS#8 标签但内容是 PKCS#1 私钥", pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv)})}, {"EC PRIVATE KEY 标签", pem.EncodeToMemory(&pem.Block{Type: "EC PRIVATE KEY", Bytes: []byte("junk")})}, } for _, tc := range badInputs { tc := tc t.Run("private/"+tc.name, func(t *testing.T) { var err error requireNoPanic(t, "ParsePrivateKeyPEM", func() { _, err = rsax.ParsePrivateKeyPEM(tc.data) }) if err == nil { t.Fatalf("ParsePrivateKeyPEM(%s) 应当返回错误", tc.name) } }) t.Run("public/"+tc.name, func(t *testing.T) { var err error requireNoPanic(t, "ParsePublicKeyPEM", func() { _, err = rsax.ParsePublicKeyPEM(tc.data) }) if err == nil { t.Fatalf("ParsePublicKeyPEM(%s) 应当返回错误", tc.name) } }) } // 公钥专属:合法 PUBLIC KEY PEM 但内容是私钥 DER、或非 RSA 的 PKIX 公钥。 publicBadInputs := []struct { name string data []byte }{ {"公钥标签但内容是 PKCS#1 私钥", pem.EncodeToMemory(&pem.Block{Type: "PUBLIC KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv)})}, {"公钥标签但内容是 PKCS#8 私钥", func() []byte { der, err := x509.MarshalPKCS8PrivateKey(priv) if err != nil { t.Fatalf("编码 PKCS#8 私钥失败: %v", err) } return pem.EncodeToMemory(&pem.Block{Type: "PUBLIC KEY", Bytes: der}) }()}, {"PKIX 标签但内容是 ECDSA 公钥", pem.EncodeToMemory(&pem.Block{Type: "PUBLIC KEY", Bytes: ecdsaDER})}, {"CERTIFICATE 标签", pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: []byte("junk")})}, } for _, tc := range publicBadInputs { tc := tc t.Run("public/"+tc.name, func(t *testing.T) { var err error requireNoPanic(t, "ParsePublicKeyPEM", func() { _, err = rsax.ParsePublicKeyPEM(tc.data) }) if err == nil { t.Fatalf("ParsePublicKeyPEM(%s) 应当返回错误", tc.name) } }) } } // TestWeakKeyPolicy 锁定弱密钥策略。 // // 该策略有意区分「生成」与「使用」两条路径: // // - 生成:一律要求 ≥ MinKeyBits(2048),绝不产生弱密钥; // - 使用既有的 1024 位密钥:允许解析、导出、加解密与签名。旧系统里真实存在 // 这类密钥,若连解析都拒绝,持有者将无法解密自己的历史数据——那是把安全 // 策略变成了功能性故障; // - 但调用方可以通过 IsWeakPrivateKey / IsWeakPublicKey 得知它已不达标; // - 低于 MinUsableKeyBits(1024) 的密钥无论如何都拒绝。 func TestWeakKeyPolicy(t *testing.T) { // 1. 生成环节必须拒绝弱密钥 for _, bits := range []int{512, 1024, 2047} { if _, err := rsax.GenerateKey(bits); !errors.Is(err, rsax.ErrInvalidKeySize) { t.Errorf("GenerateKey(%d) 应当被拒绝, 得到 %v", bits, err) } if _, _, err := rsax.GenerateKeyPairPEM(bits); !errors.Is(err, rsax.ErrInvalidKeySize) { t.Errorf("GenerateKeyPairPEM(%d) 应当被拒绝, 得到 %v", bits, err) } } // 2. 既有的 1024 位密钥:允许导出、解析与使用 legacy, err := rsa.GenerateKey(rand.Reader, 1024) if err != nil { t.Fatalf("生成 1024 位测试密钥失败: %v", err) } if !rsax.IsWeakPrivateKey(legacy) { t.Error("IsWeakPrivateKey(1024 位) = false, 期望 true") } if !rsax.IsWeakPublicKey(&legacy.PublicKey) { t.Error("IsWeakPublicKey(1024 位) = false, 期望 true") } privatePEM, err := rsax.MarshalPrivateKeyPEM(legacy) if err != nil { t.Fatalf("既有 1024 位私钥应当允许导出: %v", err) } publicPEM, err := rsax.MarshalPublicKeyPEM(&legacy.PublicKey) if err != nil { t.Fatalf("既有 1024 位公钥应当允许导出: %v", err) } parsed, err := rsax.ParsePrivateKeyPEM(privatePEM) if err != nil { t.Fatalf("既有 1024 位私钥应当允许解析: %v", err) } if _, err := rsax.ParsePublicKeyPEM(publicPEM); err != nil { t.Fatalf("既有 1024 位公钥应当允许解析: %v", err) } // 加解密与签名都要能跑通,否则"允许使用"只是空话 plaintext := []byte("legacy data that must stay readable") ciphertext, err := rsax.EncryptOAEPLong(&legacy.PublicKey, plaintext) if err != nil { t.Fatalf("1024 位密钥加密失败: %v", err) } decrypted, err := rsax.DecryptOAEPLong(parsed, ciphertext) if err != nil { t.Fatalf("1024 位密钥解密失败: %v", err) } if !bytes.Equal(decrypted, plaintext) { t.Error("1024 位密钥加解密往返不一致") } signature, err := rsax.SignPSS(parsed, plaintext) if err != nil { t.Fatalf("1024 位密钥签名失败: %v", err) } if err := rsax.VerifyPSS(&legacy.PublicKey, plaintext, signature); err != nil { t.Fatalf("1024 位密钥验签失败: %v", err) } // 2048 位密钥不应被标记为弱 strong, err := rsa.GenerateKey(rand.Reader, 2048) if err != nil { t.Fatalf("生成 2048 位密钥失败: %v", err) } if rsax.IsWeakPrivateKey(strong) { t.Error("IsWeakPrivateKey(2048 位) = true, 期望 false") } if rsax.IsWeakPublicKey(&strong.PublicKey) { t.Error("IsWeakPublicKey(2048 位) = true, 期望 false") } // 3. 低于可使用下限:一律拒绝 // // 这里用合成密钥而不是 rsa.GenerateKey:Go 1.26 的标准库已拒绝生成低于 // 1024 位的密钥,恰好与本库的 MinUsableKeyBits 一致,因此无法用标准库造出 // 更弱的密钥来做测试。 tooWeakPriv := &rsa.PrivateKey{ PublicKey: rsa.PublicKey{N: new(big.Int).Lsh(big.NewInt(1), 511), E: 65537}, D: big.NewInt(3), } tooWeakPub := &rsa.PublicKey{N: new(big.Int).Lsh(big.NewInt(1), 511), E: 65537} if tooWeakPriv.N.BitLen() >= rsax.MinUsableKeyBits { t.Fatalf("测试前提不成立: 合成密钥有 %d 位", tooWeakPriv.N.BitLen()) } if _, err := rsax.MarshalPrivateKeyPEM(tooWeakPriv); !errors.Is(err, rsax.ErrInvalidKeySize) { t.Errorf("512 位私钥应当拒绝导出, 得到 %v", err) } if _, err := rsax.MarshalPublicKeyPEM(tooWeakPub); !errors.Is(err, rsax.ErrInvalidKeySize) { t.Errorf("512 位公钥应当拒绝导出, 得到 %v", err) } if _, err := rsax.EncryptOAEP(tooWeakPub, []byte("x")); !errors.Is(err, rsax.ErrInvalidKeySize) { t.Errorf("512 位公钥应当拒绝加密, 得到 %v", err) } if _, err := rsax.SignPSS(tooWeakPriv, []byte("x")); !errors.Is(err, rsax.ErrInvalidKeySize) { t.Errorf("512 位私钥应当拒绝签名, 得到 %v", err) } if _, err := rsax.DecryptOAEP(tooWeakPriv, []byte("x")); !errors.Is(err, rsax.ErrInvalidKeySize) { t.Errorf("512 位私钥应当拒绝解密, 得到 %v", err) } if err := rsax.VerifyPSS(tooWeakPub, []byte("x"), []byte("y")); !errors.Is(err, rsax.ErrInvalidKeySize) { t.Errorf("512 位公钥应当拒绝验签, 得到 %v", err) } // 4. nil 一律视为弱密钥 if !rsax.IsWeakPrivateKey(nil) || !rsax.IsWeakPublicKey(nil) { t.Error("nil 密钥应当被视为弱密钥") } } // TestNilKeysReturnErrorRatherThanPanic 确认 nil 密钥在各入口返回错误而非 panic。 func TestNilKeysReturnErrorRatherThanPanic(t *testing.T) { // nil 密钥必须返回错误而不是 panic。 requireNoPanic(t, "EncryptOAEP(nil)", func() { if _, err := rsax.EncryptOAEP(nil, []byte("x")); err == nil { t.Error("EncryptOAEP(nil) 应当返回错误") } }) requireNoPanic(t, "DecryptOAEP(nil)", func() { if _, err := rsax.DecryptOAEP(nil, []byte("x")); err == nil { t.Error("DecryptOAEP(nil) 应当返回错误") } }) requireNoPanic(t, "SignPSS(nil)", func() { if _, err := rsax.SignPSS(nil, []byte("x")); err == nil { t.Error("SignPSS(nil) 应当返回错误") } }) requireNoPanic(t, "VerifyPSS(nil)", func() { if err := rsax.VerifyPSS(nil, nil, nil); err == nil { t.Error("VerifyPSS(nil) 应当返回错误") } }) requireNoPanic(t, "MarshalPrivateKeyPEM(nil)", func() { if _, err := rsax.MarshalPrivateKeyPEM(nil); err == nil { t.Error("MarshalPrivateKeyPEM(nil) 应当返回错误") } }) requireNoPanic(t, "MarshalPublicKeyPEM(nil)", func() { if _, err := rsax.MarshalPublicKeyPEM(nil); err == nil { t.Error("MarshalPublicKeyPEM(nil) 应当返回错误") } }) } // TestSaveAndLoadKeyPair 覆盖文件落盘、加载往返与私钥文件权限。 func TestSaveAndLoadKeyPair(t *testing.T) { dir := t.TempDir() privatePath := filepath.Join(dir, "private.pem") publicPath := filepath.Join(dir, "public.pem") if err := rsax.SaveKeyPair(privatePath, publicPath, testKeyBits); err != nil { t.Fatalf("SaveKeyPair 失败: %v", err) } priv, err := rsax.LoadPrivateKey(privatePath) if err != nil { t.Fatalf("LoadPrivateKey 失败: %v", err) } pub, err := rsax.LoadPublicKey(publicPath) if err != nil { t.Fatalf("LoadPublicKey 失败: %v", err) } if !pub.Equal(&priv.PublicKey) { t.Fatal("从文件加载的公私钥不匹配") } if priv.N.BitLen() != testKeyBits { t.Fatalf("加载的私钥位宽 = %d, 期望 %d", priv.N.BitLen(), testKeyBits) } // 加载出的密钥必须真的可用。 message := []byte("落盘密钥可用性检查") ciphertext, err := rsax.EncryptOAEP(pub, message) if err != nil { t.Fatalf("用加载的公钥加密失败: %v", err) } plaintext, err := rsax.DecryptOAEP(priv, ciphertext) if err != nil { t.Fatalf("用加载的私钥解密失败: %v", err) } if !bytes.Equal(plaintext, message) { t.Fatalf("落盘密钥往返结果 = %q, 期望 %q", plaintext, message) } // Windows 的权限位语义与 POSIX 不同(os.Stat().Perm() 通常恒为 0666),因此跳过断言。 if runtime.GOOS == "windows" { t.Log("Windows 平台跳过私钥文件权限位断言(POSIX 权限位语义不适用)") } else { info, err := os.Stat(privatePath) if err != nil { t.Fatalf("Stat 私钥文件失败: %v", err) } if perm := info.Mode().Perm(); perm != 0o600 { t.Fatalf("私钥文件权限 = %#o, 期望 0600", perm) } publicInfo, err := os.Stat(publicPath) if err != nil { t.Fatalf("Stat 公钥文件失败: %v", err) } if perm := publicInfo.Mode().Perm(); perm != 0o644 { t.Fatalf("公钥文件权限 = %#o, 期望 0644", perm) } } // 路径非法时必须返回错误。 if err := rsax.SaveKeyPair(filepath.Join(dir, "same.pem"), filepath.Join(dir, "same.pem"), testKeyBits); err == nil { t.Fatal("私钥与公钥路径相同时应当返回错误") } if err := rsax.SaveKeyPair("", publicPath, testKeyBits); err == nil { t.Fatal("私钥路径为空时应当返回错误") } if _, err := rsax.LoadPrivateKey(filepath.Join(dir, "not-exist.pem")); err == nil { t.Fatal("加载不存在的私钥文件应当返回错误") } if _, err := rsax.LoadPublicKey(filepath.Join(dir, "not-exist.pem")); err == nil { t.Fatal("加载不存在的公钥文件应当返回错误") } if _, err := rsax.LoadPrivateKey(""); err == nil { t.Fatal("私钥路径为空时应当返回错误") } if _, err := rsax.LoadPublicKey(""); err == nil { t.Fatal("公钥路径为空时应当返回错误") } if _, err := rsax.LoadPrivateKey(publicPath); err == nil { t.Fatal("把公钥文件当私钥加载应当返回错误") } }