-
Notifications
You must be signed in to change notification settings - Fork 60
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
4 changed files
with
304 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,145 @@ | ||
// Marshal & Parse CSRResponse which is defined in GM/T 0092-2020 | ||
// Specification of certificate request syntax based on SM2 cryptographic algorithm. | ||
|
||
package smx509 | ||
|
||
import ( | ||
"bytes" | ||
"crypto/ecdsa" | ||
"crypto/rand" | ||
"encoding/asn1" | ||
"errors" | ||
|
||
"github.com/emmansun/gmsm/sm2" | ||
) | ||
|
||
// CSRResponse represents the response of a certificate signing request. | ||
type CSRResponse struct { | ||
SignCerts []*Certificate | ||
EncryptPrivateKey *sm2.PrivateKey | ||
EncryptCerts []*Certificate | ||
} | ||
|
||
type tbsCSRResponse struct { | ||
SignCerts rawCertificates | ||
EncryptedPrivateKey asn1.RawValue `asn1:"optional,tag:0"` | ||
EncryptCerts rawCertificates `asn1:"optional,tag:1"` | ||
} | ||
|
||
type rawCertificates struct { | ||
Raw asn1.RawContent | ||
} | ||
|
||
// ParseCSRResponse parses a CSRResponse from DER format. | ||
func ParseCSRResponse(signPrivateKey *sm2.PrivateKey, der []byte) (CSRResponse, error) { | ||
result := CSRResponse{} | ||
resp := &tbsCSRResponse{} | ||
rest, err := asn1.Unmarshal(der, resp) | ||
if err != nil || len(rest) > 0 { | ||
return result, errors.New("smx509: invalid CSRResponse asn1 data") | ||
} | ||
signCerts, err := resp.SignCerts.Parse() | ||
if err != nil || len(signCerts) == 0 { | ||
return result, errors.New("smx509: invalid sign certificates") | ||
} | ||
|
||
// further check sign public key against the private key | ||
if !signPrivateKey.PublicKey.Equal(signCerts[0].PublicKey) { | ||
return result, errors.New("smx509: sign public key mismatch") | ||
} | ||
|
||
var encPrivateKey *sm2.PrivateKey | ||
if len(resp.EncryptedPrivateKey.Bytes) > 0 { | ||
encPrivateKey, err = sm2.ParseEnvelopedPrivateKey(signPrivateKey, resp.EncryptedPrivateKey.Bytes) | ||
if err != nil { | ||
return result, err | ||
} | ||
} | ||
var encryptCerts []*Certificate | ||
if len(resp.EncryptCerts.Raw) > 0 { | ||
encryptCerts, err = resp.EncryptCerts.Parse() | ||
if err != nil { | ||
return result, err | ||
} | ||
} | ||
|
||
// further check the public key of the encrypt certificate | ||
if encPrivateKey != nil && len(encryptCerts) == 0 { | ||
return result, errors.New("smx509: missing encrypt certificate") | ||
} | ||
|
||
if encPrivateKey != nil && !encPrivateKey.PublicKey.Equal(encryptCerts[0].PublicKey) { | ||
return result, errors.New("smx509: encrypt key pair mismatch") | ||
} | ||
|
||
result.SignCerts = signCerts | ||
result.EncryptPrivateKey = encPrivateKey | ||
result.EncryptCerts = encryptCerts | ||
return result, nil | ||
} | ||
|
||
// MarshalCSRResponse marshals a CSRResponse to DER format. | ||
func MarshalCSRResponse(signCerts []*Certificate, encryptPrivateKey *sm2.PrivateKey, encryptCerts []*Certificate) ([]byte, error) { | ||
if len(signCerts) == 0 { | ||
return nil, errors.New("smx509: no sign certificate") | ||
} | ||
signPubKey, ok := signCerts[0].PublicKey.(*ecdsa.PublicKey) | ||
if !ok || !sm2.IsSM2PublicKey(signPubKey) { | ||
return nil, errors.New("smx509: invalid sign public key") | ||
} | ||
|
||
// further check the public key of the encrypt certificate | ||
if encryptPrivateKey != nil && len(encryptCerts) == 0 { | ||
return nil, errors.New("smx509: missing encrypt certificate") | ||
} | ||
if encryptPrivateKey != nil && !encryptPrivateKey.PublicKey.Equal(encryptCerts[0].PublicKey) { | ||
return nil, errors.New("smx509: encrypt key pair mismatch") | ||
} | ||
|
||
resp := tbsCSRResponse{} | ||
resp.SignCerts = marshalCertificates(signCerts) | ||
if encryptPrivateKey != nil && len(encryptCerts) > 0 { | ||
privateKeyBytes, err := sm2.MarshalEnvelopedPrivateKey(rand.Reader, signPubKey, encryptPrivateKey) | ||
if err != nil { | ||
return nil, err | ||
} | ||
resp.EncryptedPrivateKey = asn1.RawValue{Class: 2, Tag: 0, IsCompound: true, Bytes: privateKeyBytes} | ||
resp.EncryptCerts = marshalCertificates(encryptCerts) | ||
} | ||
return asn1.Marshal(resp) | ||
} | ||
|
||
// concats and wraps the certificates in the RawValue structure | ||
func marshalCertificates(certs []*Certificate) rawCertificates { | ||
var buf bytes.Buffer | ||
for _, cert := range certs { | ||
buf.Write(cert.Raw) | ||
} | ||
rawCerts, _ := marshalCertificateBytes(buf.Bytes()) | ||
return rawCerts | ||
} | ||
|
||
// Even though, the tag & length are stripped out during marshalling the | ||
// RawContent, we have to encode it into the RawContent. If its missing, | ||
// then `asn1.Marshal()` will strip out the certificate wrapper instead. | ||
func marshalCertificateBytes(certs []byte) (rawCertificates, error) { | ||
var val = asn1.RawValue{Bytes: certs, Class: 2, Tag: 0, IsCompound: true} | ||
b, err := asn1.Marshal(val) | ||
if err != nil { | ||
return rawCertificates{}, err | ||
} | ||
return rawCertificates{Raw: b}, nil | ||
} | ||
|
||
func (raw rawCertificates) Parse() ([]*Certificate, error) { | ||
if len(raw.Raw) == 0 { | ||
return nil, nil | ||
} | ||
|
||
var val asn1.RawValue | ||
if _, err := asn1.Unmarshal(raw.Raw, &val); err != nil { | ||
return nil, err | ||
} | ||
|
||
return ParseCertificates(val.Bytes) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,155 @@ | ||
package smx509_test | ||
|
||
import ( | ||
"crypto" | ||
"crypto/rand" | ||
"crypto/x509" | ||
"crypto/x509/pkix" | ||
"encoding/pem" | ||
"fmt" | ||
"math/big" | ||
"os" | ||
"testing" | ||
"time" | ||
|
||
"github.com/emmansun/gmsm/sm2" | ||
"github.com/emmansun/gmsm/smx509" | ||
) | ||
|
||
type certKeyPair struct { | ||
Certificate *smx509.Certificate | ||
PrivateKey *crypto.PrivateKey | ||
} | ||
|
||
func createTestCertificate() ([]*certKeyPair, error) { | ||
signer, err := createTestCertificateByIssuer("Test CA", nil, true) | ||
if err != nil { | ||
return nil, err | ||
} | ||
pair1, err := createTestCertificateByIssuer("Test Org Sign", signer, false) | ||
if err != nil { | ||
return nil, err | ||
} | ||
pair2, err := createTestCertificateByIssuer("Test Org Enc", signer, false) | ||
if err != nil { | ||
return nil, err | ||
} | ||
return []*certKeyPair{pair1, pair2, signer}, nil | ||
} | ||
|
||
func createTestCertificateByIssuer(name string, issuer *certKeyPair, isCA bool) (*certKeyPair, error) { | ||
var ( | ||
err error | ||
priv crypto.PrivateKey | ||
derCert []byte | ||
issuerCert *smx509.Certificate | ||
issuerKey crypto.PrivateKey | ||
) | ||
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 32) | ||
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
template := x509.Certificate{ | ||
SerialNumber: serialNumber, | ||
Subject: pkix.Name{ | ||
CommonName: name, | ||
Organization: []string{"Acme Co"}, | ||
}, | ||
NotBefore: time.Now().Add(-1 * time.Second), | ||
NotAfter: time.Now().AddDate(1, 0, 0), | ||
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, | ||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageEmailProtection}, | ||
} | ||
if issuer != nil { | ||
issuerCert = issuer.Certificate | ||
issuerKey = *issuer.PrivateKey | ||
} | ||
|
||
priv, err = sm2.GenerateKey(rand.Reader) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
pkey := priv.(crypto.Signer) | ||
if isCA { | ||
template.IsCA = true | ||
template.KeyUsage |= x509.KeyUsageCertSign | ||
template.BasicConstraintsValid = true | ||
} | ||
if issuer == nil { | ||
// no issuer given,make this a self-signed root cert | ||
issuerCert = (*smx509.Certificate)(&template) | ||
issuerKey = priv | ||
} | ||
|
||
derCert, err = smx509.CreateCertificate(rand.Reader, &template, (*x509.Certificate)(issuerCert), pkey.Public(), issuerKey) | ||
if err != nil { | ||
return nil, err | ||
} | ||
if len(derCert) == 0 { | ||
return nil, fmt.Errorf("no certificate created, probably due to wrong keys. types were %T and %T", priv, issuerKey) | ||
} | ||
cert, err := smx509.ParseCertificate(derCert) | ||
if err != nil { | ||
return nil, err | ||
} | ||
pem.Encode(os.Stdout, &pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw}) | ||
return &certKeyPair{ | ||
Certificate: cert, | ||
PrivateKey: &priv, | ||
}, nil | ||
} | ||
|
||
func TestMarshalCSRResponse(t *testing.T) { | ||
pairs, err := createTestCertificate() | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
signPrivKey, _ := (*pairs[0].PrivateKey).(*sm2.PrivateKey) | ||
encPrivKey, _ := (*pairs[1].PrivateKey).(*sm2.PrivateKey) | ||
|
||
// Call the function | ||
result, err := smx509.MarshalCSRResponse([]*smx509.Certificate{pairs[0].Certificate, pairs[2].Certificate}, encPrivKey, []*smx509.Certificate{pairs[1].Certificate, pairs[2].Certificate}) | ||
|
||
// Check the result | ||
if err != nil { | ||
t.Errorf("Unexpected error: %v", err) | ||
} | ||
|
||
resp, err := smx509.ParseCSRResponse(signPrivKey, result) | ||
if err != nil { | ||
t.Errorf("Unexpected error: %v", err) | ||
} | ||
if len(resp.SignCerts) != 2 { | ||
t.Errorf("Unexpected number of sign certs: %d", len(resp.SignCerts)) | ||
} | ||
if resp.EncryptPrivateKey == nil || !encPrivKey.Equal(resp.EncryptPrivateKey) { | ||
t.Errorf("Unexpected encrypt private key") | ||
} | ||
if len(resp.EncryptCerts) != 2 { | ||
t.Errorf("Unexpected number of encrypt certs: %d", len(resp.EncryptCerts)) | ||
} | ||
|
||
// Marshal sign certificate only | ||
result, err = smx509.MarshalCSRResponse([]*smx509.Certificate{pairs[0].Certificate, pairs[2].Certificate}, nil, nil) | ||
// Check the result | ||
if err != nil { | ||
t.Errorf("Unexpected error: %v", err) | ||
} | ||
resp, err = smx509.ParseCSRResponse(signPrivKey, result) | ||
if err != nil { | ||
t.Errorf("Unexpected error: %v", err) | ||
} | ||
if len(resp.SignCerts) != 2 { | ||
t.Errorf("Unexpected number of sign certs: %d", len(resp.SignCerts)) | ||
} | ||
if resp.EncryptPrivateKey != nil { | ||
t.Errorf("Unexpected encrypt private key") | ||
} | ||
if resp.EncryptCerts != nil { | ||
t.Errorf("Unexpected encrypt certs") | ||
} | ||
} |