diff --git a/certificate_data.go b/certificate_data.go new file mode 100644 index 0000000..76f3323 --- /dev/null +++ b/certificate_data.go @@ -0,0 +1,28 @@ +package main + +import ( + "crypto/x509" + "crypto/x509/pkix" + "net" +) + +type ( + certificateData struct { + Subject pkix.Name + + DnsNames []string + EmailAddresses []string + IpAddresses []net.IP + } +) + +func (c *certificateData) GenerateCSR() *x509.CertificateRequest { + csr := &x509.CertificateRequest{} + + csr.Subject = c.Subject + csr.DNSNames = c.DnsNames + csr.IPAddresses = c.IpAddresses + csr.EmailAddresses = c.EmailAddresses + + return csr +} diff --git a/flags.go b/flags.go index f67657e..7aee688 100644 --- a/flags.go +++ b/flags.go @@ -5,11 +5,15 @@ package main import ( "crypto/elliptic" + "crypto/x509/pkix" "encoding/base64" "flag" "fmt" "io" + "net" "os" + "reflect" + "strings" "github.com/gibheer/pki" ) @@ -26,17 +30,22 @@ var ( 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 + manual struct { + serialNumber string // 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) + emailAddresses string // alternative email addresses + } + automatic struct { + 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 @@ -64,9 +73,10 @@ type ( Output io.WriteCloser // signature from the args Signature []byte - // private key specific stuff PrivateKeyGenerationFlags privateKeyGenerationFlags + // a certificate filled with the parameters + CertificateData certificateData } privateKeyGenerationFlags struct { @@ -253,3 +263,84 @@ func (f *Flags) parseSignature() error { if err != nil { return err } return nil } + +// add the certificate fields to the flags +func (f *Flags) AddCertificateFields() { + f.check_list = append(f.check_list, f.parseCertificateFields) + f.flagset.StringVar( + &f.flag_container.certificateFlags.manual.serialNumber, + "serial", "1", "unique serial number of the CA"); + f.flagset.StringVar( + &f.flag_container.certificateFlags.manual.commonName, + "common-name", "", "common name of the entity to certify"); + f.flagset.StringVar( + &f.flag_container.certificateFlags.manual.dnsNames, + "dns-names", "", "comma separated list of alternative fqdn entries for the entity"); + f.flagset.StringVar( + &f.flag_container.certificateFlags.manual.emailAddresses, + "email-address", "", "comma separated list of alternative email entries for the entity"); + f.flagset.StringVar( + &f.flag_container.certificateFlags.manual.ipAddresses, + "ip-address", "", "comma separated list of alternative ip entries for the entity"); + f.flagset.StringVar( + &f.flag_container.certificateFlags.automatic.Country, + "country", "", "comma separated list of countries the entitiy resides in"); + f.flagset.StringVar( + &f.flag_container.certificateFlags.automatic.Organization, + "organization", "", "comma separated list of organizations the entity belongs to"); + f.flagset.StringVar( + &f.flag_container.certificateFlags.automatic.OrganizationalUnit, + "organization-unit", "", "comma separated list of organization units or departments the entity belongs to"); + f.flagset.StringVar( + &f.flag_container.certificateFlags.automatic.Locality, + "locality", "", "comma separated list of localities or cities the entity resides in"); + f.flagset.StringVar( + &f.flag_container.certificateFlags.automatic.Province, + "province", "", "comma separated list of provinces the entity resides in"); + f.flagset.StringVar( + &f.flag_container.certificateFlags.automatic.StreetAddress, + "street-address", "", "comma separated list of street addresses the entity resides in"); + f.flagset.StringVar( + &f.flag_container.certificateFlags.automatic.PostalCode, + "postal-code", "", "comma separated list of postal codes of the localities"); +} + +// parse the certificate fields into a raw certificate +func (f *Flags) parseCertificateFields() error { + f.Flags.CertificateData = certificateData{Subject: pkix.Name{}} + // convert the automatic flags + container_type := reflect.ValueOf(&f.flag_container.certificateFlags.automatic).Elem() + cert_data_type := reflect.ValueOf(&f.Flags.CertificateData.Subject).Elem() + + for _, field := range []string{"Country", "Organization", "OrganizationalUnit", + "Locality", "Province", "StreetAddress", "PostalCode"} { + field_value := container_type.FieldByName(field).String() + if field_value == "" { continue } + target := cert_data_type.FieldByName(field) + target.Set(reflect.ValueOf(strings.Split(field_value, ","))) + } + + // convert the manual flags + data := &f.Flags.CertificateData + raw_data := f.flag_container.certificateFlags.manual + data.Subject.SerialNumber = raw_data.serialNumber + data.Subject.CommonName = raw_data.commonName + if raw_data.dnsNames != "" { + data.DnsNames = strings.Split(raw_data.dnsNames, ",") + } + if raw_data.emailAddresses != "" { + data.EmailAddresses = strings.Split(raw_data.emailAddresses, ",") + } + + if raw_data.ipAddresses == "" { return nil } + raw_ips := strings.Split(raw_data.ipAddresses, ",") + data.IpAddresses = make([]net.IP, len(raw_ips)) + for i, ip := range raw_ips { + data.IpAddresses[i] = net.ParseIP(ip) + if data.IpAddresses[i] == nil { + return fmt.Errorf("'%s' is not a valid IP", ip) + } + } + + return nil +} diff --git a/main.go b/main.go index c509fd7..b33ced9 100644 --- a/main.go +++ b/main.go @@ -2,7 +2,10 @@ package main import ( "crypto" + "crypto/rand" + "crypto/x509" "encoding/base64" + "encoding/pem" "fmt" "io" "io/ioutil" @@ -25,7 +28,7 @@ func main() { case "create-public": create_public_key() case "sign-input": sign_input() case "verify-signature": verify_input() -// case "create-cert-sign": create_sign_request() + case "create-cert-sign": create_sign_request() // case "sign-request": sign_request() case "help": print_modules() // case "info": info_on_file() @@ -115,6 +118,22 @@ func verify_input() { os.Exit(1) } +// create a certificate sign request +func create_sign_request() { + fs := NewFlags("create-cert-sign") + fs.AddPrivateKey() + fs.AddOutput() + fs.AddCertificateFields() + fs.Parse(program_args()) + + csrt := fs.Flags.CertificateData.GenerateCSR() + csr, err := x509.CreateCertificateRequest(rand.Reader, csrt, fs.Flags.PrivateKey.PrivateKey()) + if err != nil { crash_with_help(2, "Could not create certificate sign request: %s", err) } + pem_block := &pem.Block{Type: "CERTIFICATE REQUEST", Bytes: csr} + err = pem.Encode(fs.Flags.Output, pem_block) + if err != nil { crash_with_help(2, "Encoding didn't work: %s", err) } +} + // print the module help func print_modules() { fmt.Printf(`Usage: %s command args