redesign cli
This is a major rebuilding of the CLI. The library part is split out into pkilib and the cli handles only the communication with the user, I/O and the library. The API will still look the same, but the code should be much better to grasp. Instead of repeating everything, more will be grouped together and reused.
This commit is contained in:
parent
2f9126dc6a
commit
16eb14db9f
|
@ -1,118 +0,0 @@
|
|||
package main
|
||||
|
||||
// create a sign request needed for the final certificate
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/rand"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"flag"
|
||||
"io"
|
||||
"net"
|
||||
"os"
|
||||
"reflect"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
type (
|
||||
SignFlags struct {
|
||||
PrivateKeyPath string // path to the private key
|
||||
Output string // path where to store the CSR
|
||||
BaseAttributes pkix.Name
|
||||
DNSNames []string // alternative names to the BaseAttributes.CommonName
|
||||
IPAddresses []net.IP // alternative IP addresses
|
||||
|
||||
private_key crypto.Signer
|
||||
output_stream io.Writer // the output stream for the CSR
|
||||
}
|
||||
)
|
||||
|
||||
var (
|
||||
COMMA_SPLIT = regexp.MustCompile(`,[[:space:]]?`)
|
||||
)
|
||||
|
||||
// create a sign request with a private key
|
||||
func create_sign_request() {
|
||||
flags := parse_sign_flags()
|
||||
flags.private_key = load_private_key(flags.PrivateKeyPath)
|
||||
|
||||
stream, err := open_output_stream(flags.Output)
|
||||
if err != nil {
|
||||
crash_with_help(2, fmt.Sprintf("Error when creating file %s: %s", flags.Output, err))
|
||||
}
|
||||
defer stream.Close()
|
||||
flags.output_stream = stream
|
||||
|
||||
if err = create_csr(flags); err != nil {
|
||||
fmt.Fprintln(os.Stderr, "Error when generating CSR: ", err)
|
||||
os.Exit(3)
|
||||
}
|
||||
}
|
||||
|
||||
// parse the flags to create a certificate sign request
|
||||
func parse_sign_flags() SignFlags {
|
||||
dns_names := "" // string to hold the alternative names
|
||||
ips := "" // string to hold the alternative ips
|
||||
var container struct {
|
||||
Country, Organization, OrganizationalUnit, Locality, Province,
|
||||
StreetAddress, PostalCode string
|
||||
}
|
||||
|
||||
flags := SignFlags{}
|
||||
fs := flag.NewFlagSet("create-cert-sign", flag.ExitOnError)
|
||||
fs.StringVar(&flags.PrivateKeyPath, "private-key", "", "path to the private key file")
|
||||
fs.StringVar(&flags.Output, "output", "STDOUT", "path where the generated csr should be stored")
|
||||
|
||||
flags.BaseAttributes = pkix.Name{}
|
||||
fs.StringVar(&flags.BaseAttributes.CommonName, "common-name", "", "the name of the resource")
|
||||
fs.StringVar(&flags.BaseAttributes.SerialNumber, "serial", "1", "serial number for the request")
|
||||
fs.StringVar(&dns_names, "names", "", "alternative names (comma separated)")
|
||||
fs.StringVar(&ips, "ips", "", "alternative IPs (comma separated)")
|
||||
fs.StringVar(&container.Country, "country", "", "country of the organization")
|
||||
fs.StringVar(&container.Organization, "organization", "", "the name of the organization")
|
||||
fs.StringVar(&container.OrganizationalUnit, "org-unit", "", "the organizational unit")
|
||||
fs.StringVar(&container.Locality, "city", "", "the city or locality")
|
||||
fs.StringVar(&container.Province, "province", "", "the province")
|
||||
fs.StringVar(&container.StreetAddress, "street", "", "the street address for the cert")
|
||||
fs.StringVar(&container.PostalCode, "postal-code", "", "the postal code of the city")
|
||||
fs.Parse(os.Args[2:])
|
||||
|
||||
// convert array flags to config structs
|
||||
if dns_names != "" {
|
||||
flags.DNSNames = COMMA_SPLIT.Split(dns_names, -1)
|
||||
}
|
||||
if ips != "" {
|
||||
tmp_ips := COMMA_SPLIT.Split(ips, -1)
|
||||
for _, sip := range tmp_ips {
|
||||
flags.IPAddresses = append(flags.IPAddresses, net.ParseIP(sip))
|
||||
}
|
||||
}
|
||||
|
||||
container_type := reflect.ValueOf(container)
|
||||
config_type := reflect.ValueOf(&flags.BaseAttributes).Elem()
|
||||
for i := 0; i < container_type.NumField(); i++ {
|
||||
field := container_type.Field(i)
|
||||
new_field := config_type.FieldByName(container_type.Type().Field(i).Name)
|
||||
new_field.Set(reflect.ValueOf(COMMA_SPLIT.Split(field.String(), -1)))
|
||||
}
|
||||
|
||||
return flags
|
||||
}
|
||||
|
||||
// generate the csr and print into flags.output_stream
|
||||
func create_csr(flags SignFlags) (error) {
|
||||
csr_template := &x509.CertificateRequest{
|
||||
Subject: flags.BaseAttributes,
|
||||
DNSNames: flags.DNSNames,
|
||||
IPAddresses: flags.IPAddresses,
|
||||
}
|
||||
csr_raw, err := x509.CreateCertificateRequest(rand.Reader, csr_template, flags.private_key)
|
||||
if err != nil { return err }
|
||||
|
||||
block := &pem.Block{Type: TypeLabelCSR, Bytes: csr_raw}
|
||||
pem.Encode(flags.output_stream, block)
|
||||
return nil
|
||||
}
|
|
@ -1,84 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type CSRTest struct {
|
||||
ShouldBe []string
|
||||
Set func(*SignFlags)
|
||||
Fetch func(*x509.CertificateRequest) []string
|
||||
}
|
||||
|
||||
const (
|
||||
RAW_PRIVATE_KEY = `-----BEGIN EC PRIVATE KEY-----
|
||||
MIHbAgEBBEFkAEFc5264Yo7Xo+yj3ZwaqdffTphGT3/8Q+pvi4ULmXaFiGoTkR5X
|
||||
lKnlRUEp0I4Ra9U7GjLDtFLwTaLzdXuUT6AHBgUrgQQAI6GBiQOBhgAEAdW0usq0
|
||||
zEzvhR0u5ZSbOXRzg+TbICZGfOLy9KpKfz6I6suFOAO7f3fwDNOqMfyYUhtenMz7
|
||||
T/BKArg+v58UWHrwAb/UeI4l+OMOoMHYtNNO4nAjTdyY8yFSFY5syzKEYIBzUoLM
|
||||
VSfuxBk5ZS2J478X1Vxacq03keDeAY43Oc80XBih
|
||||
-----END EC PRIVATE KEY-----`
|
||||
)
|
||||
|
||||
func SetupTest() (*SignFlags, *bytes.Buffer) {
|
||||
p, _ := pem.Decode([]byte(RAW_PRIVATE_KEY))
|
||||
buf := bytes.NewBuffer(make([]byte, 0))
|
||||
|
||||
flags := &SignFlags{
|
||||
private_key: load_private_key_ecdsa(p),
|
||||
output_stream: buf,
|
||||
}
|
||||
return flags, buf
|
||||
}
|
||||
|
||||
func TestCSRGeneration(t *testing.T) {
|
||||
tests := []CSRTest {
|
||||
{
|
||||
[]string{"foo"},
|
||||
func(f *SignFlags) { f.BaseAttributes.CommonName = "foo" },
|
||||
func(c *x509.CertificateRequest) []string { return []string{c.Subject.CommonName} },
|
||||
}, {
|
||||
[]string{"foo.com", "bar.com", "baz.com"},
|
||||
func(f *SignFlags) { f.DNSNames = []string{ "foo.com", "bar.com", "baz.com" }},
|
||||
func(c *x509.CertificateRequest) []string { return c.DNSNames },
|
||||
},
|
||||
{
|
||||
[]string{"127.0.0.1", "192.168.0.1"},
|
||||
func(f *SignFlags) { f.IPAddresses = []net.IP{net.ParseIP("127.0.0.1"), net.ParseIP("192.168.0.1") }},
|
||||
func(c *x509.CertificateRequest) []string {
|
||||
res := make([]string, 0)
|
||||
for _, ip := range c.IPAddresses {
|
||||
res = append(res, ip.String())
|
||||
}
|
||||
return res
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
flags, io := SetupTest()
|
||||
test.Set(flags)
|
||||
|
||||
create_csr(*flags)
|
||||
res, _ := ioutil.ReadAll(io)
|
||||
raw, _ := pem.Decode(res)
|
||||
|
||||
csr, _ := x509.ParseCertificateRequest(raw.Bytes)
|
||||
if !diff(test.ShouldBe, test.Fetch(csr)) {
|
||||
t.Logf("Expected: %v\nbut got: %v", test.ShouldBe, test.Fetch(csr))
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func diff(a, b []string) bool {
|
||||
if len(a) != len(b) { return false }
|
||||
for i, e := range a {
|
||||
if e != b[i] { return false }
|
||||
}
|
||||
return true
|
||||
}
|
|
@ -0,0 +1,168 @@
|
|||
package main
|
||||
|
||||
// This file handles the complete parameter assignment, as some parameters are
|
||||
// often used by multiple functions.
|
||||
|
||||
import (
|
||||
"crypto/elliptic"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/gibheer/pkilib"
|
||||
)
|
||||
|
||||
const (
|
||||
RsaLowerLength = 2048
|
||||
RsaUpperLength = 16384
|
||||
)
|
||||
|
||||
var (
|
||||
EcdsaCurves = []int{224, 256, 384, 521}
|
||||
)
|
||||
|
||||
type (
|
||||
// holds all certificate related flags, which need parsing afterwards
|
||||
certFlagsContainer struct {
|
||||
serialNumber int // the serial number for the cert
|
||||
commonName string // the common name used in the cert
|
||||
dnsNames string // all alternative names in the certificate (comma separated list)
|
||||
ipAddresses string // all IP addresses in the certificate (comma separated list)
|
||||
country string // the country names which should end up in the cert (comma separated list)
|
||||
organization string // the organization names (comma separated list)
|
||||
organizationalUnit string // the organizational units (comma separated list)
|
||||
locality string // the city or locality (comma separated list)
|
||||
province string // the province name (comma separated list)
|
||||
streetAddress string // the street addresses of the organization (comma separated list)
|
||||
postalCode string // the postal codes of the locality
|
||||
}
|
||||
|
||||
// a container go gather all incoming flags for further processing
|
||||
paramContainer struct {
|
||||
outputPath string // path to output whatever is generated
|
||||
cryptType string // type of something (private key)
|
||||
length int // the length of something (private key)
|
||||
privateKeyPath string // path to the private key
|
||||
publicKeyPath string // path to the public key
|
||||
signRequestPath string // path to the certificate sign request
|
||||
certificateFlags *certFlagsContainer // container for certificate related flags
|
||||
}
|
||||
|
||||
// a container for the refined flags
|
||||
flagSet struct {
|
||||
PrivateKey pkilib.PrivateKey
|
||||
Output io.WriteCloser
|
||||
|
||||
// private key specific stuff
|
||||
PrivateKeyGenerationFlags privateKeyGenerationFlags
|
||||
}
|
||||
|
||||
privateKeyGenerationFlags struct {
|
||||
Type string // type of the private key (rsa, ecdsa)
|
||||
Curve elliptic.Curve // curve for ecdsa
|
||||
Size int // bitsize for rsa
|
||||
}
|
||||
|
||||
Flags struct {
|
||||
flagset *flag.FlagSet // the flagset reference for printing the help
|
||||
flag_container *paramContainer
|
||||
Flags *flagSet // the end result of the flag setting
|
||||
|
||||
check_list []flagCheck // list of all checks
|
||||
}
|
||||
|
||||
flagCheck func()(error)
|
||||
)
|
||||
|
||||
// create a new flag handler with the name of the subfunction
|
||||
func NewFlags(method_name string) *Flags {
|
||||
return &Flags{
|
||||
Flags: &flagSet{},
|
||||
flagset: flag.NewFlagSet(method_name, flag.ExitOnError),
|
||||
check_list: make([]flagCheck, 0),
|
||||
flag_container: ¶mContainer{},
|
||||
}
|
||||
}
|
||||
|
||||
// check all parameters for validity
|
||||
func (f *Flags) Parse(options []string) error {
|
||||
f.flagset.Parse(options)
|
||||
for _, check := range f.check_list {
|
||||
// TODO handle error in a betetr way (output specific help, not command help)
|
||||
if err := check(); err != nil { return err }
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// add the private key option to the requested flags
|
||||
func (f *Flags) AddPrivateKey() {
|
||||
f.check_list = append(f.check_list, f.parsePrivateKey)
|
||||
f.flagset.StringVar(&f.flag_container.privateKeyPath, "private-key", "", "path to the private key")
|
||||
}
|
||||
|
||||
// check the private key flag and load the private key
|
||||
func (f *Flags) parsePrivateKey() error {
|
||||
// check permissions of private key file
|
||||
info, err := os.Stat(f.flag_container.privateKeyPath)
|
||||
if err != nil { return fmt.Errorf("Error reading private key: %s", err) }
|
||||
if info.Mode().Perm().String()[4:] != "------" {
|
||||
return fmt.Errorf("private key file modifyable by others!")
|
||||
}
|
||||
|
||||
pk, err := ReadPrivateKeyFile(f.flag_container.privateKeyPath)
|
||||
if err != nil { return fmt.Errorf("Error reading private key: %s", err) }
|
||||
f.Flags.PrivateKey = pk
|
||||
return nil
|
||||
}
|
||||
|
||||
// add the output parameter to the checklist
|
||||
func (f *Flags) AddOutput() {
|
||||
f.check_list = append(f.check_list, f.parseOutput)
|
||||
f.flagset.StringVar(&f.flag_container.outputPath, "output", "STDOUT", "path to the output or STDOUT")
|
||||
}
|
||||
|
||||
// parse the output parameter and open the file handle
|
||||
func (f *Flags) parseOutput() error {
|
||||
if f.flag_container.outputPath == "STDOUT" {
|
||||
f.Flags.Output = os.Stdout
|
||||
return nil
|
||||
}
|
||||
var err error
|
||||
f.Flags.Output, err = os.OpenFile(
|
||||
f.flag_container.outputPath,
|
||||
os.O_WRONLY | os.O_APPEND | os.O_CREATE, // do not kill users files!
|
||||
0600,
|
||||
)
|
||||
if err != nil { return err }
|
||||
return nil
|
||||
}
|
||||
|
||||
// This function adds the private key generation flags.
|
||||
func (f *Flags) AddPrivateKeyGenerationFlags() {
|
||||
f.check_list = append(f.check_list, f.parsePrivateKeyGenerationFlags)
|
||||
f.flagset.StringVar(&f.flag_container.cryptType, "type", "ecdsa", "the type of the private key (ecdsa, rsa)")
|
||||
f.flagset.IntVar(
|
||||
&f.flag_container.length,
|
||||
"length", 521,
|
||||
fmt.Sprintf("%d - %d for rsa; %v for ecdsa", RsaLowerLength, RsaUpperLength, EcdsaCurves),
|
||||
)
|
||||
}
|
||||
|
||||
func (f *Flags) parsePrivateKeyGenerationFlags() error {
|
||||
pk_type := f.flag_container.cryptType
|
||||
f.Flags.PrivateKeyGenerationFlags.Type = pk_type
|
||||
switch pk_type {
|
||||
case "ecdsa":
|
||||
switch f.flag_container.length {
|
||||
case 224: f.Flags.PrivateKeyGenerationFlags.Curve = elliptic.P224()
|
||||
case 256: f.Flags.PrivateKeyGenerationFlags.Curve = elliptic.P256()
|
||||
case 384: f.Flags.PrivateKeyGenerationFlags.Curve = elliptic.P384()
|
||||
case 521: f.Flags.PrivateKeyGenerationFlags.Curve = elliptic.P521()
|
||||
default: return fmt.Errorf("Curve %d unknown!", f.flag_container.length)
|
||||
}
|
||||
case "rsa": f.Flags.PrivateKeyGenerationFlags.Size = f.flag_container.length
|
||||
default: return fmt.Errorf("Type %s is unknown!", pk_type)
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
package main
|
||||
|
||||
// handle all io and de/encoding of data
|
||||
|
||||
import (
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrBlockNotFound = errors.New("block not found")
|
||||
)
|
||||
|
||||
// load a pem section from a file
|
||||
func readSectionFromFile(path, btype string) ([]byte, error) {
|
||||
raw, err := readFile(path)
|
||||
if err != nil { return raw, err }
|
||||
|
||||
return decodeSection(raw, btype)
|
||||
}
|
||||
|
||||
// read a file completely and report possible errors
|
||||
func readFile(path string) ([]byte, error) {
|
||||
raw, err := ioutil.ReadFile(path)
|
||||
if err != nil { return EmptyByteArray, err }
|
||||
return raw, nil
|
||||
}
|
||||
|
||||
// decode a pem encoded file and search for the specified section
|
||||
func decodeSection(data []byte, btype string) ([]byte, error) {
|
||||
rest := data
|
||||
for len(rest) > 0 {
|
||||
var block *pem.Block
|
||||
block, rest = pem.Decode(rest)
|
||||
if block.Type == btype {
|
||||
return block.Bytes, nil
|
||||
}
|
||||
}
|
||||
return EmptyByteArray, ErrBlockNotFound
|
||||
}
|
91
main.go
91
main.go
|
@ -2,69 +2,74 @@ package main
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
const (
|
||||
RsaLowerLength = 2048
|
||||
RsaUpperLength = 4096
|
||||
TypeLabelRSA = "RSA PRIVATE KEY"
|
||||
TypeLabelECDSA = "EC PRIVATE KEY"
|
||||
TypeLabelCSR = "CERTIFICATE REQUEST"
|
||||
TypeLabelPubKey = "PUBLIC KEY"
|
||||
"github.com/gibheer/pkilib"
|
||||
)
|
||||
|
||||
var (
|
||||
EcdsaLength = []int{224, 256, 384, 521}
|
||||
EmptyByteArray = make([]byte, 0)
|
||||
)
|
||||
|
||||
//const (
|
||||
// RsaLowerLength = 2048
|
||||
// RsaUpperLength = 4096
|
||||
// TypeLabelRSA = "RSA PRIVATE KEY"
|
||||
// TypeLabelECDSA = "EC PRIVATE KEY"
|
||||
// TypeLabelCSR = "CERTIFICATE REQUEST"
|
||||
// TypeLabelPubKey = "PUBLIC KEY"
|
||||
//)
|
||||
//
|
||||
//var (
|
||||
// EcdsaLength = []int{224, 256, 384, 521}
|
||||
//)
|
||||
//
|
||||
func main() {
|
||||
if len(os.Args) == 1 {
|
||||
crash_with_help(1, "No module selected!")
|
||||
}
|
||||
switch os.Args[1] {
|
||||
case "create-private": create_private_key()
|
||||
case "create-cert-sign": create_sign_request()
|
||||
case "create-public": create_public_key()
|
||||
case "help": print_modules()
|
||||
case "info": info_on_file()
|
||||
case "sign-request": sign_request()
|
||||
case "sign-input": sign_input()
|
||||
case "verify-signature": verify_signature()
|
||||
// case "create-cert-sign": create_sign_request()
|
||||
// case "help": print_modules()
|
||||
// case "info": info_on_file()
|
||||
// case "sign-request": sign_request()
|
||||
// case "sign-input": sign_input()
|
||||
// case "verify-signature": verify_signature()
|
||||
default: crash_with_help(1, "Command not supported!")
|
||||
}
|
||||
}
|
||||
|
||||
// get information on file (private key, sign request, certificate, ...)
|
||||
func info_on_file() {}
|
||||
// sign a certificate request to create a new certificate
|
||||
func sign_request() {}
|
||||
// create a private key
|
||||
func create_private_key() {
|
||||
fs := NewFlags("create-private")
|
||||
fs.AddOutput()
|
||||
fs.AddPrivateKeyGenerationFlags()
|
||||
err := fs.Parse(program_args())
|
||||
if err != nil { crash_with_help(1, fmt.Sprintf("%s", err)) }
|
||||
|
||||
// open stream for given path
|
||||
func open_output_stream(path string) (io.WriteCloser, error) {
|
||||
switch path {
|
||||
case "STDOUT": return os.Stdout, nil
|
||||
case "STDERR": return os.Stderr, nil
|
||||
default: return open_stream(path, os.O_WRONLY | os.O_CREATE | os.O_TRUNC)
|
||||
var pk pkilib.Pemmer
|
||||
switch fs.Flags.PrivateKeyGenerationFlags.Type {
|
||||
case "ecdsa": pk, err = pkilib.NewPrivateKeyEcdsa(fs.Flags.PrivateKeyGenerationFlags.Curve)
|
||||
case "rsa": pk, err = pkilib.NewPrivateKeyRsa(fs.Flags.PrivateKeyGenerationFlags.Size)
|
||||
}
|
||||
if err != nil { crash_with_help(2, fmt.Sprintf("%s", err)) }
|
||||
marsh_pem, err := pk.MarshalPem()
|
||||
if err != nil { crash_with_help(2, fmt.Sprintf("%s", err)) }
|
||||
_, err = marsh_pem.WriteTo(fs.Flags.Output)
|
||||
if err != nil { crash_with_help(2, fmt.Sprintf("%s", err)) }
|
||||
}
|
||||
|
||||
func open_input_stream(path string) (io.ReadCloser, error) {
|
||||
switch path {
|
||||
case "STDIN": return os.Stdin, nil
|
||||
default: return open_stream(path, os.O_RDONLY)
|
||||
}
|
||||
}
|
||||
// create a public key derived from a private key
|
||||
func create_public_key() {
|
||||
fs := NewFlags("create-public")
|
||||
fs.AddPrivateKey()
|
||||
err := fs.Parse(program_args())
|
||||
if err != nil { crash_with_help(1, fmt.Sprintf("%s", err)) }
|
||||
|
||||
func open_stream(path string, flags int) (io.ReadWriteCloser, error) {
|
||||
var err error
|
||||
output_stream, err := os.OpenFile(path, flags, 0600)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return output_stream, nil
|
||||
fmt.Println(fs.Flags.PrivateKey.Public())
|
||||
}
|
||||
|
||||
// print the module help
|
||||
|
@ -76,15 +81,21 @@ where 'command' is one of:
|
|||
create-cert-sign create a new certificate sign request
|
||||
help show this help
|
||||
info get info on a file
|
||||
sign sign a certificate request
|
||||
sign-request sign a certificate request
|
||||
sign-input sign a message with a private key
|
||||
verify-signature verify a signature
|
||||
`, filepath.Base(os.Args[0]))
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
// crash and provide a helpful message
|
||||
func crash_with_help(code int, message string) {
|
||||
fmt.Fprintln(os.Stderr, message)
|
||||
print_modules()
|
||||
os.Exit(code)
|
||||
}
|
||||
|
||||
// return the arguments to the program
|
||||
func program_args() []string {
|
||||
return os.Args[2:]
|
||||
}
|
||||
|
|
156
private_key.go
156
private_key.go
|
@ -1,145 +1,33 @@
|
|||
package main
|
||||
|
||||
// generate an ecdsa or rsa private key
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/elliptic"
|
||||
"crypto/ecdsa"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"errors"
|
||||
"github.com/gibheer/pkilib"
|
||||
)
|
||||
|
||||
type (
|
||||
CreateFlags struct {
|
||||
CryptType string // rsa or ecdsa
|
||||
CryptLength int // the bit length
|
||||
Output string // a path or stream to output the private key to
|
||||
|
||||
output_stream io.WriteCloser // the actual stream to the output
|
||||
}
|
||||
const (
|
||||
TypeLabelRSA = "RSA PRIVATE KEY"
|
||||
TypeLabelECDSA = "EC PRIVATE KEY"
|
||||
)
|
||||
|
||||
// create a new private key
|
||||
func create_private_key() {
|
||||
flags := parse_create_flags()
|
||||
var (
|
||||
ErrNoPKFound = errors.New("no private key found")
|
||||
)
|
||||
|
||||
var err error
|
||||
flags.output_stream, err = open_output_stream(flags.Output)
|
||||
if err != nil {
|
||||
crash_with_help(2, fmt.Sprintf("Error when creating file %s: %s", flags.Output, err))
|
||||
// Read the private key from the path and try to figure out which type of key it
|
||||
// might be.
|
||||
func ReadPrivateKeyFile(path string) (pkilib.PrivateKey, error) {
|
||||
raw_pk, err := readSectionFromFile(path, TypeLabelECDSA)
|
||||
if err == nil {
|
||||
pk, err := pkilib.LoadPrivateKeyEcdsa(raw_pk)
|
||||
if err != nil { return nil, err }
|
||||
return pk, nil
|
||||
}
|
||||
defer flags.output_stream.Close()
|
||||
|
||||
switch flags.CryptType {
|
||||
case "rsa": create_private_key_rsa(flags)
|
||||
case "ecdsa": create_private_key_ecdsa(flags)
|
||||
default: crash_with_help(2, fmt.Sprintf("%s not supported!", flags.CryptType))
|
||||
raw_pk, err = readSectionFromFile(path, TypeLabelRSA)
|
||||
if err == nil {
|
||||
pk, err := pkilib.LoadPrivateKeyRsa(raw_pk)
|
||||
if err != nil { return nil, err }
|
||||
return pk, nil
|
||||
}
|
||||
}
|
||||
|
||||
// generate a rsa private key
|
||||
func create_private_key_rsa(flags CreateFlags) {
|
||||
if flags.CryptLength < 2048 {
|
||||
crash_with_help(2, "Length is smaller than 2048!")
|
||||
}
|
||||
|
||||
priv, err := rsa.GenerateKey( rand.Reader, flags.CryptLength)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, "Error: ", err)
|
||||
os.Exit(3)
|
||||
}
|
||||
marshal := x509.MarshalPKCS1PrivateKey(priv)
|
||||
block := &pem.Block{Type: TypeLabelRSA, Bytes: marshal}
|
||||
pem.Encode(flags.output_stream, block)
|
||||
}
|
||||
|
||||
// generate a ecdsa private key
|
||||
func create_private_key_ecdsa(flags CreateFlags) {
|
||||
var curve elliptic.Curve
|
||||
switch flags.CryptLength {
|
||||
case 224: curve = elliptic.P224()
|
||||
case 256: curve = elliptic.P256()
|
||||
case 384: curve = elliptic.P384()
|
||||
case 521: curve = elliptic.P521()
|
||||
default: crash_with_help(2, "Unsupported crypt length!")
|
||||
}
|
||||
|
||||
priv, err := ecdsa.GenerateKey(curve, rand.Reader)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, "Error: ", err)
|
||||
os.Exit(3)
|
||||
}
|
||||
marshal, err := x509.MarshalECPrivateKey(priv)
|
||||
if err != nil {
|
||||
crash_with_help(2, fmt.Sprintf("Problems marshalling the private key: %s", err))
|
||||
}
|
||||
block := &pem.Block{Type: TypeLabelECDSA, Bytes: marshal}
|
||||
pem.Encode(flags.output_stream, block)
|
||||
}
|
||||
|
||||
// parse the flags to create a private key
|
||||
func parse_create_flags() CreateFlags {
|
||||
flags := CreateFlags{}
|
||||
fs := flag.NewFlagSet("create-private", flag.ExitOnError)
|
||||
fs.StringVar(&flags.CryptType, "type", "ecdsa", "which type to use to encrypt key (rsa, ecdsa)")
|
||||
fs.IntVar(&flags.CryptLength, "length", 521, fmt.Sprintf(
|
||||
"%i - %i for rsa; %v for ecdsa", RsaLowerLength, RsaUpperLength, EcdsaLength,))
|
||||
fs.StringVar(&flags.Output, "output", "STDOUT", "filename to store the private key")
|
||||
fs.Parse(os.Args[2:])
|
||||
|
||||
return flags
|
||||
}
|
||||
|
||||
// load the private key stored at `path`
|
||||
func load_private_key(path string) crypto.Signer {
|
||||
if path == "" {
|
||||
crash_with_help(2, "No path to private key supplied!")
|
||||
}
|
||||
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
crash_with_help(3, fmt.Sprintf("Error when opening private key: %s", err))
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
data, err := ioutil.ReadAll(file)
|
||||
if err != nil {
|
||||
crash_with_help(3, fmt.Sprintf("Error when reading private key: %s", err))
|
||||
}
|
||||
|
||||
block, _ := pem.Decode(data)
|
||||
if block.Type == TypeLabelRSA {
|
||||
return load_private_key_rsa(block)
|
||||
} else if block.Type == TypeLabelECDSA {
|
||||
return load_private_key_ecdsa(block)
|
||||
} else {
|
||||
crash_with_help(2, "No valid private key file! Only RSA and ECDSA keys are allowed!")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// parse rsa private key
|
||||
func load_private_key_rsa(block *pem.Block) crypto.Signer {
|
||||
key, err := x509.ParsePKCS1PrivateKey(block.Bytes)
|
||||
if err != nil {
|
||||
crash_with_help(3, fmt.Sprintf("Error parsing private key: %s", err))
|
||||
}
|
||||
return key
|
||||
}
|
||||
|
||||
// parse ecdsa private key
|
||||
func load_private_key_ecdsa(block *pem.Block) crypto.Signer {
|
||||
key, err := x509.ParseECPrivateKey(block.Bytes)
|
||||
if err != nil {
|
||||
crash_with_help(3, fmt.Sprintf("Error parsing private key: %s", err))
|
||||
}
|
||||
return key
|
||||
return nil, ErrNoPKFound
|
||||
}
|
||||
|
|
|
@ -1,50 +0,0 @@
|
|||
package main
|
||||
|
||||
// create a public key from a private key
|
||||
|
||||
import (
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
|
||||
type (
|
||||
PublicKeyFlags struct {
|
||||
PrivateKeyPath string
|
||||
Output string
|
||||
|
||||
output_stream io.WriteCloser // the actual stream to the output
|
||||
}
|
||||
)
|
||||
|
||||
func create_public_key() {
|
||||
var err error
|
||||
flags := parse_public_key_flags()
|
||||
flags.output_stream, err = open_output_stream(flags.Output)
|
||||
if err != nil {
|
||||
crash_with_help(2, fmt.Sprintf("Error when creating file %s: %s", flags.Output, err))
|
||||
}
|
||||
defer flags.output_stream.Close()
|
||||
|
||||
priv_key := load_private_key(flags.PrivateKeyPath)
|
||||
marshal, err := x509.MarshalPKIXPublicKey(priv_key.Public())
|
||||
if err != nil {
|
||||
crash_with_help(2, fmt.Sprintf("Problems marshalling the public key: %s", err))
|
||||
}
|
||||
|
||||
block := &pem.Block{Type: TypeLabelPubKey, Bytes: marshal}
|
||||
pem.Encode(flags.output_stream, block)
|
||||
}
|
||||
|
||||
func parse_public_key_flags() PublicKeyFlags {
|
||||
flags := PublicKeyFlags{}
|
||||
fs := flag.NewFlagSet("create-public", flag.ExitOnError)
|
||||
fs.StringVar(&flags.PrivateKeyPath, "private-key", "", "path to the private key file")
|
||||
fs.StringVar(&flags.Output, "output", "STDOUT", "path where the generated public key should be stored")
|
||||
fs.Parse(os.Args[2:])
|
||||
|
||||
return flags
|
||||
}
|
103
sign_input.go
103
sign_input.go
|
@ -1,103 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
// "crypto/ecdsa"
|
||||
// "crypto/rsa"
|
||||
)
|
||||
|
||||
type (
|
||||
SignInputFlags struct {
|
||||
Message string // the message to sign
|
||||
MessageStream string // the message stream to sign
|
||||
PrivateKeyPath string // path to the private key
|
||||
Output string // a path or stream to output the private key to
|
||||
Format string // the format of the output
|
||||
|
||||
private_key crypto.Signer
|
||||
output_stream io.Writer // the output stream for the CSR
|
||||
input_stream io.Reader // the input stream to read the message from
|
||||
}
|
||||
)
|
||||
|
||||
func sign_input() {
|
||||
flags := parse_sign_input_flags()
|
||||
if flags.Message != "" && flags.MessageStream != "" {
|
||||
crash_with_help(2, "Only message or message file can be signed!")
|
||||
}
|
||||
flags.private_key = load_private_key(flags.PrivateKeyPath)
|
||||
|
||||
output_stream, err := open_output_stream(flags.Output)
|
||||
if err != nil {
|
||||
crash_with_help(2, fmt.Sprintf("Error when creating file %s: %s", flags.Output, err))
|
||||
}
|
||||
flags.output_stream = output_stream
|
||||
defer output_stream.Close()
|
||||
|
||||
if flags.MessageStream != "" {
|
||||
input_stream, err := open_input_stream(flags.MessageStream)
|
||||
if err != nil {
|
||||
crash_with_help(2, fmt.Sprintf("Error when opening stream %s: %s", flags.MessageStream, err))
|
||||
}
|
||||
flags.input_stream = input_stream
|
||||
defer input_stream.Close()
|
||||
}
|
||||
|
||||
if err := create_signature(flags); err != nil {
|
||||
fmt.Fprintln(os.Stderr, "Error when creating signature", err)
|
||||
os.Exit(3)
|
||||
}
|
||||
}
|
||||
|
||||
func parse_sign_input_flags() SignInputFlags {
|
||||
flags := SignInputFlags{}
|
||||
fs := flag.NewFlagSet("sign-input", flag.ExitOnError)
|
||||
fs.StringVar(&flags.PrivateKeyPath, "private-key", "", "path to the private key file")
|
||||
fs.StringVar(&flags.Output, "output", "STDOUT", "path where the generated signature should be stored (STDOUT, STDERR, file)")
|
||||
fs.StringVar(&flags.Message, "message", "", "the message to sign")
|
||||
fs.StringVar(&flags.MessageStream, "message-stream", "STDIN", "the path to the stream to sign (file, STDIN)")
|
||||
fs.StringVar(&flags.Format, "format", "base64", "the output format (binary, base64)")
|
||||
fs.Parse(os.Args[2:])
|
||||
|
||||
return flags
|
||||
}
|
||||
|
||||
func create_signature(flags SignInputFlags) error {
|
||||
var message []byte
|
||||
var err error
|
||||
|
||||
if flags.MessageStream != "" {
|
||||
message, err = ioutil.ReadAll(flags.input_stream)
|
||||
if err != nil { return err }
|
||||
} else {
|
||||
message = []byte(flags.Message)
|
||||
}
|
||||
// compute sha256 of the message
|
||||
hash := sha256.New()
|
||||
length, _ := hash.Write(message)
|
||||
if length != len(message) { return errors.New("Error when creating hash over message!") }
|
||||
|
||||
// create signature of the hash using the private key
|
||||
signature, err := flags.private_key.Sign(
|
||||
rand.Reader,
|
||||
hash.Sum([]byte("")),
|
||||
nil,
|
||||
)
|
||||
if err != nil { return err }
|
||||
if flags.Format == "base64" {
|
||||
flags.output_stream.Write([]byte(base64.StdEncoding.EncodeToString(signature)))
|
||||
} else {
|
||||
flags.output_stream.Write(signature)
|
||||
}
|
||||
flags.output_stream.Write([]byte("\n"))
|
||||
return nil
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
|
||||
)
|
||||
|
||||
// sign a certificate request to create a new certificate
|
||||
func sign_request() {
|
||||
|
||||
}
|
|
@ -1,161 +0,0 @@
|
|||
package main
|
||||
|
||||
// verify a signature generated with a private key
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"crypto/sha256"
|
||||
"crypto/x509"
|
||||
"encoding/asn1"
|
||||
"encoding/pem"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"math/big"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type (
|
||||
VerifySignatureFlags struct {
|
||||
Message string // the message to sign
|
||||
MessageStream string // the path to the input stream
|
||||
PublicKeyPath string // path to the private key
|
||||
Signature string // a path or stream to output the private key to
|
||||
SignatureStream string // read signature from an input stream
|
||||
Format string // the format of the signature
|
||||
|
||||
message_stream io.Reader // the message stream
|
||||
signature_stream io.Reader // the signature stream
|
||||
}
|
||||
// struct to load the signature into (which is basically two bigint in byte form)
|
||||
Signature struct {
|
||||
R, S *big.Int
|
||||
}
|
||||
)
|
||||
|
||||
func verify_signature() {
|
||||
flags := parse_verify_signature_flags()
|
||||
if flags.SignatureStream == flags.MessageStream &&
|
||||
( flags.Message == "" && flags.Signature == "") {
|
||||
crash_with_help(2, "Signature and Message stream can't be the same source!")
|
||||
}
|
||||
|
||||
// open streams
|
||||
if flags.Message == "" && flags.MessageStream != "" {
|
||||
message_stream, err := open_input_stream(flags.MessageStream)
|
||||
if err != nil {
|
||||
crash_with_help(2, fmt.Sprintf("Error when opening stream %s: %s", flags.MessageStream, err))
|
||||
}
|
||||
defer message_stream.Close()
|
||||
flags.message_stream = message_stream
|
||||
}
|
||||
if flags.Signature == "" && flags.SignatureStream != "" {
|
||||
signature_stream, err := open_input_stream(flags.SignatureStream)
|
||||
if err != nil {
|
||||
crash_with_help(2, fmt.Sprintf("Error when opening stream %s: %s", flags.SignatureStream, err))
|
||||
}
|
||||
defer signature_stream.Close()
|
||||
flags.signature_stream = signature_stream
|
||||
}
|
||||
|
||||
public_key, err := load_public_key_ecdsa(flags.PublicKeyPath)
|
||||
if err != nil {
|
||||
crash_with_help(2, fmt.Sprintf("Error when loading public key: %s", err))
|
||||
}
|
||||
signature, err := load_signature(flags)
|
||||
if err != nil {
|
||||
crash_with_help(2, fmt.Sprintf("Error when loading the signature: %s", err))
|
||||
}
|
||||
message, err := load_message(flags)
|
||||
hash := sha256.New()
|
||||
hash.Write([]byte(message))
|
||||
|
||||
success := ecdsa.Verify(public_key, hash.Sum(nil), signature.R, signature.S)
|
||||
fmt.Println(success)
|
||||
}
|
||||
|
||||
// parse the parameters
|
||||
func parse_verify_signature_flags() VerifySignatureFlags {
|
||||
flags := VerifySignatureFlags{}
|
||||
fs := flag.NewFlagSet("verify-signature", flag.ExitOnError)
|
||||
fs.StringVar(&flags.PublicKeyPath, "public-key", "", "path to the public key file")
|
||||
fs.StringVar(&flags.Signature, "signature", "", "path where the signature file can be found")
|
||||
fs.StringVar(&flags.SignatureStream, "signature-stream", "", "the path to the stream of the signature (file, STDIN)")
|
||||
fs.StringVar(&flags.Format, "format", "auto", "the input format of the signature (auto, binary, base64)")
|
||||
fs.StringVar(&flags.Message, "message", "", "the message to validate")
|
||||
fs.StringVar(&flags.MessageStream, "message-stream", "STDIN", "the path to the stream to validate (file, STDIN)")
|
||||
fs.Parse(os.Args[2:])
|
||||
|
||||
return flags
|
||||
}
|
||||
|
||||
// load the private key from pem file
|
||||
func load_public_key_ecdsa(path string) (*ecdsa.PublicKey, error) {
|
||||
public_key_file, err := os.Open(path)
|
||||
if err != nil { return nil, err }
|
||||
public_key_pem, err := ioutil.ReadAll(public_key_file)
|
||||
if err != nil { return nil, err }
|
||||
public_key_file.Close()
|
||||
|
||||
block, _ := pem.Decode(public_key_pem)
|
||||
if block.Type != TypeLabelPubKey {
|
||||
return nil, errors.New(fmt.Sprintf("No public key found in %s", path))
|
||||
}
|
||||
|
||||
public_key, err := x509.ParsePKIXPublicKey(block.Bytes)
|
||||
if err != nil { return nil, err }
|
||||
return public_key.(*ecdsa.PublicKey), nil
|
||||
}
|
||||
|
||||
// parse the signature from asn1 file
|
||||
func load_signature(flags VerifySignatureFlags) (*Signature, error) {
|
||||
var signature_raw []byte
|
||||
var err error
|
||||
if flags.Message != "" {
|
||||
signature_raw = []byte(flags.Message)
|
||||
} else {
|
||||
signature_raw, err = ioutil.ReadAll(flags.signature_stream)
|
||||
if err != nil { return nil, err }
|
||||
}
|
||||
|
||||
switch strings.ToLower(flags.Format) {
|
||||
case "auto":
|
||||
sig, err := load_signature_base64(signature_raw)
|
||||
if err != nil {
|
||||
sig, err = load_signature_binary(signature_raw)
|
||||
if err != nil { return nil, err }
|
||||
return sig, nil
|
||||
}
|
||||
return sig, nil
|
||||
case "base64": return load_signature_base64(signature_raw)
|
||||
case "binary": return load_signature_binary(signature_raw)
|
||||
default: return nil, errors.New("Unknown format!")
|
||||
}
|
||||
}
|
||||
|
||||
// convert the signature from base64 into a signature
|
||||
func load_signature_base64(signature_raw []byte) (*Signature, error) {
|
||||
asn1_sig, err := base64.StdEncoding.DecodeString(string(signature_raw))
|
||||
if err != nil { return nil, err }
|
||||
return load_signature_binary(asn1_sig)
|
||||
}
|
||||
|
||||
// convert the signature from asn1 into a signature
|
||||
func load_signature_binary(signature_raw []byte) (*Signature, error) {
|
||||
var signature Signature
|
||||
_, err := asn1.Unmarshal(signature_raw, &signature)
|
||||
if err != nil { return nil, err }
|
||||
return &signature, nil
|
||||
}
|
||||
|
||||
// load the message from a stream or the parameter
|
||||
func load_message(flags VerifySignatureFlags) (string, error) {
|
||||
if flags.Message != "" { return flags.Message, nil }
|
||||
message, err := ioutil.ReadAll(flags.message_stream)
|
||||
if err != nil { return "", err }
|
||||
return string(message), nil
|
||||
}
|
Loading…
Reference in New Issue