191 lines
4.5 KiB
Go
191 lines
4.5 KiB
Go
// Copyright (c) Mainflux
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
package mocks
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"crypto/ecdsa"
|
|
"crypto/rand"
|
|
"crypto/rsa"
|
|
"crypto/tls"
|
|
"crypto/x509"
|
|
"crypto/x509/pkix"
|
|
"encoding/pem"
|
|
"math/big"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/mainflux/mainflux/certs/pki"
|
|
"github.com/mainflux/mainflux/pkg/errors"
|
|
)
|
|
|
|
const keyBits = 2048
|
|
|
|
var (
|
|
errPrivateKeyEmpty = errors.New("private key is empty")
|
|
errPrivateKeyUnsupportedType = errors.New("private key type is unsupported")
|
|
)
|
|
|
|
var _ pki.Agent = (*agent)(nil)
|
|
|
|
type agent struct {
|
|
AuthTimeout time.Duration
|
|
TLSCert tls.Certificate
|
|
X509Cert *x509.Certificate
|
|
TTL string
|
|
mu sync.Mutex
|
|
counter uint64
|
|
certs map[string]pki.Cert
|
|
}
|
|
|
|
func NewPkiAgent(tlsCert tls.Certificate, caCert *x509.Certificate, ttl string, timeout time.Duration) pki.Agent {
|
|
return &agent{
|
|
AuthTimeout: timeout,
|
|
TLSCert: tlsCert,
|
|
X509Cert: caCert,
|
|
TTL: ttl,
|
|
certs: make(map[string]pki.Cert),
|
|
}
|
|
}
|
|
|
|
func (a *agent) IssueCert(cn, ttl string) (pki.Cert, error) {
|
|
a.mu.Lock()
|
|
defer a.mu.Unlock()
|
|
|
|
if a.X509Cert == nil {
|
|
return pki.Cert{}, errors.Wrap(pki.ErrFailedCertCreation, pki.ErrMissingCACertificate)
|
|
}
|
|
|
|
var priv interface{}
|
|
priv, err := rsa.GenerateKey(rand.Reader, keyBits)
|
|
if err != nil {
|
|
return pki.Cert{}, errors.Wrap(pki.ErrFailedCertCreation, err)
|
|
}
|
|
|
|
if ttl == "" {
|
|
ttl = a.TTL
|
|
}
|
|
|
|
notBefore := time.Now()
|
|
validFor, err := time.ParseDuration(ttl)
|
|
if err != nil {
|
|
return pki.Cert{}, errors.Wrap(pki.ErrFailedCertCreation, err)
|
|
}
|
|
notAfter := notBefore.Add(validFor)
|
|
|
|
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
|
|
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
|
|
if err != nil {
|
|
return pki.Cert{}, errors.Wrap(pki.ErrFailedCertCreation, err)
|
|
}
|
|
|
|
tmpl := x509.Certificate{
|
|
SerialNumber: serialNumber,
|
|
Subject: pkix.Name{
|
|
Organization: []string{"Mainflux"},
|
|
CommonName: cn,
|
|
OrganizationalUnit: []string{"mainflux"},
|
|
},
|
|
NotBefore: notBefore,
|
|
NotAfter: notAfter,
|
|
|
|
KeyUsage: x509.KeyUsageDigitalSignature,
|
|
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},
|
|
SubjectKeyId: []byte{1, 2, 3, 4, 6},
|
|
}
|
|
|
|
pubKey, err := publicKey(priv)
|
|
if err != nil {
|
|
return pki.Cert{}, errors.Wrap(pki.ErrFailedCertCreation, err)
|
|
}
|
|
derBytes, err := x509.CreateCertificate(rand.Reader, &tmpl, a.X509Cert, pubKey, a.TLSCert.PrivateKey)
|
|
if err != nil {
|
|
return pki.Cert{}, errors.Wrap(pki.ErrFailedCertCreation, err)
|
|
}
|
|
|
|
x509cert, err := x509.ParseCertificate(derBytes)
|
|
if err != nil {
|
|
return pki.Cert{}, errors.Wrap(pki.ErrFailedCertCreation, err)
|
|
}
|
|
|
|
var bw, keyOut bytes.Buffer
|
|
buffWriter := bufio.NewWriter(&bw)
|
|
buffKeyOut := bufio.NewWriter(&keyOut)
|
|
|
|
if err := pem.Encode(buffWriter, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}); err != nil {
|
|
return pki.Cert{}, errors.Wrap(pki.ErrFailedCertCreation, err)
|
|
}
|
|
buffWriter.Flush()
|
|
cert := bw.String()
|
|
|
|
block, err := pemBlockForKey(priv)
|
|
if err != nil {
|
|
return pki.Cert{}, errors.Wrap(pki.ErrFailedCertCreation, err)
|
|
}
|
|
if err := pem.Encode(buffKeyOut, block); err != nil {
|
|
return pki.Cert{}, errors.Wrap(pki.ErrFailedCertCreation, err)
|
|
}
|
|
buffKeyOut.Flush()
|
|
key := keyOut.String()
|
|
|
|
a.certs[x509cert.SerialNumber.String()] = pki.Cert{
|
|
ClientCert: cert,
|
|
}
|
|
a.counter++
|
|
|
|
return pki.Cert{
|
|
ClientCert: cert,
|
|
ClientKey: key,
|
|
Serial: x509cert.SerialNumber.String(),
|
|
Expire: x509cert.NotAfter.Unix(),
|
|
IssuingCA: x509cert.Issuer.String(),
|
|
}, nil
|
|
}
|
|
|
|
func (a *agent) Read(serial string) (pki.Cert, error) {
|
|
a.mu.Lock()
|
|
defer a.mu.Unlock()
|
|
|
|
crt, ok := a.certs[serial]
|
|
if !ok {
|
|
return pki.Cert{}, errors.ErrNotFound
|
|
}
|
|
|
|
return crt, nil
|
|
}
|
|
|
|
func (a *agent) Revoke(serial string) (time.Time, error) {
|
|
return time.Now(), nil
|
|
}
|
|
|
|
func publicKey(priv interface{}) (interface{}, error) {
|
|
if priv == nil {
|
|
return nil, errPrivateKeyEmpty
|
|
}
|
|
switch k := priv.(type) {
|
|
case *rsa.PrivateKey:
|
|
return &k.PublicKey, nil
|
|
case *ecdsa.PrivateKey:
|
|
return &k.PublicKey, nil
|
|
default:
|
|
return nil, errPrivateKeyUnsupportedType
|
|
}
|
|
}
|
|
|
|
func pemBlockForKey(priv interface{}) (*pem.Block, error) {
|
|
switch k := priv.(type) {
|
|
case *rsa.PrivateKey:
|
|
return &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(k)}, nil
|
|
case *ecdsa.PrivateKey:
|
|
b, err := x509.MarshalECPrivateKey(k)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &pem.Block{Type: "EC PRIVATE KEY", Bytes: b}, nil
|
|
default:
|
|
return nil, nil
|
|
}
|
|
}
|