Skip to content

Commit

Permalink
Refactor and updates
Browse files Browse the repository at this point in the history
Move cmd helpers into cmd/pcert. Update dependencies.
Get rid of Merger and instead use WithEnv cobra helper.
  • Loading branch information
dvob committed Jul 27, 2024
1 parent 540e8d8 commit 77d0540
Show file tree
Hide file tree
Showing 22 changed files with 130 additions and 363 deletions.
32 changes: 28 additions & 4 deletions cert.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ import (
"crypto/sha1"
"crypto/x509"
"crypto/x509/pkix"
"errors"
"fmt"
"io"
"math/big"
"reflect"
"time"
Expand Down Expand Up @@ -70,6 +72,7 @@ func Sign(cert *x509.Certificate, publicKey any, signCert *x509.Certificate, sig
cert.SubjectKeyId = subjectKeyID
}
if cert.AuthorityKeyId == nil {
// TODO: is probably already done in Go
cert.AuthorityKeyId = signCert.SubjectKeyId
}

Expand All @@ -82,7 +85,7 @@ func Sign(cert *x509.Certificate, publicKey any, signCert *x509.Certificate, sig
}

if cert.SerialNumber == nil {
serialNumber, err := getRandomSerialNumber()
serialNumber, err := generateSerial(rand.Reader)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -173,7 +176,28 @@ func getSubjectKeyID(pub crypto.PublicKey) ([]byte, error) {
return pubHash[:], nil
}

func getRandomSerialNumber() (*big.Int, error) {
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
return rand.Int(rand.Reader, serialNumberLimit)
// GenerateSerial produces an RFC 5280 conformant serial number to be used
// in a certificate. The serial number will be a positive integer, no more
// than 20 octets in length, generated using the provided random source.
//
// Code from: https://go-review.googlesource.com/c/go/+/479120/3/src/crypto/x509/x509.go#2485
func generateSerial(rand io.Reader) (*big.Int, error) {
randBytes := make([]byte, 20)
for i := 0; i < 10; i++ {
// get 20 random bytes
_, err := io.ReadFull(rand, randBytes)
if err != nil {
return nil, err
}
// clear the top bit (to prevent the number being negative)
randBytes[0] &= 0x7f
// convert to big.Int
serial := new(big.Int).SetBytes(randBytes)
// check that the serial is not zero
if serial.Sign() == 0 {
continue
}
return serial, nil
}
return nil, errors.New("x509: failed to generate serial number because the random source returns only zeros")
}
10 changes: 0 additions & 10 deletions cmd/merger.go

This file was deleted.

File renamed without changes.
62 changes: 62 additions & 0 deletions cmd/pcert/cobra.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package main

import (
"errors"
"fmt"
"os"
"strings"

"github.com/spf13/cobra"

Check failure on line 9 in cmd/pcert/cobra.go

View workflow job for this annotation

GitHub Actions / test

github.com/spf13/[email protected]: replacement directory ../cobra does not exist
"github.com/spf13/pflag"
)

func WithEnv(c *cobra.Command) *cobra.Command {
if c.HasParent() {
c = c.Root()
}

args := os.Args[1:]

var (
cmd *cobra.Command
err error
)
if c.TraverseChildren {
cmd, _, err = c.Traverse(args)
} else {
cmd, _, err = c.Find(args)
}

if err != nil {
fmt.Println("ERRROR hier")
return c
}

var errs []error
for _, fs := range []*pflag.FlagSet{
cmd.Flags(),
cmd.PersistentFlags(),
} {
fs.VisitAll(func(f *pflag.Flag) {
optName := strings.ToUpper(f.Name)
optName = strings.ReplaceAll(optName, "-", "_")
varName := envVarPrefix + optName
if val, ok := os.LookupEnv(varName); ok {
err := f.Value.Set(val)
if err != nil {
errs = append(errs, fmt.Errorf("invalid environment variable '%s': %w", varName, err))
}
f.Changed = true
}
})
}
if len(errs) != 0 {
// we want to report the error after errors from the normal
// parsing that for example a --help would still take effect.
// to report the error we just overwrite PreRunE
cmd.PreRunE = func(cmd *cobra.Command, args []string) error {
return errors.Join(errs...)
}
}
return c
}
2 changes: 1 addition & 1 deletion cmd/pcert2/create.go → cmd/pcert/create2.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ func getKeyRelativeToCert(certPath string) string {
return keyFilePath
}

func newCreateCmd() *cobra.Command {
func newCreate2Cmd() *cobra.Command {
createCommand := &createCommand{
Out: os.Stdout,
In: os.Stdin,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package cmd
package main

import (
"crypto/x509"
Expand Down
File renamed without changes.
2 changes: 1 addition & 1 deletion cmd/extkeyusage_value.go → cmd/pcert/flag_extkeyusage.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package cmd
package main

import (
"crypto/x509"
Expand Down
2 changes: 1 addition & 1 deletion cmd/keyalg_value.go → cmd/pcert/flag_keyalg.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package cmd
package main

import (
"crypto/x509"
Expand Down
2 changes: 1 addition & 1 deletion cmd/keyusage_value.go → cmd/pcert/flag_keyusage.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package cmd
package main

import (
"crypto/x509"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package cmd
package main

import (
"crypto/x509"
Expand Down
2 changes: 1 addition & 1 deletion cmd/signalgo_value.go → cmd/pcert/flag_signalgo.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package cmd
package main

import (
"crypto/x509"
Expand Down
27 changes: 8 additions & 19 deletions cmd/subject_value.go → cmd/pcert/flag_subject.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package cmd
package main

import (
"crypto/x509/pkix"
Expand All @@ -25,25 +25,20 @@ func (s *subjectValue) String() string {
}

func (s *subjectValue) Set(subject string) error {
return parseSubjectInto(subject, s.value, true)
return parseSubjectInto(subject, s.value)
}

func (s *subjectValue) Merge(subject string) error {
return parseSubjectInto(subject, s.value, false)
}

func parseSubjectInto(subject string, target *pkix.Name, overwrite bool) error {
func parseSubjectInto(subject string, target *pkix.Name) error {
for _, part := range strings.Split(subject, "/") {
if part == "" {
continue
}
parts := strings.SplitN(part, "=", 2)
if len(parts) != 2 {
key, value, ok := strings.Cut(part, "=")
if !ok {
return fmt.Errorf("failed to parse subject. could not split '%s'", part)
}
key := parts[0]
value := parts[1]

// https://datatracker.ietf.org/doc/html/rfc4519#section-2
switch key {
case "C":
target.Country = append(target.Country, value)
Expand All @@ -55,20 +50,14 @@ func parseSubjectInto(subject string, target *pkix.Name, overwrite bool) error {
target.Locality = append(target.Locality, value)
case "P":
target.Province = append(target.Province, value)
case "ST":
target.StreetAddress = append(target.StreetAddress, value)
case "STREET":
target.StreetAddress = append(target.StreetAddress, value)
case "POSTALCODE":
target.PostalCode = append(target.PostalCode, value)
case "SERIALNUMBER":
if overwrite {
target.SerialNumber = value
}
target.SerialNumber = value
case "CN":
if overwrite {
target.CommonName = value
}
target.CommonName = value
default:
return fmt.Errorf("unknown field '%s'", key)
}
Expand Down
2 changes: 1 addition & 1 deletion cmd/time_value.go → cmd/pcert/flag_time.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package cmd
package main

import "time"

Expand Down
2 changes: 1 addition & 1 deletion cmd/uri_value.go → cmd/pcert/flag_uri.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package cmd
package main

import (
"fmt"
Expand Down
2 changes: 1 addition & 1 deletion cmd/flags.go → cmd/pcert/flags.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package cmd
package main

import (
"crypto/x509"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package cmd
package main

import (
"crypto/x509"
Expand Down
28 changes: 3 additions & 25 deletions cmd/pcert/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,8 @@ package main
import (
"fmt"
"os"
"strings"

cmdutil "github.com/dvob/pcert/cmd"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
)

const (
Expand All @@ -23,7 +20,7 @@ var (
)

func main() {
err := newRootCmd().Execute()
err := WithEnv(newRootCmd()).Execute()
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
Expand All @@ -38,32 +35,13 @@ func newRootCmd() *cobra.Command {
All options can also be set as environment variable with the PCERT_
prefix (e.g PCERT_CERT instad of --cert).`,
TraverseChildren: true,
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
var err error
cmd.Flags().VisitAll(func(f *pflag.Flag) {
optName := strings.ToUpper(f.Name)
optName = strings.ReplaceAll(optName, "-", "_")
varName := envVarPrefix + optName
merger, merge := f.Value.(cmdutil.Merger)
if val, ok := os.LookupEnv(varName); ok {
var err2 error
if !f.Changed {
err2 = f.Value.Set(val)
} else if merge {
err2 = merger.Merge(val)
}
if err2 != nil {
err = fmt.Errorf("invalid environment variable %s: %w", varName, err2)
}
}
})
PersistentPreRun: func(cmd *cobra.Command, args []string) {
cmd.SilenceUsage = true
cmd.SilenceErrors = true
return err
},
}
cmd.AddCommand(
newCreateCmd(),
newCreate2Cmd(),
newRequestCmd(),
newSignCmd(),
newShowCmd(),
Expand Down
5 changes: 2 additions & 3 deletions cmd/pcert/request.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import (
"os"

"github.com/dvob/pcert"
cmdutil "github.com/dvob/pcert/cmd"
"github.com/spf13/cobra"
)

Expand Down Expand Up @@ -54,8 +53,8 @@ func newRequestCmd() *cobra.Command {

key.bindFlags(cmd)

cmdutil.BindCertificateRequestFlags(cmd.Flags(), csr)
cmdutil.RegisterCertificateRequestCompletionFuncs(cmd)
BindCertificateRequestFlags(cmd.Flags(), csr)
RegisterCertificateRequestCompletionFuncs(cmd)
cmd.Flags().StringVar(&csrFile, "csr", "", "Output file for the CSR. Defaults to <name>.csr")

return cmd
Expand Down
9 changes: 4 additions & 5 deletions cmd/pcert/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (
"time"

"github.com/dvob/pcert"
cmdutil "github.com/dvob/pcert/cmd"
"github.com/spf13/cobra"
)

Expand All @@ -25,8 +24,8 @@ func (c *cert) bindFlags(cmd *cobra.Command) {
cmd.Flags().Var(newDurationValue(&c.expiry), "expiry", "Validity period of the certificate. If --not-after is set this option has no effect.")
cmd.Flags().StringVar(&c.path, "cert", "", "Output file for the certificate. Defaults to <name>.crt")

cmdutil.BindCertificateFlags(cmd.Flags(), c.cert)
cmdutil.RegisterCertificateCompletionFuncs(cmd)
BindCertificateFlags(cmd.Flags(), c.cert)
RegisterCertificateCompletionFuncs(cmd)
}

// set options on certificate
Expand Down Expand Up @@ -67,8 +66,8 @@ type key struct {
func (k *key) bindFlags(cmd *cobra.Command) {
cmd.Flags().StringVar(&k.path, "key", "", "Output file for the key. Defaults to <name>.key")

cmdutil.BindKeyFlags(cmd.Flags(), &k.opts)
cmdutil.RegisterKeyCompletionFuncs(cmd)
BindKeyFlags(cmd.Flags(), &k.opts)
RegisterKeyCompletionFuncs(cmd)
}

type signPair struct {
Expand Down
8 changes: 5 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
module github.com/dvob/pcert

go 1.18
go 1.22

require (
github.com/spf13/cobra v1.1.1
github.com/spf13/cobra v1.8.1
github.com/spf13/pflag v1.0.5
)

require github.com/inconshreveable/mousetrap v1.0.0 // indirect
require github.com/inconshreveable/mousetrap v1.1.0 // indirect

replace github.com/spf13/cobra => ../cobra
Loading

0 comments on commit 77d0540

Please sign in to comment.