Merge pull request #133 from unistack-org/mtls
WIP: initial mtls package
This commit is contained in:
commit
5953b5aae6
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(true))
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
73
mtls/mtls_test.go
Normal file
73
mtls/mtls_test.go
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
package mtls
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/ed25519"
|
||||||
|
"crypto/x509"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNewCa(t *testing.T) {
|
||||||
|
bcrt, key, err := NewCA(
|
||||||
|
CertificateOrganization("test_org"),
|
||||||
|
CertificateOrganizationalUnit("test_unit"),
|
||||||
|
CertificateIsCA(true),
|
||||||
|
)
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
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…
Reference in New Issue
Block a user