WIP: initial mtls package

Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
This commit is contained in:
Василий Толстов 2022-05-12 15:11:31 +03:00
parent cfe0473ae0
commit b8ad19a5a2
3 changed files with 437 additions and 0 deletions

247
mtls/mtls.go Normal file
View File

@ -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
}

35
mtls/mtls_test.go Normal file
View File

@ -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)
}
}

155
mtls/options.go Normal file
View File

@ -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
}