From 038816c132e661356133c8b55f08100893aac056 Mon Sep 17 00:00:00 2001 From: Laisky Date: Tue, 19 Dec 2023 03:36:06 +0000 Subject: [PATCH] feat: Refactor SMTongsuo generation to support certificate validity - Add ability to convert an X509 certificate to an openssl configuration file. - Refactor certificate generation in SMTongsuo to use time package for expiration. - Add ability to set certificate policies in SMTongsuo. - Update SMTongsuo tests to use testify/require instead of testify/assert. --- crypto/converter.go | 5 ++- crypto/converter_test.go | 2 -- crypto/smtongsuo.go | 36 ++----------------- crypto/smtongsuo_test.go | 78 ++++++++++++++++++++++++++++++---------- 4 files changed, 66 insertions(+), 55 deletions(-) diff --git a/crypto/converter.go b/crypto/converter.go index a101a03..3211194 100644 --- a/crypto/converter.go +++ b/crypto/converter.go @@ -389,6 +389,7 @@ func VerifyCertByPrikey(certPem []byte, prikeyPem []byte) error { return err } +// X509Cert2OpensslConf marshal x509 func X509Cert2OpensslConf(cert *x509.Certificate) (opensslConf []byte) { // set req & req_distinguished_name cnt := fmt.Sprintf(`[ req ] @@ -416,7 +417,9 @@ commonName = %s "organizationName", "organizationalUnitName", } { - cnt += fmt.Sprintf("%s = %s\n", name, strings.Join(subjectMaps[name], ",")) + if len(subjectMaps[name]) != 0 { + cnt += fmt.Sprintf("%s = %s\n", name, strings.Join(subjectMaps[name], ",")) + } } cnt += "\n" diff --git a/crypto/converter_test.go b/crypto/converter_test.go index e741f60..14ae3b7 100644 --- a/crypto/converter_test.go +++ b/crypto/converter_test.go @@ -524,7 +524,6 @@ func TestX509Cert2OpensslConf(t *testing.T) { cert := &x509.Certificate{ Subject: pkix.Name{ CommonName: "example.com", - Country: []string{"US"}, Province: []string{"California"}, Locality: []string{"San Francisco"}, Organization: []string{"Acme Corp"}, @@ -542,7 +541,6 @@ x509_extensions = v3_ca [ req_distinguished_name ] commonName = example.com -countryName = US stateOrProvinceName = California localityName = San Francisco organizationName = Acme Corp diff --git a/crypto/smtongsuo.go b/crypto/smtongsuo.go index a83dcdd..8e2b5de 100644 --- a/crypto/smtongsuo.go +++ b/crypto/smtongsuo.go @@ -13,39 +13,6 @@ import ( "github.com/Laisky/errors/v2" ) -const ( - configTemplate = `[ req ] -distinguished_name = req_distinguished_name -prompt = no -string_mask = utf8only -x509_extensions = v3_ca - -[ req_distinguished_name ] -countryName = {{.CountryName}} -stateOrProvinceName = {{.StateOrProvinceName}} -localityName = {{.LocalityName}} -organizationName = {{.OrganizationName}} -organizationalUnitName = {{.OrganizationalUnitName}} -commonName = {{.CommonName}} - -[ v3_ca ] -basicConstraints = critical, CA:TRUE -keyUsage = cRLSign, keyCertSign -subjectKeyIdentifier = hash -authorityKeyIdentifier = keyid:always, issuer -certificatePolicies = {{.Policies}}` -) - -type configTemplateArgs struct { - CountryName string - StateOrProvinceName string - LocalityName string - OrganizationName string - OrganizationalUnitName string - CommonName string - Policies string -} - // Tongsuo is a wrapper of tongsuo executable binary // // https://github.com/Tongsuo-Project/Tongsuo @@ -118,7 +85,7 @@ func (t *Tongsuo) NewPrikeyAndCert(ctx context.Context, opts ...X509CertOption) return nil, nil, errors.Wrap(err, "new private key") } - _, tpl, err := X509CertOption2Template(opts...) + opt, tpl, err := X509CertOption2Template(opts...) if err != nil { return nil, nil, errors.Wrap(err, "X509CertOption2Template") } @@ -140,6 +107,7 @@ func (t *Tongsuo) NewPrikeyAndCert(ctx context.Context, opts ...X509CertOption) certPem, err := t.runCMD(ctx, []string{ "req", "-outform", "PEM", "-key", "/dev/stdin", "-set_serial", strconv.Itoa(int(t.serialGenerator.SerialNum())), + "-days", strconv.Itoa(int(time.Until(opt.notAfter) / time.Hour / 24)), "-x509", "-new", "-nodes", "-utf8", "-batch", "-sm3", "-sigopt", "sm2-za:no", "-copy_extensions", "copyall", diff --git a/crypto/smtongsuo_test.go b/crypto/smtongsuo_test.go index 72d9b43..c44c3e7 100644 --- a/crypto/smtongsuo_test.go +++ b/crypto/smtongsuo_test.go @@ -2,9 +2,10 @@ package crypto import ( "context" - "crypto/x509" + "encoding/asn1" "os/exec" "testing" + "time" "github.com/stretchr/testify/require" ) @@ -20,29 +21,70 @@ func testSkipSmTongsuo(t *testing.T) (skipped bool) { } func TestTongsuo_NewPrikeyAndCert(t *testing.T) { + t.Parallel() + + ctx := context.Background() + if testSkipSmTongsuo(t) { return } - t.Parallel() + ins, err := NewTongsuo("/usr/local/bin/tongsuo") + require.NoError(t, err) - tongsuo := &Tongsuo{} // Create an instance of Tongsuo + t.Run("ca", func(t *testing.T) { + t.Parallel() + opts := []X509CertOption{ + WithX509CertIsCA(), + WithX509CertCommonName("test-common-name"), + WithX509CertOrganization("test org"), + WithX509CertPolicies(asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 59936, 1, 1, 3}), + } - // Define the X509CertOption options for the test - opts := []X509CertOption{ - WithX509CertCommonName("test"), - WithX509CertOrganization("test org"), - } + prikeyPem, certDer, err := ins.NewPrikeyAndCert(context.Background(), opts...) + require.NoError(t, err) + require.NotNil(t, prikeyPem) + require.NotNil(t, certDer) - prikeyPem, certDer, err := tongsuo.NewPrikeyAndCert(context.Background(), opts...) - require.NoError(t, err) - require.NotNil(t, prikeyPem) - require.NotNil(t, certDer) + // Verify that the generated certificate is valid + certinfo, err := ins.ShowCertInfo(ctx, certDer) + // t.Log(string(certinfo)) + require.NoError(t, err) + require.Contains(t, string(certinfo), "test-common-name") + require.Contains(t, string(certinfo), "test org") + require.Contains(t, string(certinfo), "CA:TRUE") + require.Contains(t, string(certinfo), "1.3.6.1.4.1.59936.1.1.3") + }) + + t.Run("not ca", func(t *testing.T) { + t.Parallel() + notafter := time.Now().Add(time.Hour * 24 * 365 * 10) + + opts := []X509CertOption{ + WithX509CertCommonName("test-common-name"), + WithX509CertOrganization("test org"), + WithX509CertNotAfter(notafter), + WithX509CertPolicies( + asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 59936, 1, 1, 3}, + asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 59936, 1, 1, 4}, + ), + } + + prikeyPem, certDer, err := ins.NewPrikeyAndCert(context.Background(), opts...) + require.NoError(t, err) + require.NotNil(t, prikeyPem) + require.NotNil(t, certDer) + + // Verify that the generated certificate is valid + certinfo, err := ins.ShowCertInfo(ctx, certDer) + // t.Log(string(certinfo)) + require.NoError(t, err) + require.Contains(t, string(certinfo), "test-common-name") + require.Contains(t, string(certinfo), "test org") + require.Contains(t, string(certinfo), "CA:FALSE") + require.Contains(t, string(certinfo), "1.3.6.1.4.1.59936.1.1.3") + require.Contains(t, string(certinfo), "1.3.6.1.4.1.59936.1.1.4") + require.Contains(t, string(certinfo), notafter.UTC().Format("2006 GMT")) + }) - // Verify that the generated certificate is valid - cert, err := x509.ParseCertificate(certDer) - require.NoError(t, err) - require.NotNil(t, cert) - require.Equal(t, "test", cert.Subject.CommonName) - require.Equal(t, "test org", cert.Subject.Organization[0]) }