Commit e93306c6 authored by Heiko Reese's avatar Heiko Reese
Browse files

parent d42765bc
// Copyright © 2018 NAME HERE <EMAIL ADDRESS>
// Copyright © 2018 Heiko Reese <heiko.reese@kit.edu>
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
......@@ -24,12 +24,18 @@ import (
"strings"
"crypto/x509"
"encoding/pem"
"path/filepath"
"time"
"net"
"net/url"
"git.scc.kit.edu/KIT-CA/dfnpki"
"github.com/davecgh/go-spew/spew"
"github.com/kennygrant/sanitize"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"path/filepath"
"time"
)
type sanGenerator struct {
......@@ -49,21 +55,51 @@ var requestCmd *cobra.Command = &cobra.Command{
pkey *rsa.PrivateKey
)
viper.BindPFlags(cmd.Flags())
spew.Dump(viper.AllSettings())
//os.Exit(1)
// check ca name
if viper.GetString("ca") == "" {
log.Fatal("Please specify a valid CA")
}
// check cn
if viper.GetString("cn") == "" {
log.Fatal("Please specify a common name")
}
// clean up CommonName
cnCleanUp := strings.NewReplacer("Ä", "Ae", "Ö", "Oe", "Ü", "Ue", "ä", "ae", "ö", "oe", "ü", "ue", "ß", "ss")
cleaned := cnCleanUp.Replace(globalArgs.CommonName)
if cleaned != globalArgs.CommonName {
log.Printf("Transcribed illegal characters in cn »%s«: »%s«", globalArgs.CommonName, cleaned)
globalArgs.CommonName = cleaned
commonName := viper.GetString("cn")
commonNameCleaned := cnCleanUp.Replace(commonName)
if commonNameCleaned != viper.GetString("cn") {
log.Printf("Transcribed illegal characters in cn »%s«: »%s«", commonName, commonNameCleaned)
viper.Set("cn", commonNameCleaned)
commonName = commonNameCleaned
}
basefilename := sanitize.Name(globalArgs.CommonName)
// check profile
profile := viper.GetString("profile")
dfnrole, exists := dfnpki.DFNPublicRoles[profile]
if !exists {
log.Fatal("Unknown profile ", profile)
}
// set default outpath
basefilename := sanitize.Name(commonName)
timestamp := time.Now().Format("_2006-02-01T15_04_05")
outputdir := viper.GetString("outdir")
// create output path
err = os.MkdirAll(globalArgs.Outdir, 0700)
if outputdir == "" {
outputdir = basefilename + timestamp
viper.Set("outdir", outputdir)
}
err = os.MkdirAll(outputdir, 0700)
if err != nil {
log.Fatalf("Unable to create output directory %s: %s", globalArgs.Outdir, err)
log.Fatalf("Unable to create output directory %s: %s", outputdir, err)
} else {
log.Printf("Output directory is »%s«", outputdir)
}
// create http client
......@@ -73,12 +109,12 @@ var requestCmd *cobra.Command = &cobra.Command{
}
// generate PIN if needed
if globalArgs.PIN == "" {
globalArgs.PIN = dfnpki.RandomPIN()
log.Printf("Generated PIN: »%s«", globalArgs.PIN)
if viper.GetString("PIN") == "" {
viper.Set("PIN", dfnpki.RandomPIN())
log.Printf("Generated PIN: »%s«", viper.GetString("PIN"))
}
filename := filepath.Join(globalArgs.Outdir, basefilename+timestamp+".PIN.txt")
err = ioutil.WriteFile(filename, []byte(globalArgs.PIN), 0644)
filename := filepath.Join(outputdir, basefilename+".PIN.txt")
err = ioutil.WriteFile(filename, []byte(viper.GetString("PIN")), 0644)
if err != nil {
log.Fatalf("Unable to write PIN file %s: %s", filename, err)
} else {
......@@ -86,60 +122,131 @@ var requestCmd *cobra.Command = &cobra.Command{
}
// handle private key
if globalArgs.Keyfile != "" {
keyfilename := viper.GetString("keyfile")
if keyfilename != "" {
// read keyfile
keybytes, err := ioutil.ReadFile(globalArgs.Keyfile)
keybytes, err := ioutil.ReadFile(keyfilename)
if err != nil {
log.Fatal("Unable to read secret key from file %s: %s", globalArgs.Keyfile, err)
log.Fatal("Unable to read secret key from file %s: %s", keyfilename, err)
}
pkey, err = dfnpki.ParsePrivateKey(keybytes, []byte(globalArgs.Keypass))
pkey, err = dfnpki.ParsePrivateKey(keybytes, []byte(viper.GetString("keypass")))
if err != nil {
log.Fatal("Unable to parse secret key from file %s: %s", globalArgs.Keyfile, err)
log.Fatal("Unable to parse secret key from file %s: %s", keyfilename, err)
}
log.Printf("Read private key with %d bytes from %s", pkey.N.BitLen(), globalArgs.Keyfile)
log.Printf("Read private key with %d bytes from %s", pkey.N.BitLen(), keyfilename)
} else {
// generate key
pkey, err = dfnpki.NewPrivateKey(globalArgs.Keysize)
pkey, err = dfnpki.NewPrivateKey(viper.GetInt("keysize"))
if err != nil {
log.Fatal("Unable to generate secret key: %s", err)
} else {
log.Printf("Private rsa key with %d bytes generated", pkey.N.BitLen())
}
// save key as pkcs1
// save key as pkcs1/der
pkcs1key := x509.MarshalPKCS1PrivateKey(pkey)
filename := filepath.Join(globalArgs.Outdir, basefilename+timestamp+".key.der")
filename := filepath.Join(outputdir, basefilename+".key.der")
err = ioutil.WriteFile(filename, pkcs1key, 0600)
if err != nil {
log.Fatalf("Unable to write private to »%s« as PKCS1: %s", filename, err)
log.Fatalf("Unable to write private to »%s« as PKCS1/DER: %s", filename, err)
} else {
log.Printf("Wrote private to »%s« (format: PKCS1)", filename)
}
// TODO: save as p1/PEM
// save key as pkcs8
pkcs8key, err := x509.MarshalPKCS8PrivateKey(pkey)
if err != nil {
log.Fatal("Unable to marshal private key as PKCS8: ", err)
log.Printf("Wrote private to »%s« (format: PKCS1/DER)", filename)
}
filename = filepath.Join(globalArgs.Outdir, basefilename+timestamp+".key.p8")
err = ioutil.WriteFile(filename, pkcs8key, 0600)
// save key as pkcs1/pem
pemkey := pem.EncodeToMemory(&pem.Block{
Bytes: pkcs1key,
Type: "RSA PRIVATE KEY",
})
filename = filepath.Join(outputdir, basefilename+".key.pem")
err = ioutil.WriteFile(filename, pemkey, 0600)
if err != nil {
log.Fatalf("Unable to write private to »%s« as PKCS8: %s", filename, err)
log.Fatalf("Unable to write private to »%s« as PKCS1/PEM: %s", filename, err)
} else {
log.Printf("Wrote private to »%s« (format: PKCS8)", filename)
log.Printf("Wrote private to »%s« (format: PKCS1/PEM)", filename)
}
/*
// save key as pkcs8
pkcs8key, err := x509.MarshalPKCS8PrivateKey(pkey)
if err != nil {
log.Fatal("Unable to marshal private key as PKCS8: ", err)
}
filename = filepath.Join(outputdir, basefilename+".key.p8")
err = ioutil.WriteFile(filename, pkcs8key, 0600)
if err != nil {
log.Fatalf("Unable to write private to »%s« as PKCS8: %s", filename, err)
} else {
log.Printf("Wrote private to »%s« (format: PKCS8)", filename)
}
*/
}
// prepare certificate request
request := &x509.CertificateRequest{
Subject: pkix.Name{
CommonName: commonName,
},
SignatureAlgorithm: x509.SHA256WithRSA,
}
spew.Dump(viper.IsSet("ou"))
// OU
if viper.IsSet("ou") {
request.Subject.OrganizationalUnit = viper.GetStringSlice("ou")
}
// O
if viper.IsSet("o") {
request.Subject.Organization = []string{viper.GetString("o")}
}
// L
if viper.IsSet("locality") {
request.Subject.Locality = []string{viper.GetString("locality")}
}
// ST
if viper.IsSet("state") {
request.Subject.Province = []string{viper.GetString("state")}
}
// C
if viper.IsSet("country") {
request.Subject.Country = []string{viper.GetString("country")}
}
// DNS SAN
if viper.IsSet("dns") {
request.DNSNames = viper.GetStringSlice("dns")
}
// email
if viper.IsSet("email") {
request.EmailAddresses = viper.GetStringSlice("email")
}
// add IP SANs
var saniplist []net.IP
if viper.IsSet("ip") {
for _, ipstring := range viper.GetStringSlice("ip") {
if ip := net.ParseIP(ipstring); ip != nil {
saniplist = append(saniplist, ip)
} else {
log.Fatalf("Unable to convert »%s« to ip address", ipstring)
}
}
request.IPAddresses = saniplist
}
// add URL SANS
var sanurllist []*url.URL
if viper.IsSet("url") {
for _, urlstring := range viper.GetStringSlice("url") {
if uri, err := url.Parse(urlstring); err == nil {
sanurllist = append(sanurllist, uri)
} else {
log.Fatalf("Unable to parse »%s« as URL: %s", urlstring, err)
}
}
request.URIs = sanurllist
}
spew.Dump(request); os.Exit(4)
// generate certificate request
csr, err := dfnpki.GenerateRequest(pkey, pkix.Name{
CommonName: globalArgs.CommonName,
OrganizationalUnit: globalArgs.OU,
Organization: []string{globalArgs.Organisation},
Locality: []string{globalArgs.Locality},
Province: []string{globalArgs.State},
Country: []string{globalArgs.Country},
})
csr, err := dfnpki.GenerateRequest(pkey, request)
if err != nil {
log.Fatal("Unable to generate pkcs10 certificate request: ", err)
}
filename = filepath.Join(globalArgs.Outdir, basefilename+timestamp+".req")
filename = filepath.Join(outputdir, basefilename+".req")
err = ioutil.WriteFile(filename, []byte(csr), 0644)
if err != nil {
log.Fatalf("Unable to write request file %s: %s", filename, err)
......@@ -153,45 +260,64 @@ var requestCmd *cobra.Command = &cobra.Command{
for _, sanGenerator := range []sanGenerator{
{
Kind: dfnpki.SANEmail,
Values: globalArgs.SANEmail,
Values: viper.GetStringSlice("email"),
},
{
Kind: dfnpki.SANDNS,
Values: globalArgs.SANDNS,
Values: viper.GetStringSlice("dns"),
},
{
Kind: dfnpki.URI,
Values: globalArgs.SANURI,
Values: viper.GetStringSlice("uri"),
},
{
Kind: dfnpki.IP,
Values: globalArgs.SANIP,
Values: viper.GetStringSlice("ip"),
},
} {
for _, value := range sanGenerator.Values {
SubjectAlternativeNames = append(SubjectAlternativeNames, dfnpki.SAN(sanGenerator.Kind, value))
}
}
// fix requester date for profile User
if profile == "User" {
if viper.GetString("RequesterName") == "" {
viper.Set("RequesterName", commonName)
}
if viper.GetString("RequesterEmail") == "" {
emailArgs := viper.GetStringSlice("email")
if len(emailArgs) > 0 {
viper.Set("RequesterEmail", emailArgs[0])
} else {
log.Fatal("No email address set")
}
}
}
// prepare request
requestData := dfnpki.NewRequestData{
RaId: globalArgs.RAId,
RaId: viper.GetInt("ra-id"),
Pkcs10: csr,
AltNames: SubjectAlternativeNames,
Role: dfnpki.DFNRole(globalArgs.Profile),
PIN: globalArgs.PIN,
AddName: globalArgs.ApplicantName,
AddEMail: globalArgs.ApplicantEmail,
AddOrgUnit: globalArgs.ApplicantOU,
Publish: globalArgs.Publish,
Role: dfnrole,
PIN: viper.GetString("PIN"),
AddName: viper.GetString("RequesterName"),
AddEMail: viper.GetString("RequesterEmail"),
AddOrgUnit: viper.GetString("RequesterOU"),
Publish: viper.GetBool("Publish"),
}
// make request
requestID, err := dfnpki.NewRequest(client, dfnpki.GeneratePublicURL(globalArgs.CAName), *pkey, csr, requestData)
//spew.Dump(requestData, SubjectAlternativeNames, commonName)
publicAPIEndpoint := dfnpki.GeneratePublicURL(viper.GetString("ca"))
//fmt.Println(publicAPIEndpoint)
requestID, err := dfnpki.NewRequest(client, publicAPIEndpoint, csr, requestData)
if err != nil {
log.Fatal("Error sending request to api endpoint: ", err)
}
log.Printf("New request with ID %s created", requestID)
filename = filepath.Join(globalArgs.Outdir, basefilename+timestamp+".request-id.txt")
// save request id
filename = filepath.Join(outputdir, basefilename+".request-id.txt")
err = ioutil.WriteFile(filename, []byte(requestID+"\n"), 0644)
if err != nil {
log.Fatalf("Unable to write request file %s: %s", filename, err)
......@@ -200,15 +326,15 @@ var requestCmd *cobra.Command = &cobra.Command{
}
// fetch PDF
if globalArgs.SkipPDF == false {
pdf, err := dfnpki.GetRequestPrintout(client, globalArgs.RAId, requestID, globalArgs.PIN)
if viper.GetBool("SkipPDF") == false {
pdf, err := dfnpki.GetRequestPrintout(client, viper.GetInt("ra-id"), requestID, viper.GetString("PIN"))
if err != nil {
log.Fatal("Unable to retrieve PDF: %s", err)
log.Fatalf("Unable to retrieve PDF: %s", err)
}
filename = filepath.Join(globalArgs.Outdir, basefilename+timestamp+".request.pdf")
filename = filepath.Join(outputdir, basefilename+".request.pdf")
ioutil.WriteFile(filename, pdf, 0644)
if err != nil {
log.Fatal("Unable to write PDF file »%s«: %s", filename, err)
log.Fatalf("Unable to write PDF file »%s«: %s", filename, err)
} else {
log.Printf("PDF request saved to »%s«", filename)
}
......@@ -217,29 +343,30 @@ var requestCmd *cobra.Command = &cobra.Command{
}
func init() {
// just logging, no prefixes
log.SetFlags(0)
rootCmd.AddCommand(requestCmd)
// Here you will define your flags and configuration settings.
requestCmd.Flags().SortFlags = false
requestCmd.Flags().StringVar(&globalArgs.Profile, "profile", "", "Certificate profile/role")
requestCmd.MarkFlagRequired("profile")
requestCmd.Flags().StringVar(&globalArgs.Profile, "profile", "Web Server", "Certificate profile")
requestCmd.Flags().StringVarP(&globalArgs.CommonName, "cn", "c", "", "Set »CommonName« (common name) part of distinguished name")
requestCmd.MarkFlagRequired("cn")
requestCmd.Flags().StringArrayVar(&globalArgs.OU, "ou", nil, "Set »OU« (organizational unit) part(s) of distinguished name")
requestCmd.Flags().StringSliceVar(&globalArgs.OU, "ou", nil, "Set »OU« (organizational unit) part(s) of distinguished name")
requestCmd.Flags().StringVar(&globalArgs.Organisation, "o", "", "Set »O« (organization) part of distinguished name")
requestCmd.Flags().StringVar(&globalArgs.Locality, "locality", "", "Set »L« (locality) part of distinguished name")
requestCmd.Flags().StringVar(&globalArgs.State, "state", "", "Set »ST« (state) part of distinguished name")
requestCmd.Flags().StringVar(&globalArgs.Country, "country", "", "Set »C« (country) part of distinguished name")
requestCmd.Flags().StringArrayVar(&globalArgs.SANDNS, "dns", nil, "Add »SANDNS« (hostname or domain name) Subject Alternative Name(s)")
requestCmd.Flags().StringArrayVar(&globalArgs.SANIP, "ip", nil, "Add »IP« (ip address) Subject Alternative Name(s)")
requestCmd.Flags().StringArrayVar(&globalArgs.SANURI, "uri", nil, "Add »URI« Subject Alternative Name(s)")
requestCmd.Flags().StringArrayVar(&globalArgs.SANEmail, "email", nil, "Add »email« Subject Alternative Name(s)")
requestCmd.Flags().StringSliceVar(&globalArgs.SANDNS, "dns", nil, "Add »SANDNS« (hostname or domain name) Subject Alternative Name(s)")
requestCmd.Flags().StringSliceVar(&globalArgs.SANIP, "ip", nil, "Add »IP« (ip address) Subject Alternative Name(s)")
requestCmd.Flags().StringSliceVar(&globalArgs.SANURI, "uri", nil, "Add »URI« Subject Alternative Name(s)")
requestCmd.Flags().StringSliceVar(&globalArgs.SANEmail, "email", nil, "Add »email« Subject Alternative Name(s)")
// TODO: add other types
requestCmd.Flags().StringVar(&globalArgs.ApplicantName, "addName", "", "Name of applicant (»Beantrager«); set to CommonName for personal certificate")
requestCmd.Flags().StringVar(&globalArgs.ApplicantEmail, "addEmail", "", "E-Mail of applicant (»Beantrager«)")
requestCmd.Flags().StringVar(&globalArgs.ApplicantOU, "addOU", "", "Organisational Unit of applicant (»Beantrager«)")
requestCmd.Flags().StringVar(&globalArgs.RequesterName, "RequesterName", "", "Name of requester (»Beantrager«); set to CommonName for personal certificate")
requestCmd.Flags().StringVar(&globalArgs.RequesterEmail, "RequesterEmail", "", "E-Mail of requester (»Beantrager«)")
requestCmd.Flags().StringVar(&globalArgs.RequesterOU, "RequesterOU", "", "Organisational Unit of requester (»Beantrager«)")
requestCmd.Flags().BoolVar(&globalArgs.Publish, "publish", true, "Publish certificate")
requestCmd.Flags().StringVar(&globalArgs.PIN, "pin", "", "PIN for revocation and retrieval of unpublished certificates")
......@@ -247,8 +374,8 @@ func init() {
requestCmd.Flags().StringVarP(&globalArgs.Keyfile, "keyfile", "k", "", "Read key from filename if set; autogenerated otherwise")
requestCmd.MarkFlagFilename("keyfile", "")
requestCmd.Flags().StringVarP(&globalArgs.Keypass, "keypass", "p", "", "Password if secret key is encrypted")
requestCmd.Flags().IntVar(&globalArgs.Keysize, "keysize", 4096, "Size of secret key in bits (only used if --keyfile is not set; recommended: 4096 or 2048)")
requestCmd.Flags().StringVarP(&globalArgs.Outdir, "outdir", "o", ".", "Directory for all output files")
requestCmd.Flags().IntVar(&globalArgs.Keysize, "keysize", 4096, "Size of secret key in bits (only used if --keyfile is not set; minimum size 2048 bits)")
requestCmd.Flags().StringVarP(&globalArgs.Outdir, "outdir", "o", "", "Directory for all output files")
requestCmd.Flags().BoolVarP(&globalArgs.DryRun, "dry-run", "n", false, "Only show request data, don't execute anything")
requestCmd.Flags().BoolVarP(&globalArgs.SkipPDF, "skip-pdf", "s", false, "Don't fetch pdf after request")
......
......@@ -35,7 +35,9 @@ import (
"path/filepath"
homedir "github.com/mitchellh/go-homedir"
"log"
"github.com/mitchellh/go-homedir"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
......@@ -71,9 +73,9 @@ var (
SANIP []string
SANURI []string
SANEmail []string
ApplicantName string
ApplicantEmail string
ApplicantOU string
RequesterName string
RequesterEmail string
RequesterOU string
Keyfile string
Keypass string
Keysize int
......@@ -96,12 +98,15 @@ func Execute() {
func init() {
cobra.OnInitialize(initConfig)
requestCmd.Flags().SortFlags = false
rootCmd.Flags().SortFlags = false
rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.config/.dfnpkitool.toml)")
rootCmd.PersistentFlags().StringVar(&globalArgs.CAName, "ca", "", "Certification Authority identifier")
rootCmd.PersistentFlags().IntVar(&globalArgs.RAId, "raid", 0, "Sub-RA identifier (usually 0 for main RA)")
rootCmd.PersistentFlags().StringVarP(&globalArgs.CAName, "ca", "C", "", "Certification Authority identifier")
rootCmd.PersistentFlags().IntVarP(&globalArgs.RAId, "ra-id", "R", 0, "Sub-RA identifier (usually 0 for main RA)")
viper.SetEnvPrefix("DFNPKI")
viper.BindPFlag("ca", rootCmd.PersistentFlags().Lookup("ca"))
viper.BindPFlag("raid", rootCmd.PersistentFlags().Lookup("raid"))
viper.BindPFlag("raid", rootCmd.PersistentFlags().Lookup("ra-id"))
}
// initConfig reads in config file and ENV variables if set.
......@@ -113,8 +118,7 @@ func initConfig() {
// Find home directory.
home, err := homedir.Dir()
if err != nil {
fmt.Println(err)
os.Exit(1)
log.Fatal(err)
}
// Search config in home directory with name "dfnpkitool" (without extension).
......@@ -126,6 +130,6 @@ func initConfig() {
// If a config file is found, read it in.
err := viper.ReadInConfig()
if err == nil {
fmt.Println("Using config file:", viper.ConfigFileUsed())
log.Println("Using config file:", viper.ConfigFileUsed())
}
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment