Files
encryptx/rsax/rsax_test.go
T

777 lines
28 KiB
Go
Raw Normal View History

2023-12-27 19:00:14 +08:00
package rsax_test
import (
2026-09-30 15:49:12 +08:00
"bytes"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"crypto/x509"
"encoding/pem"
"errors"
"math/big"
"os"
"path/filepath"
"runtime"
"sync"
2023-12-27 19:00:14 +08:00
"testing"
"code.yun.ink/pkg/encryptx/rsax"
)
2026-09-30 15:49:12 +08:00
// testKeyBits 是测试用密钥长度,取安全下限以缩短密钥生成时间。
const testKeyBits = rsax.MinKeyBits
2023-12-27 19:00:14 +08:00
2026-09-30 15:49:12 +08:00
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("把公钥文件当私钥加载应当返回错误")
}
2023-12-27 19:00:14 +08:00
}