WIP: initial mtls package
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
This commit is contained in:
parent
cfe0473ae0
commit
b8ad19a5a2
247
mtls/mtls.go
Normal file
247
mtls/mtls.go
Normal 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
35
mtls/mtls_test.go
Normal 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
155
mtls/options.go
Normal 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
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user