github.com/hechain20/hechain@v0.0.0-20220316014945-b544036ba106/internal/cryptogen/ca/ca.go (about)

     1  /*
     2  Copyright hechain. All Rights Reserved.
     3  
     4  SPDX-License-Identifier: Apache-2.0
     5  */
     6  package ca
     7  
     8  import (
     9  	"crypto"
    10  	"crypto/ecdsa"
    11  	"crypto/elliptic"
    12  	"crypto/rand"
    13  	"crypto/sha256"
    14  	"crypto/x509"
    15  	"crypto/x509/pkix"
    16  	"encoding/pem"
    17  	"io/ioutil"
    18  	"math/big"
    19  	"net"
    20  	"os"
    21  	"path/filepath"
    22  	"strings"
    23  	"time"
    24  
    25  	"github.com/hechain20/hechain/internal/cryptogen/csp"
    26  	"github.com/pkg/errors"
    27  )
    28  
    29  type CA struct {
    30  	Name               string
    31  	Country            string
    32  	Province           string
    33  	Locality           string
    34  	OrganizationalUnit string
    35  	StreetAddress      string
    36  	PostalCode         string
    37  	Signer             crypto.Signer
    38  	SignCert           *x509.Certificate
    39  }
    40  
    41  // NewCA creates an instance of CA and saves the signing key pair in
    42  // baseDir/name
    43  func NewCA(
    44  	baseDir,
    45  	org,
    46  	name,
    47  	country,
    48  	province,
    49  	locality,
    50  	orgUnit,
    51  	streetAddress,
    52  	postalCode string,
    53  ) (*CA, error) {
    54  	var ca *CA
    55  
    56  	err := os.MkdirAll(baseDir, 0o755)
    57  	if err != nil {
    58  		return nil, err
    59  	}
    60  
    61  	priv, err := csp.GeneratePrivateKey(baseDir)
    62  	if err != nil {
    63  		return nil, err
    64  	}
    65  
    66  	template := x509Template()
    67  	// this is a CA
    68  	template.IsCA = true
    69  	template.KeyUsage |= x509.KeyUsageDigitalSignature |
    70  		x509.KeyUsageKeyEncipherment | x509.KeyUsageCertSign |
    71  		x509.KeyUsageCRLSign
    72  	template.ExtKeyUsage = []x509.ExtKeyUsage{
    73  		x509.ExtKeyUsageClientAuth,
    74  		x509.ExtKeyUsageServerAuth,
    75  	}
    76  
    77  	// set the organization for the subject
    78  	subject := subjectTemplateAdditional(country, province, locality, orgUnit, streetAddress, postalCode)
    79  	subject.Organization = []string{org}
    80  	subject.CommonName = name
    81  
    82  	template.Subject = subject
    83  	template.SubjectKeyId = computeSKI(priv)
    84  
    85  	x509Cert, err := genCertificateECDSA(
    86  		baseDir,
    87  		name,
    88  		&template,
    89  		&template,
    90  		&priv.PublicKey,
    91  		priv,
    92  	)
    93  	if err != nil {
    94  		return nil, err
    95  	}
    96  	ca = &CA{
    97  		Name: name,
    98  		Signer: &csp.ECDSASigner{
    99  			PrivateKey: priv,
   100  		},
   101  		SignCert:           x509Cert,
   102  		Country:            country,
   103  		Province:           province,
   104  		Locality:           locality,
   105  		OrganizationalUnit: orgUnit,
   106  		StreetAddress:      streetAddress,
   107  		PostalCode:         postalCode,
   108  	}
   109  
   110  	return ca, err
   111  }
   112  
   113  // SignCertificate creates a signed certificate based on a built-in template
   114  // and saves it in baseDir/name
   115  func (ca *CA) SignCertificate(
   116  	baseDir,
   117  	name string,
   118  	orgUnits,
   119  	alternateNames []string,
   120  	pub *ecdsa.PublicKey,
   121  	ku x509.KeyUsage,
   122  	eku []x509.ExtKeyUsage,
   123  ) (*x509.Certificate, error) {
   124  	template := x509Template()
   125  	template.KeyUsage = ku
   126  	template.ExtKeyUsage = eku
   127  
   128  	// set the organization for the subject
   129  	subject := subjectTemplateAdditional(
   130  		ca.Country,
   131  		ca.Province,
   132  		ca.Locality,
   133  		ca.OrganizationalUnit,
   134  		ca.StreetAddress,
   135  		ca.PostalCode,
   136  	)
   137  	subject.CommonName = name
   138  
   139  	subject.OrganizationalUnit = append(subject.OrganizationalUnit, orgUnits...)
   140  
   141  	template.Subject = subject
   142  	for _, san := range alternateNames {
   143  		// try to parse as an IP address first
   144  		ip := net.ParseIP(san)
   145  		if ip != nil {
   146  			template.IPAddresses = append(template.IPAddresses, ip)
   147  		} else {
   148  			template.DNSNames = append(template.DNSNames, san)
   149  		}
   150  	}
   151  
   152  	cert, err := genCertificateECDSA(
   153  		baseDir,
   154  		name,
   155  		&template,
   156  		ca.SignCert,
   157  		pub,
   158  		ca.Signer,
   159  	)
   160  	if err != nil {
   161  		return nil, err
   162  	}
   163  
   164  	return cert, nil
   165  }
   166  
   167  // compute Subject Key Identifier using RFC 7093, Section 2, Method 4
   168  func computeSKI(privKey *ecdsa.PrivateKey) []byte {
   169  	// Marshall the public key
   170  	raw := elliptic.Marshal(privKey.Curve, privKey.PublicKey.X, privKey.PublicKey.Y)
   171  
   172  	// Hash it
   173  	hash := sha256.Sum256(raw)
   174  	return hash[:]
   175  }
   176  
   177  // default template for X509 subject
   178  func subjectTemplate() pkix.Name {
   179  	return pkix.Name{
   180  		Country:  []string{"US"},
   181  		Locality: []string{"San Francisco"},
   182  		Province: []string{"California"},
   183  	}
   184  }
   185  
   186  // Additional for X509 subject
   187  func subjectTemplateAdditional(
   188  	country,
   189  	province,
   190  	locality,
   191  	orgUnit,
   192  	streetAddress,
   193  	postalCode string,
   194  ) pkix.Name {
   195  	name := subjectTemplate()
   196  	if len(country) >= 1 {
   197  		name.Country = []string{country}
   198  	}
   199  	if len(province) >= 1 {
   200  		name.Province = []string{province}
   201  	}
   202  
   203  	if len(locality) >= 1 {
   204  		name.Locality = []string{locality}
   205  	}
   206  	if len(orgUnit) >= 1 {
   207  		name.OrganizationalUnit = []string{orgUnit}
   208  	}
   209  	if len(streetAddress) >= 1 {
   210  		name.StreetAddress = []string{streetAddress}
   211  	}
   212  	if len(postalCode) >= 1 {
   213  		name.PostalCode = []string{postalCode}
   214  	}
   215  	return name
   216  }
   217  
   218  // default template for X509 certificates
   219  func x509Template() x509.Certificate {
   220  	// generate a serial number
   221  	serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
   222  	serialNumber, _ := rand.Int(rand.Reader, serialNumberLimit)
   223  
   224  	// set expiry to around 10 years
   225  	expiry := 3650 * 24 * time.Hour
   226  	// round minute and backdate 5 minutes
   227  	notBefore := time.Now().Round(time.Minute).Add(-5 * time.Minute).UTC()
   228  
   229  	// basic template to use
   230  	x509 := x509.Certificate{
   231  		SerialNumber:          serialNumber,
   232  		NotBefore:             notBefore,
   233  		NotAfter:              notBefore.Add(expiry).UTC(),
   234  		BasicConstraintsValid: true,
   235  	}
   236  	return x509
   237  }
   238  
   239  // generate a signed X509 certificate using ECDSA
   240  func genCertificateECDSA(
   241  	baseDir,
   242  	name string,
   243  	template,
   244  	parent *x509.Certificate,
   245  	pub *ecdsa.PublicKey,
   246  	priv interface{},
   247  ) (*x509.Certificate, error) {
   248  	// create the x509 public cert
   249  	certBytes, err := x509.CreateCertificate(rand.Reader, template, parent, pub, priv)
   250  	if err != nil {
   251  		return nil, err
   252  	}
   253  
   254  	// write cert out to file
   255  	fileName := filepath.Join(baseDir, name+"-cert.pem")
   256  	certFile, err := os.Create(fileName)
   257  	if err != nil {
   258  		return nil, err
   259  	}
   260  	// pem encode the cert
   261  	err = pem.Encode(certFile, &pem.Block{Type: "CERTIFICATE", Bytes: certBytes})
   262  	certFile.Close()
   263  	if err != nil {
   264  		return nil, err
   265  	}
   266  
   267  	x509Cert, err := x509.ParseCertificate(certBytes)
   268  	if err != nil {
   269  		return nil, err
   270  	}
   271  	return x509Cert, nil
   272  }
   273  
   274  // LoadCertificateECDSA load a ecdsa cert from a file in cert path
   275  func LoadCertificateECDSA(certPath string) (*x509.Certificate, error) {
   276  	var cert *x509.Certificate
   277  	var err error
   278  
   279  	walkFunc := func(path string, info os.FileInfo, err error) error {
   280  		if strings.HasSuffix(path, ".pem") {
   281  			rawCert, err := ioutil.ReadFile(path)
   282  			if err != nil {
   283  				return err
   284  			}
   285  			block, _ := pem.Decode(rawCert)
   286  			if block == nil || block.Type != "CERTIFICATE" {
   287  				return errors.Errorf("%s: wrong PEM encoding", path)
   288  			}
   289  			cert, err = x509.ParseCertificate(block.Bytes)
   290  			if err != nil {
   291  				return errors.Errorf("%s: wrong DER encoding", path)
   292  			}
   293  		}
   294  		return nil
   295  	}
   296  
   297  	err = filepath.Walk(certPath, walkFunc)
   298  	if err != nil {
   299  		return nil, err
   300  	}
   301  
   302  	return cert, err
   303  }