gmsm/sm2/sm2_envelopedkey.go
2024-12-19 08:17:21 +08:00

160 lines
5.2 KiB
Go

package sm2
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/x509/pkix"
"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:
//
// SM2EnvelopedKey ::= SEQUENCE {
// symAlgID AlgorithmIdentifier,
// symEncryptedKey SM2Cipher,
// sm2PublicKey SM2PublicKey,
// sm2EncryptedPrivateKey BIT STRING,
// }
//
// This implementation follows GB/T 35276-2017, uses SM4 cipher to encrypt sm2 private key.
// Please note the standard did NOT clarify if the ECB mode requires padding or not.
//
// This function can be used in CSRResponse.encryptedPrivateKey, reference GM/T 0092-2020
// Specification of certificate request syntax based on SM2 cryptographic algorithm.
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
}
symAlgID := pkix.AlgorithmIdentifier{
Algorithm: oidSM4ECB,
Parameters: asn1.NullRawValue,
}
symAlgIDBytes, _ := asn1.Marshal(symAlgID)
// marshal the result
var b cryptobyte.Builder
b.AddASN1(cryptobyte_asn1.SEQUENCE, func(b *cryptobyte.Builder) {
b.AddBytes(symAlgIDBytes)
b.AddBytes(encryptedKey)
b.AddASN1BitString(elliptic.Marshal(tobeEnveloped.Curve, tobeEnveloped.X, tobeEnveloped.Y))
b.AddASN1BitString(encryptedPrivateKey)
})
return b.Bytes()
}
// ParseEnvelopedPrivateKey parses an enveloped private key using the provided private key.
// The enveloped key is expected to be in ASN.1 format and encrypted with a symmetric cipher.
//
// Parameters:
// - priv: The private key used to decrypt the symmetric key.
// - enveloped: The ASN.1 encoded and encrypted enveloped private key.
//
// Returns:
// - A pointer to the decrypted PrivateKey.
// - An error if the parsing or decryption fails.
//
// The function performs the following steps:
// 1. Unmarshals the ASN.1 data to extract the symmetric algorithm identifier, encrypted symmetric key, public key, and encrypted private key.
// 2. Verifies that the symmetric algorithm is supported (SM4 or SM4ECB).
// 3. Parses the public key from the ASN.1 data.
// 4. Decrypts the symmetric key using the provided private key.
// 5. Decrypts the SM2 private key using the decrypted symmetric key.
// 6. Verifies that the decrypted private key matches the public key.
//
// Errors are returned if any of the steps fail, including invalid ASN.1 format, unsupported symmetric cipher, decryption failures, or key mismatches.
func ParseEnvelopedPrivateKey(priv *PrivateKey, enveloped []byte) (*PrivateKey, error) {
// unmarshal the asn.1 data
var (
symAlgId pkix.AlgorithmIdentifier
encryptedPrivateKey, pub asn1.BitString
inner, symEncryptedKey, symAlgIdBytes cryptobyte.String
)
input := cryptobyte.String(enveloped)
if !input.ReadASN1(&inner, cryptobyte_asn1.SEQUENCE) ||
!input.Empty() ||
!inner.ReadASN1Element(&symAlgIdBytes, cryptobyte_asn1.SEQUENCE) ||
!inner.ReadASN1Element(&symEncryptedKey, cryptobyte_asn1.SEQUENCE) ||
!inner.ReadASN1BitString(&pub) ||
!inner.ReadASN1BitString(&encryptedPrivateKey) ||
!inner.Empty() {
return nil, errors.New("sm2: invalid asn1 format enveloped key")
}
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)
}
// parse public key
pubKey, err := NewPublicKey(pub.RightAlign())
if err != nil {
return nil, err
}
// 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)
// Do we need to check length in order to be compatible with some implementations with padding?
sm2Key, err := NewPrivateKey(plaintext)
if err != nil {
return nil, err
}
if !sm2Key.PublicKey.Equal(pubKey) {
return nil, errors.New("sm2: mismatch key pair in enveloped data")
}
return sm2Key, nil
}