From d199697b2063e31e603c4e38c227b314931b971b Mon Sep 17 00:00:00 2001 From: Sun Yimin Date: Thu, 19 Dec 2024 15:42:19 +0800 Subject: [PATCH] pkcs7: remove deprecated method SignWithSM2 --- docs/sm2.md | 18 ++++++++ pkcs7/sign.go | 107 ++++++++++++++++++++++++--------------------- pkcs7/sign_test.go | 1 + sm2/sm2_dsa.go | 16 +++++-- 4 files changed, 89 insertions(+), 53 deletions(-) diff --git a/docs/sm2.md b/docs/sm2.md index e77ccf8..ad70c26 100644 --- a/docs/sm2.md +++ b/docs/sm2.md @@ -306,6 +306,24 @@ func calculateSM2Hash(pub *ecdsa.PublicKey, data, uid []byte) ([]byte, error) { ``` 公钥加密就没啥特殊,只要确保输出密文的编码格式和KMS一致即可。 +## 基于密码硬件,定制SM2私钥 +密码硬件(SDF/SKF)中的私钥通常是无法导出的,但通常提供了签名、解密APIs供调用,为了和本软件库集成,通常需要自定义实现以下接口: + +1. `crypto.Signer`,这个接口的实现通常把传入的数据作为哈希值。 +2. `crypto.Decrypter`,这个接口用于解密操作。 + +通常需要实现四个方法: +1. `Public() crypto.PublicKey` +2. `Sign(rand io.Reader, digest []byte, opts SignerOpts) (signature []byte, err error)` +3. `Decrypt(rand io.Reader, msg []byte, opts DecrypterOpts) (plaintext []byte, err error)` + +第一个返回公钥的方法是必须要实现的,后面的方法取决于这个KEY的用途。 + +**注意**: +`Sign(rand io.Reader, digest []byte, opts SignerOpts) (signature []byte, err error)`方法通常用于对哈希值作签名,最好遵从以下实现逻辑:检查`opts`是否是`*sm2.SM2SignerOption`类型,如果是,则把传入的`digest`作为原始数据进行处理,具体实现可以参考`sm2.SignASN1`函数。当然,目前不管三七二十一,简单当作原始数据大多数情况下都没有问题。实现者可以根据实际应用情况酌情处理。 + +如果密码硬件有自己的随机数源,可以忽略传入的`rand`。 + ## SM2扩展应用 SM2的一些扩展应用,譬如从签名中恢复公钥、半同态加密、环签名等,大多尚处于POC状态,也无相关标准。其它扩展应用(但凡椭圆曲线公钥密码算法能用到的场合),包括但不限于: * [确定性签名](https://datatracker.ietf.org/doc/html/rfc6979) diff --git a/pkcs7/sign.go b/pkcs7/sign.go index a1d3b9d..6d0a8aa 100644 --- a/pkcs7/sign.go +++ b/pkcs7/sign.go @@ -19,12 +19,12 @@ import ( // SignedData is an opaque data structure for creating signed data payloads type SignedData struct { - sd signedData - certs []*smx509.Certificate - data, messageDigest []byte - contentTypeOid asn1.ObjectIdentifier - digestOid asn1.ObjectIdentifier - encryptionOid asn1.ObjectIdentifier + sd signedData + certs []*smx509.Certificate + data []byte + contentTypeOid asn1.ObjectIdentifier + digestOid asn1.ObjectIdentifier + encryptionOid asn1.ObjectIdentifier } // NewSignedData takes data and initializes a PKCS7 SignedData struct that is @@ -167,30 +167,11 @@ func (sd *SignedData) AddSignerChain(ee *smx509.Certificate, pkey crypto.Private sd.sd.DigestAlgorithmIdentifiers = append(sd.sd.DigestAlgorithmIdentifiers, pkix.AlgorithmIdentifier{Algorithm: sd.digestOid, Parameters: asn1.NullRawValue}, ) - hasher, err := getHashForOID(sd.digestOid) - if err != nil { - return err - } - h := newHash(hasher, sd.digestOid) - h.Write(sd.data) - sd.messageDigest = h.Sum(nil) encryptionOid, err := getOIDForEncryptionAlgorithm(pkey, sd.digestOid) if err != nil { return err } - attrs := &attributes{} - attrs.Add(OIDAttributeContentType, sd.sd.ContentInfo.ContentType) - attrs.Add(OIDAttributeMessageDigest, sd.messageDigest) - attrs.Add(OIDAttributeSigningTime, time.Now().UTC()) - for _, attr := range config.ExtraSignedAttributes { - attrs.Add(attr.Type, attr.Value) - } - finalAttrs, err := attrs.ForMarshalling() - if err != nil { - return err - } - // create signature of signed attributes - signature, err := signAttributes(finalAttrs, pkey, hasher) + finalAttrs, signature, err := sd.signWithAttributes(pkey, config) if err != nil { return err } @@ -216,6 +197,34 @@ func (sd *SignedData) AddSignerChain(ee *smx509.Certificate, pkey crypto.Private return nil } +func (sd *SignedData) signWithAttributes(pkey crypto.PrivateKey, config SignerInfoConfig) ([]attribute, []byte, error) { + hasher, err := getHashForOID(sd.digestOid) + if err != nil { + return nil, nil, err + } + h := newHash(hasher, sd.digestOid) + h.Write(sd.data) + messageDigest := h.Sum(nil) + + attrs := &attributes{} + attrs.Add(OIDAttributeContentType, sd.sd.ContentInfo.ContentType) + attrs.Add(OIDAttributeMessageDigest, messageDigest) + attrs.Add(OIDAttributeSigningTime, time.Now().UTC()) + for _, attr := range config.ExtraSignedAttributes { + attrs.Add(attr.Type, attr.Value) + } + finalAttrs, err := attrs.ForMarshalling() + if err != nil { + return nil, nil, err + } + // create signature of signed attributes + signature, err := signAttributes(finalAttrs, pkey, hasher) + if err != nil { + return nil, nil, err + } + return finalAttrs, signature, nil +} + func newHash(hasher crypto.Hash, hashOid asn1.ObjectIdentifier) hash.Hash { var h hash.Hash if hashOid.Equal(OIDDigestAlgorithmSM3) || hashOid.Equal(OIDDigestAlgorithmSM2SM3) { @@ -240,20 +249,7 @@ func (sd *SignedData) SignWithoutAttr(ee *smx509.Certificate, pkey crypto.Privat if err != nil { return err } - key, ok := pkey.(crypto.Signer) - if !ok { - return errors.New("pkcs7: private key does not implement crypto.Signer") - } - _, isSM2 := pkey.(sm2.Signer) - if isSM2 { - signature, err = key.Sign(rand.Reader, sd.data, sm2.DefaultSM2SignerOpts) - } else { - h := newHash(hasher, sd.digestOid) - h.Write(sd.data) - sd.messageDigest = h.Sum(nil) - signature, err = key.Sign(rand.Reader, sd.messageDigest, hasher) - } - if err != nil { + if signature, err = signData(sd.data, pkey, hasher); err != nil { return err } var ias issuerAndSerial @@ -380,20 +376,33 @@ func signAttributes(attrs []attribute, pkey crypto.PrivateKey, hasher crypto.Has if err != nil { return nil, err } + return signData(attrBytes, pkey, hasher) +} - if key, ok := pkey.(sm2.Signer); ok { - return key.SignWithSM2(rand.Reader, nil, attrBytes) - } - - h := hasher.New() - h.Write(attrBytes) - hash := h.Sum(nil) - +// signData signs the provided data using the given private key and hash function. +// It returns the signed data or an error if the signing process fails. +func signData(data []byte, pkey crypto.PrivateKey, hasher crypto.Hash) ([]byte, error) { key, ok := pkey.(crypto.Signer) if !ok { return nil, errors.New("pkcs7: private key does not implement crypto.Signer") } - return key.Sign(rand.Reader, hash, hasher) + hash := data + var opts crypto.SignerOpts = hasher + + if !hasher.Available() { + // if you pass a private key with type *ecdsa.PrivateKey and the curve is SM2, + // you will get unexpected signature. + if sm2.IsSM2PublicKey(key.Public()) { + opts = sm2.DefaultSM2SignerOpts + } else { + return nil, fmt.Errorf("pkcs7: unsupported hash function %s", hasher) + } + } else { + h := hasher.New() + h.Write(data) + hash = h.Sum(nil) + } + return key.Sign(rand.Reader, hash, opts) } // concats and wraps the certificates in the RawValue structure diff --git a/pkcs7/sign_test.go b/pkcs7/sign_test.go index 4f37079..1ec8589 100644 --- a/pkcs7/sign_test.go +++ b/pkcs7/sign_test.go @@ -87,6 +87,7 @@ func testSign(t *testing.T, isSM bool, content []byte, sigalgs []x509.SignatureA } } } + func TestSign(t *testing.T) { content := []byte("Hello World") sigalgs := []x509.SignatureAlgorithm{ diff --git a/sm2/sm2_dsa.go b/sm2/sm2_dsa.go index 7db8168..6e473a4 100644 --- a/sm2/sm2_dsa.go +++ b/sm2/sm2_dsa.go @@ -25,8 +25,17 @@ import ( // should be performed. var directSigning crypto.Hash = 0 -// Signer SM2 special signer +// Signer is an interface for an opaque private key that can be used for +// signing operations. For example, an SM2 key kept in a hardware module. +// Deprecated: please use crypto.Signer directly. type Signer interface { + // Public returns the public key corresponding to the opaque, + // private key. + Public() crypto.PublicKey + + // SignWithSM2 signs raw message with the private key, possibly using entropy from + // rand, and the user ID (UID). If the UID is not provided, a default UID (1234567812345678) is used. + // The signature is generated using the SM2 algorithm. SignWithSM2(rand io.Reader, uid, msg []byte) ([]byte, error) } @@ -251,8 +260,8 @@ func bigIntToBytes(curve elliptic.Curve, value *big.Int) []byte { } // CalculateSM2Hash calculates the SM2 hash for the given public key, data, and user ID (UID). -// If the UID is not provided, a default UID (1234567812345678) is used. -// The public key must be valid, otherwise will be panic. +// If the UID is not provided, a default UID (1234567812345678) is used. +// The public key must be valid, otherwise will be panic. // This function is used to calculate the hash value for SM2 signature. func CalculateSM2Hash(pub *ecdsa.PublicKey, data, uid []byte) ([]byte, error) { if len(uid) == 0 { @@ -650,7 +659,6 @@ func randomPoint(c *sm2Curve, rand io.Reader, checkOrderMinus1 bool) (k *bigmod. // randomPoint rejects a candidate for being higher than the modulus. var testingOnlyRejectionSamplingLooped func() - // RecoverPublicKeysFromSM2Signature attempts to recover the public keys from an SM2 signature. // This function takes a hash and a signature as input and returns a slice of possible public keys // that could have generated the given signature.