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

First prototype (currently broken)

parent aa8ef4f5
......@@ -15,22 +15,204 @@
package cmd
import (
type sanGenerator struct {
Kind int
Values []string
// requestCmd represents the request command
var requestCmd = &cobra.Command{
var requestCmd *cobra.Command = &cobra.Command{
Use: "request",
Short: "Request a new certificate and retrieve the pdf application form",
// Long: ``,
TraverseChildren: true,
Run: func(cmd *cobra.Command, args []string) {
var (
err error
pkey *rsa.PrivateKey
fmt.Println("request called")
// 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
basefilename := sanitize.Name(globalArgs.CommonName)
timestamp := time.Now().Format("_2006-02-01T15_04_05")
// create output path
err = os.MkdirAll(globalArgs.Outdir, 0700)
if err != nil {
log.Fatalf("Unable to create output directory %s: %s", globalArgs.Outdir, err)
// create http client
client, err := dfnpki.GetHTTPClient()
if err != nil {
// generate PIN if needed
if globalArgs.PIN == "" {
globalArgs.PIN = dfnpki.RandomPIN()
log.Printf("Generated PIN: »%s«", globalArgs.PIN)
filename := filepath.Join(globalArgs.Outdir, basefilename+timestamp+".PIN.txt")
err = ioutil.WriteFile(filename, []byte(globalArgs.PIN), 0644)
if err != nil {
log.Fatalf("Unable to write PIN file %s: %s", filename, err)
} else {
log.Printf("Wrote PIN to »%s«", filename)
// handle private key
if globalArgs.Keyfile != "" {
// read keyfile
keybytes, err := ioutil.ReadFile(globalArgs.Keyfile)
if err != nil {
log.Fatal("Unable to read secret key from file %s: %s", globalArgs.Keyfile, err)
pkey, err = dfnpki.ParsePrivateKey(keybytes, []byte(globalArgs.Keypass))
if err != nil {
log.Fatal("Unable to parse secret key from file %s: %s", globalArgs.Keyfile, err)
log.Printf("Read private key with %d bytes from %s", pkey.N.BitLen(), globalArgs.Keyfile)
} else {
// generate key
pkey, err = dfnpki.NewPrivateKey(globalArgs.Keysize)
if err != nil {
log.Fatal("Unable to generate secret key: %s", err)
// save key as pkcs1
pkcs1key := x509.MarshalPKCS1PrivateKey(pkey)
filename := filepath.Join(globalArgs.Outdir, basefilename+timestamp+".key.der")
err = ioutil.WriteFile(filename, pkcs1key, 0600)
if err != nil {
log.Fatalf("Unable to write private to »%s« as PKCS1: %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)
filename = filepath.Join(globalArgs.Outdir, basefilename+timestamp+".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)
// 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},
if err != nil {
log.Fatal("Unable to generate pkcs10 certificate request: ", err)
filename = filepath.Join(globalArgs.Outdir, basefilename+timestamp+".req")
err = ioutil.WriteFile(filename, []byte(csr), 0644)
if err != nil {
log.Fatalf("Unable to write request file %s: %s", filename, err)
} else {
log.Printf("Wrote request to »%s« (format: PKCS10)", filename)
// generate compatible argument for SAN addition via API
// TODO: support all types
var SubjectAlternativeNames []string
for _, sanGenerator := range []sanGenerator{
Kind: dfnpki.SANEmail,
Values: globalArgs.SANEmail,
Kind: dfnpki.SANDNS,
Values: globalArgs.SANDNS,
Kind: dfnpki.URI,
Values: globalArgs.SANURI,
Kind: dfnpki.IP,
Values: globalArgs.SANIP,
} {
for _, value := range sanGenerator.Values {
SubjectAlternativeNames = append(SubjectAlternativeNames, dfnpki.SAN(sanGenerator.Kind, value))
// prepare request
requestData := dfnpki.NewRequestData{
RaId: globalArgs.RAId,
Pkcs10: csr,
AltNames: SubjectAlternativeNames,
Role: dfnpki.DFNRole(globalArgs.Profile),
PIN: globalArgs.PIN,
AddName: globalArgs.ApplicantName,
AddEMail: globalArgs.ApplicantEmail,
AddOrgUnit: globalArgs.ApplicantOU,
Publish: globalArgs.Publish,
// make request
requestID, err := dfnpki.NewRequest(client, dfnpki.GeneratePublicURL(globalArgs.CAName), *pkey, 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")
err = ioutil.WriteFile(filename, []byte(requestID+"\n"), 0644)
if err != nil {
log.Fatalf("Unable to write request file %s: %s", filename, err)
} else {
log.Printf("Request ID written to »%s«", filename)
// fetch PDF
if globalArgs.SkipPDF == false {
pdf, err := dfnpki.GetRequestPrintout(client, globalArgs.RAId, requestID, globalArgs.PIN)
if err != nil {
log.Fatal("Unable to retrieve PDF: %s", err)
filename = filepath.Join(globalArgs.Outdir, basefilename+timestamp+".request.pdf")
ioutil.WriteFile(filename, pdf, 0644)
if err != nil {
log.Fatal("Unable to write PDF file »%s«: %s", filename, err)
} else {
log.Printf("PDF request saved to »%s«", filename)
......@@ -39,22 +221,37 @@ func init() {
// Here you will define your flags and configuration settings.
requestCmd.Flags().SortFlags = false
requestCmd.Flags().StringVarP(&globalArgs.Keyfile, "cn", "c", "", "Set common name »CN«")
requestCmd.Flags().StringVar(&globalArgs.Profile, "profile", "", "Certificate profile/role")
requestCmd.Flags().StringVarP(&globalArgs.CommonName, "cn", "c", "", "Set »CommonName« (common name) part of distinguished name")
requestCmd.Flags().StringArrayVar(&globalArgs.DNS, "dns", nil, "Add »DNS« (hostname or domain name) Subject Alternative Name")
requestCmd.Flags().StringArrayVar(&globalArgs.IPs, "ip", nil, "Add »IP« (ip address) Subject Alternative Name")
requestCmd.Flags().StringArrayVar(&globalArgs.URIs, "uri", nil, "Add »URI« Subject Alternative Name")
requestCmd.Flags().StringArrayVar(&globalArgs.OUs, "ou", nil, "Add »OU« (organizational unit) Subject Alternative Name")
requestCmd.Flags().StringArrayVar(&globalArgs.Os, "o", nil, "Add »O« (organization) Subject Alternative Name")
requestCmd.Flags().StringArrayVar(&globalArgs.Localitys, "locality", nil, "Add »L« (locality) Subject Alternative Name")
requestCmd.Flags().StringArrayVar(&globalArgs.Countrys, "country", nil, "Add »C« (country) Subject Alternative Name")
requestCmd.Flags().StringVarP(&globalArgs.Keyfile, "keyfile", "k", "", "Filename of secret key (autogenerated if not set)")
requestCmd.Flags().StringArrayVar(&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)")
// 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().BoolVar(&globalArgs.Publish, "publish", true, "Publish certificate")
requestCmd.Flags().StringVar(&globalArgs.PIN, "pin", "", "PIN for revocation and retrieval of unpublished certificates")
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().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")
// Cobra supports local flags which will only run when this command
// is called directly, e.g.:
......@@ -52,26 +52,35 @@ var (
// Long: ``,
// Uncomment the following line if your bare application
// has an action associated with it:
Run: func(cmd *cobra.Command, args []string) {
//Run: func(cmd *cobra.Command, args []string) {
// see also: Zertifizierungsrichtlinie der DFN-PKI-Sicherheitsniveau Global, Version:3.8, (OID):
globalArgs struct {
DryRun bool
CAName string
RAId int
CN string
DNS []string
IPs []string
URIs []string
Os []string
OUs []string
Localitys []string
Countrys []string
Keyfile string
Keypass string
PIN string // TODO
Outdir string
DryRun bool
CAName string
RAId int
Profile string
CommonName string
Organisation string
OU []string
Locality string
State string
Country string
SANDNS []string
SANIP []string
SANURI []string
SANEmail []string
ApplicantName string
ApplicantEmail string
ApplicantOU string
Keyfile string
Keypass string
Keysize int
PIN string
Outdir string
SkipPDF bool
Publish bool
......@@ -89,7 +98,7 @@ func init() {
requestCmd.Flags().SortFlags = false
rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.config/.dfnpkitool.toml)")
rootCmd.PersistentFlags().StringVar(&globalArgs.CAName, "ca", "kit-ca-g2", "Certification Authority identifier")
rootCmd.PersistentFlags().StringVar(&globalArgs.CAName, "ca", "", "Certification Authority identifier")
rootCmd.PersistentFlags().IntVar(&globalArgs.RAId, "raid", 0, "Sub-RA identifier (usually 0 for main RA)")
viper.BindPFlag("ca", rootCmd.PersistentFlags().Lookup("ca"))
viper.BindPFlag("raid", rootCmd.PersistentFlags().Lookup("raid"))
......@@ -118,7 +127,5 @@ func initConfig() {
err := viper.ReadInConfig()
if err == nil {
fmt.Println("Using config file:", viper.ConfigFileUsed())
} else {
fmt.Println("No config file read: ", err)
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