303 lines
6.4 KiB
Go
303 lines
6.4 KiB
Go
/*
|
|
Copyright IBM Corp. All Rights Reserved.
|
|
|
|
SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
package ca
|
|
|
|
import (
|
|
"crypto"
|
|
"crypto/ecdsa"
|
|
"crypto/elliptic"
|
|
"crypto/rand"
|
|
"crypto/sha256"
|
|
"crypto/x509"
|
|
"crypto/x509/pkix"
|
|
"encoding/pem"
|
|
"math/big"
|
|
"net"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/hyperledger/fabric/internal/cryptogen/csp"
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
type CA struct {
|
|
Name string
|
|
Country string
|
|
Province string
|
|
Locality string
|
|
OrganizationalUnit string
|
|
StreetAddress string
|
|
PostalCode string
|
|
Signer crypto.Signer
|
|
SignCert *x509.Certificate
|
|
}
|
|
|
|
// NewCA creates an instance of CA and saves the signing key pair in
|
|
// baseDir/name
|
|
func NewCA(
|
|
baseDir,
|
|
org,
|
|
name,
|
|
country,
|
|
province,
|
|
locality,
|
|
orgUnit,
|
|
streetAddress,
|
|
postalCode string,
|
|
) (*CA, error) {
|
|
var ca *CA
|
|
|
|
err := os.MkdirAll(baseDir, 0o755)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
priv, err := csp.GeneratePrivateKey(baseDir)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
template := x509Template()
|
|
// this is a CA
|
|
template.IsCA = true
|
|
template.KeyUsage |= x509.KeyUsageDigitalSignature |
|
|
x509.KeyUsageKeyEncipherment | x509.KeyUsageCertSign |
|
|
x509.KeyUsageCRLSign
|
|
template.ExtKeyUsage = []x509.ExtKeyUsage{
|
|
x509.ExtKeyUsageClientAuth,
|
|
x509.ExtKeyUsageServerAuth,
|
|
}
|
|
|
|
// set the organization for the subject
|
|
subject := subjectTemplateAdditional(country, province, locality, orgUnit, streetAddress, postalCode)
|
|
subject.Organization = []string{org}
|
|
subject.CommonName = name
|
|
|
|
template.Subject = subject
|
|
template.SubjectKeyId = computeSKI(priv)
|
|
|
|
x509Cert, err := genCertificateECDSA(
|
|
baseDir,
|
|
name,
|
|
&template,
|
|
&template,
|
|
&priv.PublicKey,
|
|
priv,
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
ca = &CA{
|
|
Name: name,
|
|
Signer: &csp.ECDSASigner{
|
|
PrivateKey: priv,
|
|
},
|
|
SignCert: x509Cert,
|
|
Country: country,
|
|
Province: province,
|
|
Locality: locality,
|
|
OrganizationalUnit: orgUnit,
|
|
StreetAddress: streetAddress,
|
|
PostalCode: postalCode,
|
|
}
|
|
|
|
return ca, err
|
|
}
|
|
|
|
// SignCertificate creates a signed certificate based on a built-in template
|
|
// and saves it in baseDir/name
|
|
func (ca *CA) SignCertificate(
|
|
baseDir,
|
|
name string,
|
|
orgUnits,
|
|
alternateNames []string,
|
|
pub *ecdsa.PublicKey,
|
|
ku x509.KeyUsage,
|
|
eku []x509.ExtKeyUsage,
|
|
) (*x509.Certificate, error) {
|
|
template := x509Template()
|
|
template.KeyUsage = ku
|
|
template.ExtKeyUsage = eku
|
|
|
|
// set the organization for the subject
|
|
subject := subjectTemplateAdditional(
|
|
ca.Country,
|
|
ca.Province,
|
|
ca.Locality,
|
|
ca.OrganizationalUnit,
|
|
ca.StreetAddress,
|
|
ca.PostalCode,
|
|
)
|
|
subject.CommonName = name
|
|
|
|
subject.OrganizationalUnit = append(subject.OrganizationalUnit, orgUnits...)
|
|
|
|
template.Subject = subject
|
|
for _, san := range alternateNames {
|
|
// try to parse as an IP address first
|
|
ip := net.ParseIP(san)
|
|
if ip != nil {
|
|
template.IPAddresses = append(template.IPAddresses, ip)
|
|
} else {
|
|
template.DNSNames = append(template.DNSNames, san)
|
|
}
|
|
}
|
|
|
|
cert, err := genCertificateECDSA(
|
|
baseDir,
|
|
name,
|
|
&template,
|
|
ca.SignCert,
|
|
pub,
|
|
ca.Signer,
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return cert, nil
|
|
}
|
|
|
|
// compute Subject Key Identifier using RFC 7093, Section 2, Method 4
|
|
func computeSKI(privKey *ecdsa.PrivateKey) []byte {
|
|
// Marshall the public key
|
|
raw := elliptic.Marshal(privKey.Curve, privKey.PublicKey.X, privKey.PublicKey.Y)
|
|
|
|
// Hash it
|
|
hash := sha256.Sum256(raw)
|
|
return hash[:]
|
|
}
|
|
|
|
// default template for X509 subject
|
|
func subjectTemplate() pkix.Name {
|
|
return pkix.Name{
|
|
Country: []string{"US"},
|
|
Locality: []string{"San Francisco"},
|
|
Province: []string{"California"},
|
|
}
|
|
}
|
|
|
|
// Additional for X509 subject
|
|
func subjectTemplateAdditional(
|
|
country,
|
|
province,
|
|
locality,
|
|
orgUnit,
|
|
streetAddress,
|
|
postalCode string,
|
|
) pkix.Name {
|
|
name := subjectTemplate()
|
|
if len(country) >= 1 {
|
|
name.Country = []string{country}
|
|
}
|
|
if len(province) >= 1 {
|
|
name.Province = []string{province}
|
|
}
|
|
|
|
if len(locality) >= 1 {
|
|
name.Locality = []string{locality}
|
|
}
|
|
if len(orgUnit) >= 1 {
|
|
name.OrganizationalUnit = []string{orgUnit}
|
|
}
|
|
if len(streetAddress) >= 1 {
|
|
name.StreetAddress = []string{streetAddress}
|
|
}
|
|
if len(postalCode) >= 1 {
|
|
name.PostalCode = []string{postalCode}
|
|
}
|
|
return name
|
|
}
|
|
|
|
// default template for X509 certificates
|
|
func x509Template() x509.Certificate {
|
|
// generate a serial number
|
|
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
|
|
serialNumber, _ := rand.Int(rand.Reader, serialNumberLimit)
|
|
|
|
// set expiry to around 10 years
|
|
expiry := 3650 * 24 * time.Hour
|
|
// round minute and backdate 5 minutes
|
|
notBefore := time.Now().Round(time.Minute).Add(-5 * time.Minute).UTC()
|
|
|
|
// basic template to use
|
|
x509 := x509.Certificate{
|
|
SerialNumber: serialNumber,
|
|
NotBefore: notBefore,
|
|
NotAfter: notBefore.Add(expiry).UTC(),
|
|
BasicConstraintsValid: true,
|
|
}
|
|
return x509
|
|
}
|
|
|
|
// generate a signed X509 certificate using ECDSA
|
|
func genCertificateECDSA(
|
|
baseDir,
|
|
name string,
|
|
template,
|
|
parent *x509.Certificate,
|
|
pub *ecdsa.PublicKey,
|
|
priv interface{},
|
|
) (*x509.Certificate, error) {
|
|
// create the x509 public cert
|
|
certBytes, err := x509.CreateCertificate(rand.Reader, template, parent, pub, priv)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// write cert out to file
|
|
fileName := filepath.Join(baseDir, name+"-cert.pem")
|
|
certFile, err := os.Create(fileName)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
// pem encode the cert
|
|
err = pem.Encode(certFile, &pem.Block{Type: "CERTIFICATE", Bytes: certBytes})
|
|
certFile.Close()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
x509Cert, err := x509.ParseCertificate(certBytes)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return x509Cert, nil
|
|
}
|
|
|
|
// LoadCertificateECDSA load a ecdsa cert from a file in cert path
|
|
func LoadCertificateECDSA(certPath string) (*x509.Certificate, error) {
|
|
var cert *x509.Certificate
|
|
var err error
|
|
|
|
walkFunc := func(path string, info os.FileInfo, err error) error {
|
|
if strings.HasSuffix(path, ".pem") {
|
|
rawCert, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
block, _ := pem.Decode(rawCert)
|
|
if block == nil || block.Type != "CERTIFICATE" {
|
|
return errors.Errorf("%s: wrong PEM encoding", path)
|
|
}
|
|
cert, err = x509.ParseCertificate(block.Bytes)
|
|
if err != nil {
|
|
return errors.Errorf("%s: wrong DER encoding", path)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
err = filepath.Walk(certPath, walkFunc)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return cert, err
|
|
}
|