From b8ad19a5a220f9bfcba5af466084c9922399ab72 Mon Sep 17 00:00:00 2001 From: Vasiliy Tolstov Date: Thu, 12 May 2022 15:11:31 +0300 Subject: [PATCH 1/3] WIP: initial mtls package Signed-off-by: Vasiliy Tolstov --- mtls/mtls.go | 247 ++++++++++++++++++++++++++++++++++++++++++++++ mtls/mtls_test.go | 35 +++++++ mtls/options.go | 155 +++++++++++++++++++++++++++++ 3 files changed, 437 insertions(+) create mode 100644 mtls/mtls.go create mode 100644 mtls/mtls_test.go create mode 100644 mtls/options.go diff --git a/mtls/mtls.go b/mtls/mtls.go new file mode 100644 index 00000000..892e6dc7 --- /dev/null +++ b/mtls/mtls.go @@ -0,0 +1,247 @@ +package mtls // import "go.unistack.org/micro/v3/mtls" + +import ( + "bytes" + "crypto" + "crypto/ed25519" + "crypto/rand" + "crypto/tls" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" + "sync" +) + +var bp = newBPool() + +type bpool struct { + pool sync.Pool +} + +func newBPool() *bpool { + var bp bpool + bp.pool.New = alloc + return &bp +} + +func alloc() interface{} { + return &bytes.Buffer{} +} + +func (bp *bpool) Get() *bytes.Buffer { + return bp.pool.Get().(*bytes.Buffer) +} + +func (bp *bpool) Put(buf *bytes.Buffer) { + buf.Reset() + bp.pool.Put(buf) +} + +// NewCA creates new CA keypair +func NewCA(opts ...CertificateOption) ([]byte, crypto.PrivateKey, error) { + options := NewCertificateOptions(opts...) + + crtreq := &x509.CertificateRequest{ + Subject: pkix.Name{ + Organization: options.Organization, + OrganizationalUnit: options.OrganizationalUnit, + CommonName: options.CommonName, + }, + SignatureAlgorithm: options.SignatureAlgorithm, + } + + pemcsr, pemkey, err := newCsr(crtreq) + if err != nil { + return nil, nil, err + } + + pemcrt, err := SignCSR(pemcsr, nil, pemkey, opts...) + if err != nil { + return nil, nil, err + } + + return pemcrt, pemkey, nil +} + +func NewIntermediate(cacrt *x509.Certificate, cakey crypto.PrivateKey, opts ...CertificateOption) ([]byte, crypto.PrivateKey, error) { + options := &CertificateOptions{} + for _, o := range opts { + o(options) + } + + crtreq := &x509.CertificateRequest{ + Subject: pkix.Name{ + Organization: options.Organization, + OrganizationalUnit: options.OrganizationalUnit, + CommonName: options.CommonName, + }, + SignatureAlgorithm: options.SignatureAlgorithm, + } + + pemcsr, pemkey, err := newCsr(crtreq) + if err != nil { + return nil, nil, err + } + + pemcrt, err := SignCSR(pemcsr, cacrt, cakey) + if err != nil { + return nil, nil, err + } + + return pemcrt, pemkey, nil +} + +// SignCSR sign certificate request and return signed pubkey +func SignCSR(rawcsr []byte, cacrt *x509.Certificate, cakey crypto.PrivateKey, opts ...CertificateOption) ([]byte, error) { + if cacrt == nil { + opts = append(opts, CertificateIsCA(false)) + } + + options := NewCertificateOptions(opts...) + + csr, err := x509.ParseCertificateRequest(rawcsr) + if err == nil { + err = csr.CheckSignature() + } + if err != nil { + return nil, err + } + + tpl := &x509.Certificate{ + Signature: csr.Signature, + SignatureAlgorithm: csr.SignatureAlgorithm, + PublicKeyAlgorithm: csr.PublicKeyAlgorithm, + PublicKey: csr.PublicKey, + SerialNumber: options.SerialNumber, + OCSPServer: options.OCSPServer, + IssuingCertificateURL: options.IssuingCertificateURL, + Subject: csr.Subject, + NotBefore: options.NotBefore, + NotAfter: options.NotAfter, + KeyUsage: options.KeyUsage, + ExtKeyUsage: options.ExtKeyUsage, + BasicConstraintsValid: true, + IsCA: options.IsCA, + } + + if !options.IsCA { + cacrt = tpl + } else { + tpl.Issuer = cacrt.Subject + } + + crt, err := x509.CreateCertificate(rand.Reader, tpl, cacrt, csr.PublicKey, cakey) + if err != nil { + return nil, err + } + + return crt, nil +} + +// NewCertificateRequest create new certificate signing request and return key, csr in byte slice and err +func NewCertificateRequest(opts ...CertificateOption) ([]byte, crypto.PrivateKey, error) { + options := NewCertificateOptions(opts...) + + crtreq := &x509.CertificateRequest{ + Subject: pkix.Name{ + Organization: options.Organization, + OrganizationalUnit: options.OrganizationalUnit, + CommonName: options.CommonName, + }, + SignatureAlgorithm: options.SignatureAlgorithm, + } + + return newCsr(crtreq) +} + +// newCsr returns CSR and private key +func newCsr(crtreq *x509.CertificateRequest) ([]byte, crypto.PrivateKey, error) { + _, key, err := ed25519.GenerateKey(rand.Reader) + if err != nil { + return nil, nil, err + } + csr, err := x509.CreateCertificateRequest(rand.Reader, crtreq, key) + if err != nil { + return nil, nil, err + } + return csr, key, nil +} + +// ServerOptions holds server specific options +type ServerOptions struct { + ServerName string + RootCAs []string + ClientCAs []string +} + +// ServerOption func signature +type ServerOption func(*ServerOptions) + +func NewServerConfig(src *tls.Config) *tls.Config { + dst := src.Clone() + dst.InsecureSkipVerify = true + dst.MinVersion = tls.VersionTLS13 + dst.ClientAuth = tls.VerifyClientCertIfGiven + return dst +} + +func DecodeCrtKey(rawcrt []byte, rawkey []byte) (*x509.Certificate, crypto.PrivateKey, error) { + var crt *x509.Certificate + var key crypto.PrivateKey + var err error + + crt, err = DecodeCrt(rawcrt) + if err == nil { + key, err = DecodeKey(rawkey) + } + + if err != nil { + return nil, nil, err + } + + return crt, key, nil +} + +func DecodeCrt(rawcrt []byte) (*x509.Certificate, error) { + pemcrt, _ := pem.Decode(rawcrt) + return x509.ParseCertificate(pemcrt.Bytes) +} + +func EncodeCrt(crts ...*x509.Certificate) ([]byte, error) { + var err error + buf := bp.Get() + defer bp.Put(buf) + for _, crt := range crts { + if err = pem.Encode(buf, &pem.Block{Type: "CERTIFICATE", Bytes: crt.Raw}); err != nil { + return nil, err + } + } + return buf.Bytes(), nil +} + +func EncodeCsr(csr *x509.Certificate) ([]byte, error) { + buf := bp.Get() + defer bp.Put(buf) + if err := pem.Encode(buf, &pem.Block{Type: "CERTIFICATE REQUEST", Bytes: csr.Raw}); err != nil { + return nil, err + } + return buf.Bytes(), nil +} + +func DecodeKey(rawkey []byte) (crypto.PrivateKey, error) { + pemkey, _ := pem.Decode(rawkey) + return x509.ParsePKCS8PrivateKey(pemkey.Bytes) +} + +func EncodeKey(privkey crypto.PrivateKey) ([]byte, error) { + buf := bp.Get() + defer bp.Put(buf) + enckey, err := x509.MarshalPKCS8PrivateKey(privkey) + if err == nil { + err = pem.Encode(buf, &pem.Block{Type: "PRIVATE KEY", Bytes: enckey}) + } + if err != nil { + return nil, err + } + return buf.Bytes(), nil +} diff --git a/mtls/mtls_test.go b/mtls/mtls_test.go new file mode 100644 index 00000000..a81ff1e2 --- /dev/null +++ b/mtls/mtls_test.go @@ -0,0 +1,35 @@ +package mtls + +import ( + "crypto/ed25519" + "crypto/x509" + "testing" +) + +func TestNewCa(t *testing.T) { + bcrt, key, err := NewCA( + CertificateOrganization("test_org"), + CertificateOrganizationalUnit("test_unit"), + ) + if err != nil { + t.Fatal(err) + } + + if _, ok := key.(ed25519.PrivateKey); !ok { + t.Fatalf("key is not ed25519") + } + + crt, err := x509.ParseCertificate(bcrt) + if err != nil { + t.Fatal(err) + } + if crt.IsCA { + t.Fatalf("crt IsCA invalid %v", crt) + } + if crt.Subject.Organization[0] != "test_org" { + t.Fatalf("crt subject invalid %v", crt.Subject) + } + if crt.Subject.OrganizationalUnit[0] != "test_unit" { + t.Fatalf("crt subject invalid %v", crt.Subject) + } +} diff --git a/mtls/options.go b/mtls/options.go new file mode 100644 index 00000000..b5547f7e --- /dev/null +++ b/mtls/options.go @@ -0,0 +1,155 @@ +package mtls + +import ( + "crypto/x509" + "math/big" + "time" +) + +// CertificateOptions holds options for x509.CreateCertificate +type CertificateOptions struct { + Organization []string + OrganizationalUnit []string + CommonName string + OCSPServer []string + IssuingCertificateURL []string + SerialNumber *big.Int + NotAfter time.Time + NotBefore time.Time + SignatureAlgorithm x509.SignatureAlgorithm + PublicKeyAlgorithm x509.PublicKeyAlgorithm + ExtKeyUsage []x509.ExtKeyUsage + KeyUsage x509.KeyUsage + IsCA bool +} + +// CertificateOrganizationalUnit set OrganizationalUnit in certificate subject +func CertificateOrganizationalUnit(s ...string) CertificateOption { + return func(o *CertificateOptions) { + o.OrganizationalUnit = s + } +} + +// CertificateOrganization set Organization in certificate subject +func CertificateOrganization(s ...string) CertificateOption { + return func(o *CertificateOptions) { + o.Organization = s + } +} + +// CertificateCommonName set CommonName in certificate subject +func CertificateCommonName(s string) CertificateOption { + return func(o *CertificateOptions) { + o.CommonName = s + } +} + +// CertificateOCSPServer set OCSPServer in certificate +func CertificateOCSPServer(s ...string) CertificateOption { + return func(o *CertificateOptions) { + o.OCSPServer = s + } +} + +// CertificateIssuingCertificateURL set IssuingCertificateURL in certificate +func CertificateIssuingCertificateURL(s ...string) CertificateOption { + return func(o *CertificateOptions) { + o.IssuingCertificateURL = s + } +} + +// CertificateSerialNumber set SerialNumber in certificate +func CertificateSerialNumber(n *big.Int) CertificateOption { + return func(o *CertificateOptions) { + o.SerialNumber = n + } +} + +// CertificateNotAfter set NotAfter in certificate +func CertificateNotAfter(t time.Time) CertificateOption { + return func(o *CertificateOptions) { + o.NotAfter = t + } +} + +// CertificateNotBefore set SerialNumber in certificate +func CertificateNotBefore(t time.Time) CertificateOption { + return func(o *CertificateOptions) { + o.NotBefore = t + } +} + +// CertificateExtKeyUsage set ExtKeyUsage in certificate +func CertificateExtKeyUsage(x ...x509.ExtKeyUsage) CertificateOption { + return func(o *CertificateOptions) { + o.ExtKeyUsage = x + } +} + +// CertificateSignatureAlgorithm set SignatureAlgorithm in certificate +func CertificateSignatureAlgorithm(alg x509.SignatureAlgorithm) CertificateOption { + return func(o *CertificateOptions) { + o.SignatureAlgorithm = alg + } +} + +// CertificatePublicKeyAlgorithm set PublicKeyAlgorithm in certificate +func CertificatePublicKeyAlgorithm(alg x509.PublicKeyAlgorithm) CertificateOption { + return func(o *CertificateOptions) { + o.PublicKeyAlgorithm = alg + } +} + +// CertificateKeyUsage set KeyUsage in certificate +func CertificateKeyUsage(u x509.KeyUsage) CertificateOption { + return func(o *CertificateOptions) { + o.KeyUsage = u + } +} + +// CertificateIsCA set IsCA in certificate +func CertificateIsCA(b bool) CertificateOption { + return func(o *CertificateOptions) { + o.IsCA = b + } +} + +// CertificateOption func signature +type CertificateOption func(*CertificateOptions) + +func NewCertificateOptions(opts ...CertificateOption) CertificateOptions { + options := CertificateOptions{} + for _, o := range opts { + o(&options) + } + if options.SerialNumber == nil { + options.SerialNumber = big.NewInt(time.Now().UnixNano()) + } + if options.NotBefore.IsZero() { + options.NotBefore = time.Now() + } + if options.NotAfter.IsZero() { + options.NotAfter = time.Now().Add(10 * time.Minute) + } + if options.SignatureAlgorithm == x509.UnknownSignatureAlgorithm { + options.SignatureAlgorithm = x509.PureEd25519 + } + if options.PublicKeyAlgorithm == x509.UnknownPublicKeyAlgorithm { + options.PublicKeyAlgorithm = x509.Ed25519 + } + if options.ExtKeyUsage == nil { + options.ExtKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth} + if options.IsCA { + options.ExtKeyUsage = append(options.ExtKeyUsage, x509.ExtKeyUsageOCSPSigning, x509.ExtKeyUsageTimeStamping) + } + } + + if options.KeyUsage == 0 { + options.KeyUsage = x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature + if options.IsCA { + options.KeyUsage = x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageDataEncipherment | x509.KeyUsageCertSign + } + } + + return options +} From eb8c1332f0e7ec2e2b24f3ec0abb2b46c86af6a8 Mon Sep 17 00:00:00 2001 From: Vasiliy Tolstov Date: Mon, 27 Jun 2022 00:18:14 +0300 Subject: [PATCH 2/3] fix test Signed-off-by: Vasiliy Tolstov --- mtls/mtls.go | 4 ++-- mtls/mtls_test.go | 40 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/mtls/mtls.go b/mtls/mtls.go index 892e6dc7..a0086a66 100644 --- a/mtls/mtls.go +++ b/mtls/mtls.go @@ -94,7 +94,7 @@ func NewIntermediate(cacrt *x509.Certificate, cakey crypto.PrivateKey, opts ...C // SignCSR sign certificate request and return signed pubkey func SignCSR(rawcsr []byte, cacrt *x509.Certificate, cakey crypto.PrivateKey, opts ...CertificateOption) ([]byte, error) { if cacrt == nil { - opts = append(opts, CertificateIsCA(false)) + opts = append(opts, CertificateIsCA(true)) } options := NewCertificateOptions(opts...) @@ -124,7 +124,7 @@ func SignCSR(rawcsr []byte, cacrt *x509.Certificate, cakey crypto.PrivateKey, op IsCA: options.IsCA, } - if !options.IsCA { + if options.IsCA { cacrt = tpl } else { tpl.Issuer = cacrt.Subject diff --git a/mtls/mtls_test.go b/mtls/mtls_test.go index a81ff1e2..ca1fa140 100644 --- a/mtls/mtls_test.go +++ b/mtls/mtls_test.go @@ -10,6 +10,7 @@ func TestNewCa(t *testing.T) { bcrt, key, err := NewCA( CertificateOrganization("test_org"), CertificateOrganizationalUnit("test_unit"), + CertificateIsCA(true), ) if err != nil { t.Fatal(err) @@ -23,7 +24,7 @@ func TestNewCa(t *testing.T) { if err != nil { t.Fatal(err) } - if crt.IsCA { + if !crt.IsCA { t.Fatalf("crt IsCA invalid %v", crt) } if crt.Subject.Organization[0] != "test_org" { @@ -33,3 +34,40 @@ func TestNewCa(t *testing.T) { t.Fatalf("crt subject invalid %v", crt.Subject) } } + +func TestNewIntermediate(t *testing.T) { + bcrt, cakey, err := NewCA( + CertificateOrganization("test_org"), + CertificateOrganizationalUnit("test_unit"), + ) + if err != nil { + t.Fatal(err) + } + cacrt, err := x509.ParseCertificate(bcrt) + if err != nil { + t.Fatal(err) + } + + bcrt, ikey, err := NewIntermediate(cacrt, cakey, + CertificateOrganization("test_org"), + CertificateOrganizationalUnit("test_unit"), + ) + if err != nil { + t.Fatal(err) + } + _ = ikey + icrt, err := x509.ParseCertificate(bcrt) + if err != nil { + t.Fatal(err) + } + + if icrt.IsCA { + t.Fatalf("crt IsCA invalid %v", icrt) + } + if icrt.Subject.Organization[0] != "test_org" { + t.Fatalf("crt subject invalid %v", icrt.Subject) + } + if icrt.Subject.OrganizationalUnit[0] != "test_unit" { + t.Fatalf("crt subject invalid %v", icrt.Subject) + } +} From 4a8f490e0c4c521e7f71d44a63a2b2ed511ba135 Mon Sep 17 00:00:00 2001 From: Vasiliy Tolstov Date: Mon, 27 Jun 2022 00:20:04 +0300 Subject: [PATCH 3/3] fixup Signed-off-by: Vasiliy Tolstov --- mtls/mtls.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mtls/mtls.go b/mtls/mtls.go index a0086a66..238be41b 100644 --- a/mtls/mtls.go +++ b/mtls/mtls.go @@ -179,7 +179,7 @@ type ServerOption func(*ServerOptions) func NewServerConfig(src *tls.Config) *tls.Config { dst := src.Clone() - dst.InsecureSkipVerify = true + // dst.InsecureSkipVerify = true dst.MinVersion = tls.VersionTLS13 dst.ClientAuth = tls.VerifyClientCertIfGiven return dst