2023-02-22 17:37:56 +08:00
|
|
|
package sm2
|
|
|
|
|
|
|
|
import (
|
|
|
|
"crypto/ecdsa"
|
|
|
|
"crypto/elliptic"
|
|
|
|
"crypto/rand"
|
2023-12-13 13:43:05 +08:00
|
|
|
"crypto/x509/pkix"
|
2023-02-22 17:37:56 +08:00
|
|
|
"encoding/asn1"
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
|
|
|
|
"github.com/emmansun/gmsm/cipher"
|
|
|
|
"github.com/emmansun/gmsm/sm4"
|
|
|
|
"golang.org/x/crypto/cryptobyte"
|
|
|
|
cryptobyte_asn1 "golang.org/x/crypto/cryptobyte/asn1"
|
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
|
|
|
oidSM4 = asn1.ObjectIdentifier{1, 2, 156, 10197, 1, 104}
|
|
|
|
oidSM4ECB = asn1.ObjectIdentifier{1, 2, 156, 10197, 1, 104, 1}
|
|
|
|
)
|
|
|
|
|
|
|
|
// MarshalEnvelopedPrivateKey, returns sm2 key pair protected data with ASN.1 format:
|
|
|
|
//
|
2023-12-13 13:43:05 +08:00
|
|
|
// SM2EnvelopedKey ::= SEQUENCE {
|
|
|
|
// symAlgID AlgorithmIdentifier,
|
|
|
|
// symEncryptedKey SM2Cipher,
|
|
|
|
// sm2PublicKey SM2PublicKey,
|
|
|
|
// sm2EncryptedPrivateKey BIT STRING,
|
|
|
|
// }
|
2023-02-22 17:37:56 +08:00
|
|
|
//
|
|
|
|
// This implementation follows GB/T 35276-2017, uses SM4 cipher to encrypt sm2 private key.
|
2023-03-27 13:41:45 +08:00
|
|
|
// Please note the standard did NOT clarify if the ECB mode requires padding or not.
|
2024-06-12 17:49:37 +08:00
|
|
|
//
|
|
|
|
// This function can be used in CSRResponse.encryptedPrivateKey, reference GM/T 0092-2020
|
|
|
|
// Specification of certificate request syntax based on SM2 cryptographic algorithm.
|
2023-02-22 17:37:56 +08:00
|
|
|
func MarshalEnvelopedPrivateKey(rand io.Reader, pub *ecdsa.PublicKey, tobeEnveloped *PrivateKey) ([]byte, error) {
|
|
|
|
// encrypt sm2 private key
|
|
|
|
size := (tobeEnveloped.Curve.Params().N.BitLen() + 7) / 8
|
|
|
|
if tobeEnveloped.D.BitLen() > size*8 {
|
|
|
|
return nil, errors.New("sm2: invalid private key")
|
|
|
|
}
|
|
|
|
plaintext := tobeEnveloped.D.FillBytes(make([]byte, size))
|
|
|
|
|
|
|
|
key := make([]byte, sm4.BlockSize)
|
|
|
|
if _, err := io.ReadFull(rand, key); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
block, err := sm4.NewCipher(key)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
mode := cipher.NewECBEncrypter(block)
|
|
|
|
|
|
|
|
encryptedPrivateKey := make([]byte, len(plaintext))
|
|
|
|
mode.CryptBlocks(encryptedPrivateKey, plaintext)
|
|
|
|
|
|
|
|
// encrypt the symmetric key
|
|
|
|
encryptedKey, err := EncryptASN1(rand, pub, key)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2023-12-13 13:43:05 +08:00
|
|
|
symAlgID := pkix.AlgorithmIdentifier{
|
|
|
|
Algorithm: oidSM4ECB,
|
|
|
|
Parameters: asn1.NullRawValue,
|
|
|
|
}
|
|
|
|
symAlgIDBytes, _ := asn1.Marshal(symAlgID)
|
|
|
|
|
2023-02-22 17:37:56 +08:00
|
|
|
// marshal the result
|
|
|
|
var b cryptobyte.Builder
|
|
|
|
b.AddASN1(cryptobyte_asn1.SEQUENCE, func(b *cryptobyte.Builder) {
|
2023-12-13 13:43:05 +08:00
|
|
|
b.AddBytes(symAlgIDBytes)
|
2023-02-22 17:37:56 +08:00
|
|
|
b.AddBytes(encryptedKey)
|
|
|
|
b.AddASN1BitString(elliptic.Marshal(tobeEnveloped.Curve, tobeEnveloped.X, tobeEnveloped.Y))
|
|
|
|
b.AddASN1BitString(encryptedPrivateKey)
|
|
|
|
})
|
|
|
|
return b.Bytes()
|
|
|
|
}
|
|
|
|
|
|
|
|
// ParseEnvelopedPrivateKey, parses and decrypts the enveloped SM2 private key.
|
|
|
|
// This methed just supports SM4 cipher now.
|
|
|
|
func ParseEnvelopedPrivateKey(priv *PrivateKey, enveloped []byte) (*PrivateKey, error) {
|
|
|
|
// unmarshal the asn.1 data
|
|
|
|
var (
|
2023-12-15 15:11:49 +08:00
|
|
|
symAlgId pkix.AlgorithmIdentifier
|
|
|
|
encryptedPrivateKey, pub asn1.BitString
|
|
|
|
inner, symEncryptedKey, symAlgIdBytes cryptobyte.String
|
2023-02-22 17:37:56 +08:00
|
|
|
)
|
|
|
|
input := cryptobyte.String(enveloped)
|
|
|
|
if !input.ReadASN1(&inner, cryptobyte_asn1.SEQUENCE) ||
|
|
|
|
!input.Empty() ||
|
2023-12-13 13:43:05 +08:00
|
|
|
!inner.ReadASN1Element(&symAlgIdBytes, cryptobyte_asn1.SEQUENCE) ||
|
2023-02-22 17:37:56 +08:00
|
|
|
!inner.ReadASN1Element(&symEncryptedKey, cryptobyte_asn1.SEQUENCE) ||
|
|
|
|
!inner.ReadASN1BitString(&pub) ||
|
|
|
|
!inner.ReadASN1BitString(&encryptedPrivateKey) ||
|
|
|
|
!inner.Empty() {
|
|
|
|
return nil, errors.New("sm2: invalid asn1 format enveloped key")
|
|
|
|
}
|
|
|
|
|
2023-12-13 13:43:05 +08:00
|
|
|
if _, err := asn1.Unmarshal(symAlgIdBytes, &symAlgId); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if !(symAlgId.Algorithm.Equal(oidSM4) || symAlgId.Algorithm.Equal(oidSM4ECB)) {
|
|
|
|
return nil, fmt.Errorf("sm2: unsupported symmetric cipher <%v>", symAlgId.Algorithm)
|
2023-02-22 17:37:56 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// parse public key
|
2023-12-15 15:11:49 +08:00
|
|
|
pubKey, err := NewPublicKey(pub.RightAlign())
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2023-02-22 17:37:56 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// decrypt symmetric cipher key
|
|
|
|
key, err := priv.Decrypt(rand.Reader, symEncryptedKey, nil)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// decrypt sm2 private key
|
|
|
|
block, err := sm4.NewCipher(key)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
mode := cipher.NewECBDecrypter(block)
|
|
|
|
bytes := encryptedPrivateKey.RightAlign()
|
|
|
|
plaintext := make([]byte, len(bytes))
|
|
|
|
mode.CryptBlocks(plaintext, bytes)
|
2023-03-27 13:41:45 +08:00
|
|
|
// Do we need to check length in order to be compatible with some implementations with padding?
|
2023-12-15 15:11:49 +08:00
|
|
|
sm2Key, err := NewPrivateKey(plaintext)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if !sm2Key.PublicKey.Equal(pubKey) {
|
2023-02-22 17:37:56 +08:00
|
|
|
return nil, errors.New("sm2: mismatch key pair in enveloped data")
|
|
|
|
}
|
|
|
|
|
|
|
|
return sm2Key, nil
|
|
|
|
}
|