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 }