Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Certificate Authority Generating tool to Crux #32

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 94 additions & 0 deletions config/cert_config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package config

import (
"github.com/spf13/pflag"
"github.com/spf13/viper"
"fmt"
"os"
log "github.com/sirupsen/logrus"
)

const (
OrgName = "org-name"
CountryCode = "country-code"
Province = "province"
Locality = "locality"
Address = "address"
PostalCode = "postal-code"

ValidityYears = "validity-years"
ValidityMonths = "validity-months"
ValidityDays = "validity-days"

IsCA = "is-ca"
KeyBits = "key-bits"
FileName = "filename"
)

var v *viper.Viper
var flags *pflag.FlagSet

// CertInitFlags initializes all Certificate generation supported flags.
func CertInitFlags() {
v = viper.New()
flags = pflag.NewFlagSet("flags", pflag.ExitOnError)
flags.StringSlice(OrgName, []string{}, "Organisation name")
flags.StringSlice(CountryCode, []string{}, "Country coder")
flags.StringSlice(Province, []string{}, "Province")
flags.StringSlice(Locality, []string{}, "Locality")
flags.StringSlice(Address, []string{}, "Address")
flags.StringSlice(PostalCode, []string{}, "Postal Code")

flags.String(FileName, "ca", "File name of the Certificates")

flags.Int(ValidityDays, 0, "Validity of the certificate - days")
flags.Int(ValidityMonths, 0, "Validity of the certificate - months")
flags.Int(ValidityYears, 10, "Validity of the certificate - years")
flags.Int(KeyBits, 2048, "The number of bits in the Key")

flags.Bool(IsCA, true, "Is Certificate Authority")

v.BindPFlags(flags) // Binding the flags to test the initial configuration
}

// CertUsage prints usage instructions to the console.
func CertUsage() {
if flags == nil {
log.Fatalf("Certificate flags not initialised ")
}
fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0])
fmt.Fprintf(os.Stderr, " %-25s%s\n", "certificate.conf", "Certificate config file")
flags.PrintDefaults()
}

// CertLoadConfig loads all configuration settings in the provided configPath location.
func CertLoadConfig(configPath string) error {
if v == nil {
log.Fatalf("Certificate configuration not initialised ")
}
v.SetConfigType("hcl")
v.SetConfigFile(configPath)
return v.ReadInConfig()
}

func CertAllSettings() map[string]interface{} {
return v.AllSettings()
}

func CertGetBool(key string) bool {
return v.GetBool(key)
}

func CertGetInt(key string) int {
return v.GetInt(key)
}

func CertGetString(key string) string {
return v.GetString(key)
}

func CertGetStringSlice(key string) []string {
return v.GetStringSlice(key)
}


54 changes: 54 additions & 0 deletions config/cert_config_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package config

import (
"testing"
)

const certConfigFile = "cert_config_testdata.conf"

func TestCertInitFlags(t *testing.T) {
CertInitFlags()
conf := CertAllSettings()
expected := map[string]interface{}{
OrgName: []string{},
CountryCode: []string{},
Province: []string{},
Locality: []string{},
Address: []string{},
PostalCode: []string{},
ValidityYears: 10,
ValidityMonths: 0,
ValidityDays: 0,
IsCA: true,
KeyBits: 2048,
FileName: "ca",
}
verifyConfig(t, conf, expected)
}

func TestCertLoadConfig(t *testing.T) {
err := CertLoadConfig(certConfigFile)

if err != nil {
t.Fatalf("Unable to load config file: %s, %v", certConfigFile, err)
}

conf := CertAllSettings()

expected := map[string]interface{}{
OrgName: "blk-io",
CountryCode: "UK",
Province: "",
Locality: "London",
Address: "",
PostalCode: "",
ValidityYears: 10,
ValidityMonths: 0,
ValidityDays: 0,
IsCA: true,
KeyBits: 2048,
FileName: "tm",
}

verifyConfig(t, conf, expected)
}
16 changes: 16 additions & 0 deletions config/cert_config_testdata.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
org-name = "blk-io"
country-code = "UK"
province = ""
locality = "London"
address = ""
postal-code = ""

validity-years = 10
validity-months = 0
validity-days = 0

is-ca = true

key-bits = 2048

