diff options
| -rw-r--r-- | cmd/easyca/main.go | 66 | ||||
| -rw-r--r-- | pkg/easyca/easyca.go | 155 |
2 files changed, 178 insertions, 43 deletions
diff --git a/cmd/easyca/main.go b/cmd/easyca/main.go index e4a5ff7..2991a70 100644 --- a/cmd/easyca/main.go +++ b/cmd/easyca/main.go @@ -3,7 +3,6 @@ package main import ( "crypto/x509" "crypto/x509/pkix" - "fmt" "log" "net" "os" @@ -20,43 +19,10 @@ import ( func initPki(c *cli.Context) { log.Print("generating new pki structure") - pkiroot := filepath.Join(c.GlobalString("root")) - - for _, dir := range []string{"private", "issued"} { - err := os.Mkdir(filepath.Join(pkiroot, dir), 0755) - if err != nil { - log.Fatalf("creating dir %v: %v", dir, err) - } - log.Printf("created %v directory", dir) - } - - serial, err := os.Create(filepath.Join(pkiroot, "serial")) - if err != nil { - log.Fatalf("create serial: %v", err) - } - defer serial.Close() - n, err := fmt.Fprintln(serial, "01") - if err != nil { - log.Fatalf("write serial: %v", err) - } - if n == 0 { - log.Fatal("write serial, written 0 bytes") - } - log.Print("created serial") - - crlnumber, err := os.Create(filepath.Join(pkiroot, "crlnumber")) - if err != nil { - log.Fatalf("create crlnumber: %v", err) - } - defer crlnumber.Close() - n, err = fmt.Fprintln(crlnumber, "01") + err := easyca.GeneratePKIStructure(filepath.Join(c.GlobalString("root"))) if err != nil { - log.Fatalf("write crlnumber: %v", err) + log.Fatalf("generate pki structure: %v", err) } - if n == 0 { - log.Fatal("write crlnumber, written 0 bytes") - } - log.Print("created crlnumber") } func createBundle(c *cli.Context) { @@ -115,7 +81,21 @@ func createBundle(c *cli.Context) { if err != nil { log.Fatal(err) } - +} +func revoke(c *cli.Context) { + if !c.Args().Present() { + cli.ShowSubcommandHelp(c) + log.Fatalf("Usage: %v path/to/cert.crt", c.Command.FullName()) + } + crtPath := c.Args().First() + crt, err := easyca.GetCertificate(crtPath) + if err != nil { + log.Fatalf("get certificate (%v): %v", crtPath, err) + } + err = easyca.RevokeSerial(c.GlobalString("root"), crt.SerialNumber) + if err != nil { + log.Fatalf("revoke serial %X: %v", crt.SerialNumber, err) + } } func parseArgs() { @@ -136,9 +116,15 @@ func parseArgs() { } app.Commands = []cli.Command{ { - Name: "init", - Usage: "create directory structure", - Action: initPki, + Name: "init", + Description: "create directory structure", + Action: initPki, + }, + { + Name: "revoke", + Usage: "revoke path/to/cert", + Description: "revoke certificate", + Action: revoke, }, { Name: "create", diff --git a/pkg/easyca/easyca.go b/pkg/easyca/easyca.go index 2bd43fe..b7521da 100644 --- a/pkg/easyca/easyca.go +++ b/pkg/easyca/easyca.go @@ -1,6 +1,7 @@ package easyca import ( + "bufio" "crypto/rand" "crypto/rsa" "crypto/sha1" @@ -12,9 +13,22 @@ import ( "math/big" "os" "path/filepath" + "regexp" "time" ) +var ( + // Index format + // 0 full string + // 1 Valid/Revoked/Expired + // 2 Expiration date + // 3 Revocation date + // 4 Serial + // 5 Filename + // 6 Subject + indexRegexp = regexp.MustCompile("^(V|R|E)\t([0-9]{12}Z)\t([0-9]{12}Z)?\t([0-9a-fA-F]{2,})\t([^\t]+)\t(.+)") +) + func GeneratePrivateKey(path string) (*rsa.PrivateKey, error) { keyFile, err := os.Create(path) if err != nil { @@ -39,8 +53,13 @@ func GeneratePrivateKey(path string) (*rsa.PrivateKey, error) { func GenerateCertifcate(pkiroot, name string, template *x509.Certificate) error { // TODO(jclerc): check that pki has been init + var crtPath string privateKeyPath := filepath.Join(pkiroot, "private", name+".key") - crtPath := filepath.Join(pkiroot, name+".crt") + if name == "ca" { + crtPath = filepath.Join(pkiroot, name+".crt") + } else { + crtPath = filepath.Join(pkiroot, "issued", name+".crt") + } var caCrt *x509.Certificate var caKey *rsa.PrivateKey @@ -150,8 +169,72 @@ func GetCA(pkiroot string) (*x509.Certificate, *rsa.PrivateKey, error) { return caCrt, caKey, nil } +func RevokeSerial(pkiroot string, serial *big.Int) error { + f, err := os.OpenFile(filepath.Join(pkiroot, "index.txt"), os.O_RDWR, 0644) + if err != nil { + return err + } + defer f.Close() + + var lines []string + scanner := bufio.NewScanner(f) + for scanner.Scan() { + matches := indexRegexp.FindStringSubmatch(scanner.Text()) + if len(matches) != 7 { + return fmt.Errorf("wrong line format") + } + matchedSerial := big.NewInt(0) + fmt.Sscanf(matches[4], "%X", matchedSerial) + if matchedSerial.Cmp(serial) == 0 { + if matches[1] == "R" { + return fmt.Errorf("certificate already revoked") + } + + lines = append(lines, fmt.Sprintf("R\t%v\t%vZ\t%v\t%v\t%v", + matches[2], + time.Now().UTC().Format("060102150405"), + matches[4], + matches[5], + matches[6])) + } else { + lines = append(lines, matches[0]) + } + } + + f.Truncate(0) + f.Seek(0, 0) + + for _, line := range lines { + n, err := fmt.Fprintln(f, line) + if err != nil { + return fmt.Errorf("write line: %v", err) + } + if n == 0 { + return fmt.Errorf("supposed to write [%v], written 0 bytes", line) + } + } + return nil +} + +func GetCertificate(path string) (*x509.Certificate, error) { + crtBytes, err := ioutil.ReadFile(path) + if err != nil { + return nil, fmt.Errorf("read crt: %v", err) + } + p, _ := pem.Decode(crtBytes) + if p == nil { + return nil, fmt.Errorf("pem decode did not found pem encoded cert") + } + crt, err := x509.ParseCertificate(p.Bytes) + if err != nil { + return nil, fmt.Errorf("parse crt: %v", err) + } + + return crt, nil +} + func WriteIndex(pkiroot, filename string, crt *x509.Certificate) error { - f, err := os.OpenFile(filepath.Join(pkiroot, "index.txt"), os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644) + f, err := os.OpenFile(filepath.Join(pkiroot, "index.txt"), os.O_WRONLY|os.O_APPEND, 0644) if err != nil { return err } @@ -162,7 +245,7 @@ func WriteIndex(pkiroot, filename string, crt *x509.Certificate) error { if len(serialOutput)%2 == 1 { serialOutput = "0" + serialOutput } - + // subject: /C=FR/ST=IDF/O=Umbrella Corporation/CN=test.clerc.io // Date format: yymmddHHMMSSZ // E|R|V<tab>Expiry<tab>[RevocationDate]<tab>Serial<tab>filename<tab>SubjectDN n, err := fmt.Fprintf(f, "V\t%vZ\t\t%v\t%v.crt\t%v\n", @@ -178,3 +261,69 @@ func WriteIndex(pkiroot, filename string, crt *x509.Certificate) error { } return nil } + +// |-ca.crt +// |-crlnumber +// |-index.txt +// |-index.txt.attr +// |-serial +// |-issued/ +// |- name.crt +// |-private +// |- ca.key +// |- name.key +func GeneratePKIStructure(pkiroot string) error { + + for _, dir := range []string{"private", "issued"} { + err := os.Mkdir(filepath.Join(pkiroot, dir), 0755) + if err != nil { + return fmt.Errorf("creating dir %v: %v", dir, err) + } + } + + serial, err := os.Create(filepath.Join(pkiroot, "serial")) + if err != nil { + return fmt.Errorf("create serial: %v", err) + } + defer serial.Close() + n, err := fmt.Fprintln(serial, "01") + if err != nil { + return fmt.Errorf("write serial: %v", err) + } + if n == 0 { + return fmt.Errorf("write serial, written 0 bytes") + } + + crlnumber, err := os.Create(filepath.Join(pkiroot, "crlnumber")) + if err != nil { + return fmt.Errorf("create crlnumber: %v", err) + } + defer crlnumber.Close() + n, err = fmt.Fprintln(crlnumber, "01") + if err != nil { + return fmt.Errorf("write crlnumber: %v", err) + } + if n == 0 { + return fmt.Errorf("write crlnumber, written 0 bytes") + } + + index, err := os.Create(filepath.Join(pkiroot, "index.txt")) + if err != nil { + return fmt.Errorf("create index: %v", err) + } + defer index.Close() + + indexattr, err := os.Create(filepath.Join(pkiroot, "index.txt.attr")) + if err != nil { + return fmt.Errorf("create index.txt.attr: %v", err) + } + defer indexattr.Close() + n, err = fmt.Fprintln(indexattr, "unique_subject = no") + if err != nil { + return fmt.Errorf("write index.txt.attr: %v", err) + } + if n == 0 { + return fmt.Errorf("write index.txt.attr, written 0 bytes") + } + return nil +} |
