-
Notifications
You must be signed in to change notification settings - Fork 28
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
base: master
Are you sure you want to change the base?
Changes from all commits
2fcb2bb
085b18b
902e3bc
4f0e180
542024e
ac49cc8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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) | ||
} | ||
|
||
|
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) | ||
} |
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" |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
package enclave | ||
|
||
import ( | ||
"crypto/rand" | ||
"crypto/rsa" | ||
"crypto/x509" | ||
"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), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
} |
There was a problem hiding this comment.
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.