mirror of
https://github.com/xiaoqidun/ofdgo.git
synced 2026-10-30 01:27:26 +08:00
This commit is contained in:
1 parent
d6f70de7be
commit
3927929822
30 files changed
+4707
-1161
No files matched your search
@@ -0,0 +1,334 @@
|
||||
// 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}})
|
||||
}
|
||||
Reference in new issue
Block a user