mirror of
https://github.com/xiaoqidun/ofdgo.git
synced 2026-10-29 16:44:23 +08:00
336 lines
13 KiB
Go
336 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"
|
|
baseimage "github.com/xiaoqidun/ofdgo/internal/base/image"
|
|
)
|
|
|
|
// signatureWriteKey 已验证签署身份
|
|
type signatureWriteKey struct {
|
|
cert *smx509.Certificate
|
|
method string
|
|
digest string
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
// 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 != baseimage.NormalizeFormat(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}})
|
|
}
|