55 lines
2.1 KiB
Go
55 lines
2.1 KiB
Go
// Package secretchat 实现私聊端对端加密(Secret Chat / EncryptedChat)的握手
|
||
// 状态机。服务端是盲中继:g_a/g_b/key_fingerprint/加密 bytes 全部不透明存储与
|
||
// 原样转发,唯一参与密码学的点是对 g_a/g_b 做 DH 范围边界校验(防弱 DH/MITM)。
|
||
// 共享密钥与明文是 E2E 客户端职责,服务端不知道也无法计算。
|
||
// 设计见 docs/secret-chat-module.md。
|
||
package secretchat
|
||
|
||
import (
|
||
"errors"
|
||
"math/big"
|
||
|
||
appphone "telesrv/internal/app/phone"
|
||
)
|
||
|
||
// dhPubSize 是 g_a/g_b 的规范字节长度(2048-bit)。
|
||
const dhPubSize = 256
|
||
|
||
// ErrGAInvalid:g_a/g_b 不在合法 DH 区间 → rpc 层映射为 DH_G_A_INVALID。
|
||
var ErrGAInvalid = errors.New("secretchat: dh parameter invalid")
|
||
|
||
var (
|
||
// dhPrimeMinusOne/边界值复用 phone 域官方 2048-bit safe prime(与 getDhConfig 下发的 p 同源)。
|
||
dhPrimeMinusOne = new(big.Int).Sub(new(big.Int).SetBytes(appphone.DHPrime()), big.NewInt(1))
|
||
// 2^(2048-64) 下界与 p-2^(2048-64) 上界(官方 isGoodPrime 同款边界)。
|
||
dhLowerBound = new(big.Int).Lsh(big.NewInt(1), 2048-64)
|
||
dhUpperBound = new(big.Int).Sub(new(big.Int).SetBytes(appphone.DHPrime()), new(big.Int).Lsh(big.NewInt(1), 2048-64))
|
||
dhOne = big.NewInt(1)
|
||
)
|
||
|
||
// validateDHParam 对 g_a/g_b 做范围校验,通过后左补零到 256 字节返回(规范线格式;
|
||
// 首字节为 0 被裁的合法 g_a 不能误拒)。校验用的是 big.Int 值,与补零无关。
|
||
func validateDHParam(g []byte) ([]byte, error) {
|
||
if len(g) == 0 || len(g) > dhPubSize {
|
||
return nil, ErrGAInvalid
|
||
}
|
||
x := new(big.Int).SetBytes(g)
|
||
// 1 < x < p-1 且 2^(2048-64) < x < p-2^(2048-64)
|
||
if x.Cmp(dhOne) <= 0 || x.Cmp(dhPrimeMinusOne) >= 0 {
|
||
return nil, ErrGAInvalid
|
||
}
|
||
if x.Cmp(dhLowerBound) <= 0 || x.Cmp(dhUpperBound) >= 0 {
|
||
return nil, ErrGAInvalid
|
||
}
|
||
return leftPad256(g), nil
|
||
}
|
||
|
||
// leftPad256 左补零到 256 字节(输入已保证 ≤256)。
|
||
func leftPad256(b []byte) []byte {
|
||
if len(b) == dhPubSize {
|
||
return append([]byte(nil), b...)
|
||
}
|
||
out := make([]byte, dhPubSize)
|
||
copy(out[dhPubSize-len(b):], b)
|
||
return out
|
||
}
|