Files
ofdgo/ofdgo_sign_write_crypto.go
T

335 lines
13 KiB
Go

// Copyright 2025-2026 肖其顿 (XIAO QI DUN)
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package ofdgo
import (
"bytes"
"crypto"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"crypto/x509"
"crypto/x509/pkix"
"encoding/asn1"
"encoding/pem"
"fmt"
"math/big"
"reflect"
"time"
"github.com/emmansun/gmsm/pkcs8"
"github.com/emmansun/gmsm/sm2"
"github.com/emmansun/gmsm/sm3"
"github.com/emmansun/gmsm/smx509"
)
// ParseSignatureIdentity 解析签署私钥和证书,并检查公私钥对应关系
// 支持普通PKCS8、SEC1、PKCS1以及加密PKCS8,不支持传统加密PEM
// 不加载系统信任或暗中信任该证书,返回的SM2/RSA私钥同时实现crypto.Decrypter
// 入参: certificate 证书DER或PEM, privateKey 私钥DER或PEM, password 可选的单一私钥口令
// 返回: crypto.Signer 签署器, []byte 证书DER, error 错误信息
func ParseSignatureIdentity(certificate, privateKey []byte, password ...[]byte) (crypto.Signer, []byte, error) {
if len(password) > 1 {
return nil, nil, fmt.Errorf("expected at most one private key password")
}
cert, err := parseSignatureCertificate(certificate)
if err != nil {
return nil, nil, err
}
der := privateKey
encrypted := false
if block, rest := pem.Decode(privateKey); block != nil {
if len(bytes.TrimSpace(rest)) != 0 || len(block.Headers) != 0 {
return nil, nil, fmt.Errorf("legacy encrypted PEM or multiple private keys are unsupported")
}
encrypted = block.Type == "ENCRYPTED PRIVATE KEY"
if !encrypted && block.Type != "PRIVATE KEY" && block.Type != "RSA PRIVATE KEY" && block.Type != "EC PRIVATE KEY" {
return nil, nil, fmt.Errorf("unsupported private key PEM type")
}
der = block.Bytes
}
var raw asn1.RawValue
if rest, err := asn1.Unmarshal(der, &raw); err != nil || len(rest) != 0 || raw.Tag != asn1.TagSequence || raw.Class != asn1.ClassUniversal {
return nil, nil, fmt.Errorf("invalid private key encoding")
}
var pass []byte
if len(password) == 1 {
pass = bytes.Clone(password[0])
defer clear(pass)
}
if encrypted && len(pass) == 0 {
return nil, nil, fmt.Errorf("private key password required")
}
key, err := pkcs8.ParsePKCS8PrivateKey(der, pass)
if err != nil && !encrypted {
key, err = smx509.ParsePKCS8PrivateKey(der)
if err != nil {
key, err = smx509.ParseTypedECPrivateKey(der)
}
if err != nil {
key, err = x509.ParsePKCS1PrivateKey(der)
}
}
if err != nil {
return nil, nil, fmt.Errorf("invalid private key or incorrect password")
}
signer, ok := key.(crypto.Signer)
if !ok {
return nil, nil, fmt.Errorf("unsupported signature private key")
}
if _, _, err := signatureWriteAlgorithm(signer, cert); err != nil {
return nil, nil, err
}
return signer, bytes.Clone(cert.Raw), nil
}
// signatureWriteKey 已验证签署身份
type signatureWriteKey struct {
cert *smx509.Certificate
method string
digest string
}
// signatureWriteIdentity 检查签署身份、证书信任与印章授权
// 制章证书按制章时间验证,不以签署时间代替制章时间
// 入参: options 签署选项
// 返回: *signatureWriteKey 已验证身份, error 错误信息
func signatureWriteIdentity(options SignatureWriteOptions) (*signatureWriteKey, error) {
cert, err := parseSignatureCertificate(options.Certificate)
if err != nil {
return nil, err
}
method, digest, err := signatureWriteAlgorithm(options.Signer, cert)
if err != nil {
return nil, err
}
if err := signatureWriteTrust(cert, options.Time, options); err != nil {
return nil, fmt.Errorf("signer certificate: %w", err)
}
if len(options.Seal) > 0 {
if method != signMethodSM2SM3 {
return nil, fmt.Errorf("SES v4 writing requires SM2/SM3")
}
var raw asn1.RawValue
rest, err := asn1.Unmarshal(options.Seal, &raw)
if err != nil || len(rest) != 0 || raw.Tag != asn1.TagSequence || raw.Class != asn1.ClassUniversal {
return nil, fmt.Errorf("invalid SES seal DER; image files are not electronic seals")
}
if err := signatureWriteSealStructure(raw); err != nil {
return nil, err
}
seal, err := parseSESSeal(raw)
if err != nil {
return nil, err
}
if seal.Info.Version != 4 || seal.SignAlg != signMethodSM2SM3 {
return nil, fmt.Errorf("only SES v4 SM2/SM3 seals are supported")
}
if !sesCertInList(cert.Raw, seal.CertList) {
return nil, fmt.Errorf("signer certificate is not authorized by the seal")
}
if seal.Info.CreateTime.IsZero() || seal.Info.ValidStart.IsZero() || seal.Info.ValidEnd.IsZero() || options.Time.Before(seal.Info.CreateTime) || options.Time.Before(seal.Info.ValidStart) || options.Time.After(seal.Info.ValidEnd) || seal.Info.ValidEnd.Before(seal.Info.ValidStart) {
return nil, fmt.Errorf("seal is not valid at signature time")
}
maker, err := parseSignatureCertificate(seal.Cert)
if err != nil {
return nil, err
}
if err := signatureWriteTrust(maker, seal.Info.CreateTime, options); err != nil {
return nil, fmt.Errorf("seal maker certificate: %w", err)
}
pub, ok := maker.PublicKey.(*ecdsa.PublicKey)
if !ok || !sm2.IsSM2PublicKey(pub) || !sm2.VerifyASN1WithSM2(pub, nil, seal.SignData, seal.Signature) {
return nil, fmt.Errorf("invalid seal maker signature")
}
mediaType, media := probeSealMedia(seal.PicData)
if len(media) == 0 || mediaType != normalizeSealType(seal.PicType) {
return nil, fmt.Errorf("invalid seal picture")
}
}
return &signatureWriteKey{cert: cert, method: method, digest: digest}, nil
}
// signatureWriteSealStructure 限定SES v4印章结构,拒绝旧版容器伪装和无效图像尺寸
// 入参: raw 印章DER节点
// 返回: error 错误信息
func signatureWriteSealStructure(raw asn1.RawValue) error {
items, ok := asn1Children(raw.Bytes)
if !ok || len(items) != 4 || items[0].Tag != asn1.TagSequence || items[1].Tag != asn1.TagOctetString || items[2].Tag != asn1.TagOID || items[3].Tag != asn1.TagBitString {
return fmt.Errorf("unsupported SES v4 seal structure")
}
for _, item := range items {
if item.Class != asn1.ClassUniversal {
return fmt.Errorf("invalid SES v4 seal tag")
}
}
info, ok := asn1Children(items[0].Bytes)
if !ok || len(info) < 4 || len(info) > 5 {
return fmt.Errorf("invalid SES v4 seal info")
}
header, ok := asn1Children(info[0].Bytes)
if !ok || len(header) != 3 || header[0].Tag != asn1.TagIA5String || header[2].Tag != asn1.TagIA5String || info[1].Tag != asn1.TagIA5String {
return fmt.Errorf("invalid SES v4 seal header")
}
picture, ok := asn1Children(info[3].Bytes)
if !ok || len(picture) != 4 || picture[0].Tag != asn1.TagIA5String || picture[1].Tag != asn1.TagOctetString {
return fmt.Errorf("invalid SES v4 picture structure")
}
w, err := asn1Integer(picture[2])
if err != nil || w <= 0 {
return fmt.Errorf("invalid SES v4 picture width")
}
h, err := asn1Integer(picture[3])
if err != nil || h <= 0 {
return fmt.Errorf("invalid SES v4 picture height")
}
return nil
}
// signatureWriteAlgorithm 校验证书、公钥匹配和支持的安全算法
// 入参: signer 签署器, cert 证书
// 返回: string 签名算法OID, string 摘要OID, error 错误信息
func signatureWriteAlgorithm(signer crypto.Signer, cert *smx509.Certificate) (string, string, error) {
if signer == nil || reflect.ValueOf(signer).Kind() == reflect.Ptr && reflect.ValueOf(signer).IsNil() {
return "", "", fmt.Errorf("nil signature signer")
}
pub := signer.Public()
match, ok := cert.PublicKey.(interface{ Equal(crypto.PublicKey) bool })
if !ok || !match.Equal(pub) {
return "", "", fmt.Errorf("signature certificate and private key do not match")
}
switch key := pub.(type) {
case *rsa.PublicKey:
if key.N == nil || key.N.BitLen() < 2048 || key.E < 65537 {
return "", "", fmt.Errorf("RSA signature key must be at least 2048 bits with exponent at least 65537")
}
return "1.2.840.113549.1.1.11", "2.16.840.1.101.3.4.2.1", nil
case *ecdsa.PublicKey:
if sm2.IsSM2PublicKey(key) {
return signMethodSM2SM3, signDigestSM3, nil
}
if key.Curve != elliptic.P256() && key.Curve != elliptic.P384() && key.Curve != elliptic.P521() {
return "", "", fmt.Errorf("unsupported ECDSA signature curve")
}
return "1.2.840.10045.4.3.2", "2.16.840.1.101.3.4.2.1", nil
default:
return "", "", fmt.Errorf("unsupported signature public key")
}
}
// signatureWriteTrust 校验证书用途、有效期和调用方显式指定的信任链
// 入参: cert 证书, at 验证时间, options 签署选项
// 返回: error 错误信息
func signatureWriteTrust(cert *smx509.Certificate, at time.Time, options SignatureWriteOptions) error {
if cert.KeyUsage != 0 && cert.KeyUsage&(smx509.KeyUsageDigitalSignature|smx509.KeyUsageContentCommitment) == 0 {
return fmt.Errorf("certificate does not authorize digital signatures")
}
if len(options.TrustRoots) == 0 {
return fmt.Errorf("explicit signature trust roots are required")
}
return verifySignatureCertificateChain(cert, options.Intermediates, options.TrustRoots, at, smx509.ExtKeyUsageAny)
}
// sign 编码数字签名或SES v4签章
// 入参: data 最终Signature.xml原文, property 包内绝对路径, options 签署选项
// 返回: []byte 签名值, error 错误信息
func (key *signatureWriteKey) sign(data []byte, property string, options SignatureWriteOptions) ([]byte, error) {
if len(options.Seal) == 0 {
if key.method != signMethodSM2SM3 {
return key.signMessage(data, options.Signer)
}
digest := sm3.Sum(data)
signature, err := key.signMessage(digest[:], options.Signer)
if err != nil {
return nil, err
}
return signatureWriteSignedData(key.cert, digest[:], signature)
}
digest := sm3.Sum(data)
tbs, err := asn1.Marshal(struct {
Version int
Seal asn1.RawValue
Time time.Time `asn1:"generalized"`
Hash asn1.BitString
Property string `asn1:"ia5"`
}{4, asn1.RawValue{FullBytes: options.Seal}, options.Time, asn1.BitString{Bytes: digest[:], BitLength: 256}, property})
if err != nil {
return nil, err
}
signature, err := key.signMessage(tbs, options.Signer)
if err != nil {
return nil, err
}
return asn1.Marshal(struct {
TBS asn1.RawValue
Certificate []byte
Algorithm asn1.ObjectIdentifier
Signature asn1.BitString
}{asn1.RawValue{FullBytes: tbs}, key.cert.Raw, asn1.ObjectIdentifier{1, 2, 156, 10197, 1, 501}, asn1.BitString{Bytes: signature, BitLength: len(signature) * 8}})
}
// signMessage 通过crypto.Signer调用成熟实现或硬件签署器
// SM2签署器必须接受gmsm的SM2SignerOption,接收原文并计算默认用户标识的ZA
// 入参: data 原文, signer 签署器
// 返回: []byte 签名值, error 错误信息
func (key *signatureWriteKey) signMessage(data []byte, signer crypto.Signer) ([]byte, error) {
if key.method == signMethodSM2SM3 {
return signer.Sign(rand.Reader, data, sm2.NewSM2SignerOption(true, nil))
}
digest := sha256.Sum256(data)
return signer.Sign(rand.Reader, digest[:], crypto.SHA256)
}
// signatureWriteSignedData 编码带签署证书的SM2数字签名消息
// 入参: cert 证书, digest 文档描述摘要, signature SM2签名值
// 返回: []byte SignedData编码, error 错误信息
func signatureWriteSignedData(cert *smx509.Certificate, digest, signature []byte) ([]byte, error) {
type contentInfo struct {
Type asn1.ObjectIdentifier
Content asn1.RawValue
}
type signerInfo struct {
Version int
IssuerAndSerial struct {
Issuer asn1.RawValue
Serial *big.Int
}
Digest pkix.AlgorithmIdentifier
Algorithm pkix.AlgorithmIdentifier
Signature []byte
}
digestAlg := pkix.AlgorithmIdentifier{Algorithm: asn1.ObjectIdentifier{1, 2, 156, 10197, 1, 401}, Parameters: asn1.NullRawValue}
signer := signerInfo{Version: 1, Digest: digestAlg, Algorithm: pkix.AlgorithmIdentifier{Algorithm: asn1.ObjectIdentifier{1, 2, 156, 10197, 1, 301, 1}}, Signature: signature}
signer.IssuerAndSerial.Issuer = asn1.RawValue{FullBytes: cert.RawIssuer}
signer.IssuerAndSerial.Serial = cert.SerialNumber
content, err := asn1.Marshal(digest)
if err != nil {
return nil, err
}
sd, err := asn1.Marshal(struct {
Version int
Digests []pkix.AlgorithmIdentifier `asn1:"set"`
Content contentInfo
Certificates asn1.RawValue
Signers []signerInfo `asn1:"set"`
}{1, []pkix.AlgorithmIdentifier{digestAlg}, contentInfo{asn1.ObjectIdentifier{1, 2, 156, 10197, 6, 1, 4, 2, 1}, asn1.RawValue{Class: 2, Tag: 0, IsCompound: true, Bytes: content}}, asn1.RawValue{Class: 2, Tag: 0, IsCompound: true, Bytes: cert.Raw}, []signerInfo{signer}})
if err != nil {
return nil, err
}
return asn1.Marshal(contentInfo{asn1.ObjectIdentifier{1, 2, 156, 10197, 6, 1, 4, 2, 2}, asn1.RawValue{Class: 2, Tag: 0, IsCompound: true, Bytes: sd}})
}