Last active
August 12, 2022 23:13
-
-
Save jiahuif/07771dc46335e351a34ef72c9e455be4 to your computer and use it in GitHub Desktop.
cfssl initCA with Name Constraints
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"crypto/ecdsa" | |
"crypto/elliptic" | |
"crypto/rand" | |
"crypto/x509" | |
"crypto/x509/pkix" | |
"encoding/hex" | |
"encoding/json" | |
"flag" | |
"fmt" | |
"log" | |
"math/big" | |
"os" | |
"time" | |
"github.com/cloudflare/cfssl/cli" | |
"github.com/cloudflare/cfssl/config" | |
"github.com/cloudflare/cfssl/csr" | |
"github.com/cloudflare/cfssl/helpers" | |
"github.com/cloudflare/cfssl/initca" | |
"github.com/cloudflare/cfssl/signer" | |
"github.com/cloudflare/cfssl/signer/local" | |
) | |
// main is the entry of the program | |
// | |
// the following fields are honored from the JSON-serialized certificate | |
// | |
// PermittedDNSDomainsCritical | |
// PermittedDNSDomains | |
// PermittedURIDomains | |
// PermittedIPRanges | |
// PermittedEmailAddresses | |
// ExcludedDNSDomains | |
// ExcludedURIDomains | |
// ExcludedIPRanges | |
// ExcludedEmailAddresses | |
// | |
// example certificate.json | |
// | |
// { | |
// "PermittedDNSDomainsCritical": true, | |
// "PermittedDNSDomains": [ | |
// ".example.com" | |
// ] | |
// } | |
// | |
// example csr.json | |
// | |
// { | |
// "CN": "example.com", | |
// "names": [ | |
// { | |
// "C": "US", | |
// "L": "San Francisco", | |
// "O": "Internet Widgets, Inc.", | |
// "OU": "WWW", | |
// "ST": "California" | |
// } | |
// ], | |
// "ca": { | |
// "expiry": "87600h" | |
// } | |
// } | |
func main() { | |
var csrFile, certFile string | |
flag.StringVar(&csrFile, "csr", "", "cfssl compatible csr JSON file (optional)") | |
flag.StringVar(&certFile, "cert", "", "golang certificate as JSON file (optional)") | |
flag.Parse() | |
req := csr.New() | |
if csrFile != "" { | |
err := func() error { | |
f, err := os.Open(csrFile) | |
if err != nil { | |
return err | |
} | |
defer func(f *os.File) { | |
_ = f.Close() | |
}(f) | |
return json.NewDecoder(f).Decode(req) | |
}() | |
if err != nil { | |
log.Fatalln(err) | |
} | |
} | |
cert := new(x509.Certificate) | |
if certFile != "" { | |
err := func() error { | |
f, err := os.Open(certFile) | |
if err != nil { | |
return err | |
} | |
defer func(f *os.File) { | |
_ = f.Close() | |
}(f) | |
return json.NewDecoder(f).Decode(cert) | |
}() | |
if err != nil { | |
log.Fatalln(err) | |
} | |
} | |
extensions, err := generateExtensions(cert) | |
if err != nil { | |
log.Fatalln(err) | |
} | |
req.Extensions = append(req.Extensions, extensions...) | |
err = initCA(req) | |
if err != nil { | |
log.Fatalln(err) | |
} | |
} | |
// initCA is borrowed from "github.com/cloudflare/cfssl/initca" | |
func initCA(req *csr.CertificateRequest) (err error) { | |
policy := initca.CAPolicy() | |
if policy.Default.ExtensionWhitelist == nil { | |
policy.Default.ExtensionWhitelist = make(map[string]bool) | |
} | |
// add each extension to the allowed list | |
for _, e := range req.Extensions { | |
policy.Default.ExtensionWhitelist[e.Id.String()] = true | |
} | |
if req.CA != nil { | |
if req.CA.Expiry != "" { | |
policy.Default.ExpiryString = req.CA.Expiry | |
policy.Default.Expiry, err = time.ParseDuration(req.CA.Expiry) | |
if err != nil { | |
return | |
} | |
} | |
if req.CA.Backdate != "" { | |
policy.Default.Backdate, err = time.ParseDuration(req.CA.Backdate) | |
if err != nil { | |
return | |
} | |
} | |
policy.Default.CAConstraint.MaxPathLen = req.CA.PathLength | |
if req.CA.PathLength != 0 && req.CA.PathLenZero { | |
log.Println("ignore invalid 'pathlenzero' value") | |
} else { | |
policy.Default.CAConstraint.MaxPathLenZero = req.CA.PathLenZero | |
} | |
} | |
if req.CRL != "" { | |
policy.Default.CRL = req.CRL | |
} | |
g := &csr.Generator{ | |
Validator: func(req *csr.CertificateRequest) error { | |
if req.CN != "" { | |
return nil | |
} | |
if len(req.Names) == 0 { | |
return fmt.Errorf("missing subject information") | |
} | |
for i := range req.Names { | |
if csr.IsNameEmpty(req.Names[i]) { | |
return fmt.Errorf("missing subject information") | |
} | |
} | |
return nil | |
}, | |
} | |
csrPEM, key, err := g.ProcessRequest(req) | |
if err != nil { | |
log.Fatalf("failed to process request: %v", err) | |
return | |
} | |
priv, err := helpers.ParsePrivateKeyPEM(key) | |
if err != nil { | |
log.Fatalf("failed to parse private key: %v", err) | |
return | |
} | |
s, err := local.NewSigner(priv, nil, signer.DefaultSigAlgo(priv), policy) | |
if err != nil { | |
log.Fatalf("failed to create signer: %v", err) | |
return | |
} | |
signReq := signer.SignRequest{ | |
Hosts: req.Hosts, | |
Request: string(csrPEM), | |
Extensions: convertExtensions(req.Extensions), | |
} | |
cert, err := s.Sign(signReq) | |
if err != nil { | |
log.Fatalf("failed to sign: %v", err) | |
} | |
cli.PrintCert(key, csrPEM, cert) | |
return | |
} | |
// generateExtensions generates the X509v3 Name Constraints from corresponding | |
// fields of golang API. | |
func generateExtensions(cert *x509.Certificate) ([]pkix.Extension, error) { | |
newCert := &x509.Certificate{ | |
SerialNumber: big.NewInt(1), | |
PermittedDNSDomainsCritical: cert.PermittedDNSDomainsCritical, | |
PermittedDNSDomains: cert.PermittedDNSDomains, | |
PermittedURIDomains: cert.PermittedURIDomains, | |
PermittedIPRanges: cert.PermittedIPRanges, | |
PermittedEmailAddresses: cert.PermittedEmailAddresses, | |
ExcludedDNSDomains: cert.ExcludedDNSDomains, | |
ExcludedURIDomains: cert.ExcludedURIDomains, | |
ExcludedIPRanges: cert.ExcludedIPRanges, | |
ExcludedEmailAddresses: cert.ExcludedEmailAddresses, | |
} | |
priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) | |
if err != nil { | |
return nil, err | |
} | |
// generated and reparse so that the extension field is populated. | |
certBytes, err := x509.CreateCertificate(rand.Reader, newCert, newCert, &priv.PublicKey, priv) | |
if err != nil { | |
return nil, err | |
} | |
newCert, err = x509.ParseCertificate(certBytes) | |
return newCert.Extensions, err | |
} | |
func convertExtensions(in []pkix.Extension) []signer.Extension { | |
out := make([]signer.Extension, 0, len(in)) | |
for _, e := range in { | |
out = append(out, signer.Extension{ | |
ID: config.OID(e.Id), | |
Critical: e.Critical, | |
Value: hex.EncodeToString(e.Value), | |
}) | |
} | |
return out | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment