Skip to content

Commit

Permalink
Merge pull request #25 from kebugcheckex/fix-issue21
Browse files Browse the repository at this point in the history
[redo] Attempt to fix intermediate CA issue #21
  • Loading branch information
kairoaraujo authored Sep 26, 2022
2 parents 493afe1 + ca5c4a4 commit e887bda
Show file tree
Hide file tree
Showing 7 changed files with 237 additions and 131 deletions.
8 changes: 4 additions & 4 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
coverage.out
./DoNotUseThisCAPATHTestOnly
DoNotUseThisCAPATHTestOnly
.vscode/
bin/
count.out
count.out
coverage.out
DoNotUseThisCAPATHTestOnly/
121 changes: 78 additions & 43 deletions ca.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,16 +41,17 @@ type Identity struct {
// A CAData represents all the Certificate Authority Data as
// RSA Keys, CRS, CRL, Certificates etc
type CAData struct {
CRL string `json:"crl" example:"-----BEGIN X509 CRL-----...-----END X509 CRL-----\n"` // Revocation List string
Certificate string `json:"certificate" example:"-----BEGIN CERTIFICATE-----...-----END CERTIFICATE-----\n"` // Certificate string
CSR string `json:"csr" example:"-----BEGIN CERTIFICATE REQUEST-----...-----END CERTIFICATE REQUEST-----\n"` // Certificate Signing Request string
PrivateKey string `json:"private_key" example:"-----BEGIN PRIVATE KEY-----...-----END PRIVATE KEY-----\n"` // Private Key string
PublicKey string `json:"public_key" example:"-----BEGIN PUBLIC KEY-----...-----END PUBLIC KEY-----\n"` // Public Key string
privateKey rsa.PrivateKey
certificate *x509.Certificate
publicKey rsa.PublicKey
csr *x509.CertificateRequest
crl *pkix.CertificateList
CRL string `json:"crl" example:"-----BEGIN X509 CRL-----...-----END X509 CRL-----\n"` // Revocation List string
Certificate string `json:"certificate" example:"-----BEGIN CERTIFICATE-----...-----END CERTIFICATE-----\n"` // Certificate string
CSR string `json:"csr" example:"-----BEGIN CERTIFICATE REQUEST-----...-----END CERTIFICATE REQUEST-----\n"` // Certificate Signing Request string
PrivateKey string `json:"private_key" example:"-----BEGIN PRIVATE KEY-----...-----END PRIVATE KEY-----\n"` // Private Key string
PublicKey string `json:"public_key" example:"-----BEGIN PUBLIC KEY-----...-----END PUBLIC KEY-----\n"` // Public Key string
privateKey rsa.PrivateKey
certificate *x509.Certificate
publicKey rsa.PublicKey
csr *x509.CertificateRequest
crl *pkix.CertificateList
IsIntermediate bool
}

// ErrCAMissingInfo means that all information goca.Information{} is required
Expand All @@ -69,7 +70,9 @@ var ErrCertLoadNotFound = errors.New("the requested Certificate does not exist")
// ErrCertRevoked means that certificate was not found in $CAPATH to be loaded.
var ErrCertRevoked = errors.New("the requested Certificate is already revoked")

func (c *CA) create(commonName string, id Identity) error {
var ErrParentCommonNameNotSpecified = errors.New("parent common name is empty when creating an intermediate CA certificate")

func (c *CA) create(commonName, parentCommonName string, id Identity) error {

caData := CAData{}

Expand All @@ -84,9 +87,10 @@ func (c *CA) create(commonName string, id Identity) error {
caCertsDir string = filepath.Join(commonName, "certs")
keyString []byte
publicKeyString []byte
csrString []byte
certBytes []byte
certString []byte
crlString []byte
err error
)

if id.Organization == "" || id.OrganizationalUnit == "" || id.Country == "" || id.Locality == "" || id.Province == "" {
Expand Down Expand Up @@ -123,51 +127,82 @@ func (c *CA) create(commonName string, id Identity) error {
caData.PublicKey = string(publicKeyString)

if !id.Intermediate {
certBytes, err := cert.CreateRootCert(commonName, commonName, id.Country, id.Province, id.Locality, id.Organization, id.OrganizationalUnit, id.EmailAddresses, id.Valid, id.DNSNames, privKey, pubKey, storage.CreationTypeCA)
if err != nil {
return err
}
certificate, _ := x509.ParseCertificate(certBytes)

if certString, err = storage.LoadFile(caDir, commonName+certExtension); err != nil {
certString = []byte{}
caData.IsIntermediate = false
certBytes, err = cert.CreateRootCert(
commonName,
commonName,
id.Country,
id.Province,
id.Locality,
id.Organization,
id.OrganizationalUnit,
id.EmailAddresses,
id.Valid,
id.DNSNames,
privKey,
pubKey,
storage.CreationTypeCA,
)
} else {
if parentCommonName == "" {
return ErrParentCommonNameNotSpecified
}

caData.certificate = certificate
caData.Certificate = string(certString)

crlBytes, err := cert.RevokeCertificate(c.CommonName, []pkix.RevokedCertificate{}, certificate, privKey)
var (
parentCertificate *x509.Certificate
parentPrivateKey *rsa.PrivateKey
)
caData.IsIntermediate = true
parentCertificate, parentPrivateKey, err = cert.LoadParentCACertificate(parentCommonName)
if err != nil {
crl, err := x509.ParseCRL(crlBytes)
if err != nil {
caData.crl = crl
}
return nil
}

if crlString, err = storage.LoadFile(caDir, commonName+crlExtension); err != nil {
crlString = []byte{}
}
certBytes, err = cert.CreateCACert(
commonName,
commonName,
id.Country,
id.Province,
id.Locality,
id.Organization,
id.OrganizationalUnit,
id.EmailAddresses,
id.Valid,
id.DNSNames,
privKey,
parentPrivateKey,
parentCertificate,
pubKey,
storage.CreationTypeCA,
)
}
if err != nil {
return err
}
certificate, _ := x509.ParseCertificate(certBytes)

c.Data.CRL = string(crlString)
if certString, err = storage.LoadFile(caDir, commonName+certExtension); err != nil {
certString = []byte{}
}

} else {
csrBytes, err := cert.CreateCSR(commonName, commonName, id.Country, id.Province, id.Locality, id.Organization, id.OrganizationalUnit, id.EmailAddresses, id.DNSNames, privKey, storage.CreationTypeCA)
caData.certificate = certificate
caData.Certificate = string(certString)

crlBytes, err := cert.RevokeCertificate(c.CommonName, []pkix.RevokedCertificate{}, certificate, privKey)
if err != nil {
crl, err := x509.ParseCRL(crlBytes)
if err != nil {
return err
}
csr, _ := x509.ParseCertificateRequest(csrBytes)
if csrString, err = storage.LoadFile(caDir, commonName+csrExtension); err != nil {
csrString = []byte{}
caData.crl = crl
}
}

caData.csr = csr
caData.CSR = string(csrString)
if crlString, err = storage.LoadFile(caDir, commonName+crlExtension); err != nil {
crlString = []byte{}
}

c.Data.CRL = string(crlString)
c.Data = caData

return nil

}

func (c *CA) loadCA(commonName string) error {
Expand Down
142 changes: 127 additions & 15 deletions cert/cert.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@
// Generating Certificates (even by Signing), the files will be saved in the
// $CAPATH by default.
// For $CAPATH, please check out the GoCA documentation.

package cert

import (
Expand All @@ -40,9 +39,11 @@ import (
"encoding/pem"
"errors"
"math/big"
"path/filepath"
"time"

storage "github.com/kairoaraujo/goca/_storage"
"github.com/kairoaraujo/goca/key"
)

const (
Expand All @@ -52,11 +53,15 @@ const (
MaxValidCert int = 825
// DefaultValidCert is the default valid time: 397 days
DefaultValidCert int = 397
// Certificate file extension
certExtension string = ".crt"
)

// ErrCertExists means that the certificate requested already exists
var ErrCertExists = errors.New("certificate already exists")

var ErrParentCANotFound = errors.New("parent CA not found")

func newSerialNumber() (serialNumber *big.Int) {
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
serialNumber, _ = rand.Int(rand.Reader, serialNumberLimit)
Expand Down Expand Up @@ -135,35 +140,129 @@ func LoadCRL(crlString []byte) (*pkix.CertificateList, error) {
return crl, nil
}

// CreateRootCert creates a Root CA Certificate (self signed)
func CreateRootCert(CACommonName, commonName, country, province, locality, organization, organizationalUnit, emailAddresses string, valid int, dnsNames []string, priv *rsa.PrivateKey, pub *rsa.PublicKey, creationType storage.CreationType) (cert []byte, err error) {
// LoadParentCACertificate loads parent CA's certificate and private key
//
// TODO maybe make this more generic, something like LoadCACertificate that
// returns the certificate and private/public key
func LoadParentCACertificate(commonName string) (certificate *x509.Certificate, privateKey *rsa.PrivateKey, err error) {
caStorage := storage.CAStorage(commonName)
if !caStorage {
return nil, nil, ErrParentCANotFound
}

if valid == 0 {
valid = DefaultValidCert
var caDir = filepath.Join(commonName, "ca")

if keyString, loadErr := storage.LoadFile(filepath.Join(caDir, "key.pem")); loadErr == nil {
privateKey, err = key.LoadPrivateKey(keyString)
if err != nil {
return nil, nil, err
}
} else {
return nil, nil, loadErr
}

if certString, loadErr := storage.LoadFile(filepath.Join(caDir, commonName+certExtension)); loadErr == nil {
certificate, err = LoadCert(certString)
if err != nil {
return nil, nil, err
}
} else {
return nil, nil, loadErr
}
rootCA := &x509.Certificate{
return certificate, privateKey, nil
}

// CreateRootCert creates a Root CA Certificate (self-signed)
func CreateRootCert(
CACommonName,
commonName,
country,
province,
locality,
organization,
organizationalUnit,
emailAddresses string,
valid int,
dnsNames []string,
privateKey *rsa.PrivateKey,
publicKey *rsa.PublicKey,
creationType storage.CreationType,
) (cert []byte, err error) {
cert, err = CreateCACert(
CACommonName,
commonName,
country,
province,
locality,
organization,
organizationalUnit,
emailAddresses,
valid,
dnsNames,
privateKey,
nil, // parentPrivateKey
nil, // parentCertificate
publicKey,
creationType)
return cert, err
}

// CreateCACert creates a CA Certificate
//
// Root certificates are self-signed. When creating a root certificate, leave
// parentPrivateKey and parentCertificate parameters as nil. When creating an
// intermediate CA certificates, provide parentPrivateKey and parentCertificate
func CreateCACert(
CACommonName,
commonName,
country,
province,
locality,
organization,
organizationalUnit,
emailAddresses string,
validDays int,
dnsNames []string,
privateKey,
parentPrivateKey *rsa.PrivateKey,
parentCertificate *x509.Certificate,
publicKey *rsa.PublicKey,
creationType storage.CreationType,
) (cert []byte, err error) {
if validDays == 0 {
validDays = DefaultValidCert
}
caCert := &x509.Certificate{
SerialNumber: newSerialNumber(),
Subject: pkix.Name{
CommonName: commonName,
Organization: []string{organization},
OrganizationalUnit: []string{organizationalUnit},
Country: []string{country},
Province: []string{province},
Locality: []string{locality},
//TODO: StreetAddress: []string{"ADDRESS"},
//TODO: PostalCode: []string{"POSTAL_CODE"},
// TODO: StreetAddress: []string{"ADDRESS"},
// TODO: PostalCode: []string{"POSTAL_CODE"},
},
NotBefore: time.Now(),
NotAfter: time.Now().AddDate(0, 0, valid),
NotAfter: time.Now().AddDate(0, 0, validDays),
IsCA: true,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign | x509.KeyUsageCRLSign,
BasicConstraintsValid: true,
}

dnsNames = append(dnsNames, commonName)
rootCA.DNSNames = dnsNames
caCert.DNSNames = dnsNames

cert, err = x509.CreateCertificate(rand.Reader, rootCA, rootCA, pub, priv)
signingPrivateKey := privateKey
if parentPrivateKey != nil {
signingPrivateKey = parentPrivateKey
}
signingCertificate := caCert
if parentCertificate != nil {
signingCertificate = parentCertificate
}
cert, err = x509.CreateCertificate(rand.Reader, caCert, signingCertificate, publicKey, signingPrivateKey)
if err != nil {
return nil, err
}
Expand All @@ -175,13 +274,27 @@ func CreateRootCert(CACommonName, commonName, country, province, locality, organ
CertData: cert,
CreationType: creationType,
}

err = storage.SaveFile(fileData)

if err != nil {
return nil, err
}

// When creating intermediate CA certificates, store the certificates to its
// parent CA's cert dir
if parentCertificate != nil {
fileData := storage.File{
CA: parentCertificate.Subject.CommonName,
CommonName: commonName,
FileType: storage.FileTypeCertificate,
CreationType: storage.CreationTypeCertificate,
CertData: cert,
}
err = storage.SaveFile(fileData)
if err != nil {
return nil, err
}
}

return cert, nil
}

Expand All @@ -191,7 +304,6 @@ func CreateRootCert(CACommonName, commonName, country, province, locality, organ
func LoadCert(certString []byte) (*x509.Certificate, error) {
block, _ := pem.Decode([]byte(string(certString)))
cert, _ := x509.ParseCertificate(block.Bytes)

return cert, nil
}

Expand Down
Loading

0 comments on commit e887bda

Please sign in to comment.