filename = "tm"
5 changes: 3 additions & 2 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ const (
Socket = "socket"

GenerateKeys = "generate-keys"
GenerateCert = "generate-cert"

BerkeleyDb = "berkeleydb"
UseGRPC = "grpc"
Expand All @@ -43,9 +44,10 @@ const (
// InitFlags initializes all supported command line flags.
func InitFlags() {
flag.String(GenerateKeys, "", "Generate a new keypair")
flag.String(GenerateCert, "", "Generate a new Certificate keypair")
flag.String(Url, "", "The URL to advertise to other nodes (reachable by them)")
flag.Int(Port, -1, "The local port to listen on")
flag.String(WorkDir, ".", "The folder to put stuff in (default: .)")
flag.String(WorkDir, ".", "The folder to put stuff in")
flag.String(Socket, "crux.ipc", "IPC socket to create for access to the Private API")
flag.String(OtherNodes, "", "\"Boot nodes\" to connect to to discover the network")
flag.String(PublicKeys, "", "Public keys hosted by this node")
Expand All @@ -63,7 +65,6 @@ func InitFlags() {
flag.Int(GrpcJsonPort, -1, "The local port to listen on for JSON extensions of gRPC")

// storage not currently supported as we use LevelDB

pflag.CommandLine.AddGoFlagSet(flag.CommandLine)
viper.BindPFlags(pflag.CommandLine) // Binding the flags to test the initial configuration
}
Expand Down
2 changes: 2 additions & 0 deletions config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ func verifyConfig(t *testing.T, conf map[string]interface{}, expected map[string
switch actV.(type) { // we cannot use == for equality with []interface{}
case []interface{}:
eq = reflect.DeepEqual(actV, expV)
case []string:
eq = reflect.DeepEqual(actV, expV)
default:
eq = actV == expV
}
Expand Down
15 changes: 14 additions & 1 deletion crux.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ func main() {
}

for _, arg := range args[1:] {
if strings.Contains(arg, ".conf") {
if strings.Contains(arg, ".conf") && !strings.Contains(arg, "generate-cert") {
err := config.LoadConfig(arg)
if err != nil {
log.Fatalln(err)
Expand Down Expand Up @@ -58,6 +58,19 @@ func main() {
log.Printf("Key pair successfully written to %s", keyFile)
os.Exit(0)
}
genCert := config.GetString(config.GenerateCert)
if genCert != "" {
if !strings.Contains(genCert, ".conf"){
log.Errorf("A Configuration file(.conf) must be used to generate Certificates")
os.Exit(1)
} else {
err := enclave.CertGen(genCert)
if err != nil {
config.CertUsage()
}
}
os.Exit(0)
}

workDir := config.GetString(config.WorkDir)
dbStorage := config.GetString(config.Storage)
Expand Down
85 changes: 85 additions & 0 deletions enclave/certificate_gen.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package enclave

import (
"crypto/rand"
"crypto/rsa"
"crypto/x509"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's cool that this is part of the standard library. We may want to split it out into a separate project or binary called crux-utils or similar. But I'd be interested to get your views.

"crypto/x509/pkix"
"encoding/pem"
log "github.com/sirupsen/logrus"
"github.com/blk-io/crux/config"
"math/big"
"os"
"time"
"fmt"
)

func CertGen(configFile string) error {
config.CertInitFlags()
err := config.CertLoadConfig(configFile)
if err != nil {
log.Errorf("Loading from config failed with %v", err)
return err
}

serialNo, err := rand.Int(rand.Reader, big.NewInt(32))
if err != nil {
log.Errorf("Generating random number failed with %v", err)
return err
}
certificateParam := &x509.Certificate{
SerialNumber: serialNo,
Subject: pkix.Name{
Organization: config.CertGetStringSlice(config.OrgName),
Country: config.CertGetStringSlice(config.CountryCode),
Province: config.CertGetStringSlice(config.Province),
Locality: config.CertGetStringSlice(config.Locality),
StreetAddress: config.CertGetStringSlice(config.Address),
PostalCode: config.CertGetStringSlice(config.PostalCode),
},
NotBefore: time.Now(),
NotAfter: time.Now().AddDate(
config.CertGetInt(config.ValidityYears),
config.CertGetInt(config.ValidityMonths),
config.CertGetInt(config.ValidityDays)),
IsCA: config.CertGetBool(config.IsCA),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it's not a certificate authority, could we do signing on behalf of a local authority who's keys are on the same host? I appreciate this isn't exactly a production use case, but could be good to explore

ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
BasicConstraintsValid: true,
}

privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What flexibility is there for supporting other key types?

if err != nil {
log.Errorf("Generating private Key failed with %v", err)
return err
}

publicKey := &privateKey.PublicKey
certificate, err := x509.CreateCertificate(rand.Reader, certificateParam, certificateParam, publicKey, privateKey)
if err != nil {
log.Errorf("Create certificate failed %v", err)
return err
}

// Public key
certOut, err := os.Create(fmt.Sprintf("%s.crt", config.CertGetString(config.FileName)))
if err != nil {
log.Errorf("Writing public key to file failed with %v", err)
return err
}
pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: certificate})
certOut.Close()

// Private key
keyOut, err := os.OpenFile (
fmt.Sprintf("%s.key", config.CertGetString(config.FileName)),
os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
if err != nil {
log.Errorf("Writing private key to file failed with %v", err)
return err
}
pem.Encode(keyOut, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(privateKey)})
keyOut.Close()

return nil
}