From b478d6efe85dbaccc021bc8603535bf383ea6070 Mon Sep 17 00:00:00 2001 From: tim <24852530+cleancodeninja@users.noreply.github.com> Date: Wed, 6 Sep 2017 02:40:24 +0200 Subject: [PATCH 1/4] WIP - Command line tool --- .gitignore | 3 + config/config.go | 58 +++++++ config/config_test.go | 37 ++++ p11sign.go => p11sign.go_ | 0 sign.go | 346 +++++++++++++++++++++++++------------- verify/verify.go | 4 +- 6 files changed, 329 insertions(+), 119 deletions(-) create mode 100644 .gitignore create mode 100644 config/config.go create mode 100644 config/config_test.go rename p11sign.go => p11sign.go_ (100%) diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..23abe13 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +pdfsign +certs/ +.realize/ \ No newline at end of file diff --git a/config/config.go b/config/config.go new file mode 100644 index 0000000..3728493 --- /dev/null +++ b/config/config.go @@ -0,0 +1,58 @@ +package config + +import ( + "log" + "os" + + "github.com/BurntSushi/toml" + "github.com/asaskevich/govalidator" +) + +func init() { + govalidator.SetFieldsRequiredByDefault(true) +} + +var ( + DefaultLocation string = "./pdfsign.conf" // Default location of the config file + Settings Config // Initialized once inside Read method Settings are stored in memory. +) + +// Config is the root of the config +type Config struct { + //Info: + //Name: "Jeroen Bobbeldijk", + //Location: "Rotterdam", + //Reason: "Test", + //ContactInfo: "Geen", + //CertType: 2, + //Approval: false, + //TSA: sign.TSA{ + //URL: "http://aatl-timestamp.globalsign.com/tsa/aohfewat2389535fnasgnlg5m23", +} + +// ValidateFields validates all the fields of the config +func (c Config) ValidateFields() error { + _, err := govalidator.ValidateStruct(c) + if err != nil { + return err + } + return nil +} + +func Read(configfile string) { + + _, err := os.Stat(configfile) + if err != nil { + log.Fatal("Config file is missing: ", configfile) + } + + var c Config + if _, err := toml.DecodeFile(configfile, &c); err != nil { + } + + if err := c.ValidateFields(); err != nil { + log.Fatal("Config is not valid: ", err) + } + + Settings = c +} diff --git a/config/config_test.go b/config/config_test.go new file mode 100644 index 0000000..ad3e1b7 --- /dev/null +++ b/config/config_test.go @@ -0,0 +1,37 @@ +package config_test + +import ( + "testing" + + "bitbucket.org/digitorus/littlewatcher/src/config" + "github.com/BurntSushi/toml" + "github.com/stretchr/testify/assert" +) + +func TestConfig(t *testing.T) { + const configContent = ` +staticPath = "../static" +` + + var c config.Config + + if _, err := toml.Decode(configContent, &c); err != nil { + t.Error(err) + } + + // Root + assert.Equal(t, "../static", c.StaticPath) + +} + +func TestValidation(t *testing.T) { + const configContent = `` + + var c config.Config + if _, err := toml.Decode(configContent, &c); err != nil { + t.Error(err) + } + + err := c.ValidateFields() + assert.NotNil(t, err) +} diff --git a/p11sign.go b/p11sign.go_ similarity index 100% rename from p11sign.go rename to p11sign.go_ diff --git a/sign.go b/sign.go index 22e8931..d947829 100644 --- a/sign.go +++ b/sign.go @@ -11,134 +11,246 @@ import ( "errors" "io/ioutil" + "fmt" + "bitbucket.org/digitorus/pdfsign/revocation" "bitbucket.org/digitorus/pdfsign/sign" "bitbucket.org/digitorus/pdfsign/verify" ) +// usage is a usage function for the flags package. func usage() { - log.Fatal("Usage: sign input.pdf output.pdf certificate.crt private_key.key [chain.crt] OR verify input.pdf") + fmt.Fprintf(os.Stderr, "Pdfsign is a tool to sign and verifyPDF PDF digital signatures\n\n") + fmt.Fprintf(os.Stderr, "Usage:\n\n") + fmt.Fprintf(os.Stderr, "\tpdfsign command [arguments]\n\n") + fmt.Fprintf(os.Stderr, "The commands are:\n\n") + fmt.Fprintf(os.Stderr, "\tsign \t\tsign single PDF document\n") + fmt.Fprintf(os.Stderr, "\tverifyPDF \t\tverifyPDF signature of single PDF document\n") + fmt.Fprintf(os.Stderr, "\tserve \t\tserve web API with signing capabilities. API documentation url\n") + fmt.Fprintf(os.Stderr, "\twatch \t\tautomatically sign PDF files inside a folder\n") + fmt.Fprintf(os.Stderr, "\n\n") + flag.PrintDefaults() + os.Exit(2) } func main() { - flag.Parse() - - if len(flag.Args()) < 2 { + // if no flags provided print usage + if len(os.Args) == 1 { usage() + return } - method := flag.Arg(0) - if method != "sign" && method != "verify" { - usage() - } - - input := flag.Arg(1) - if len(input) == 0 { - usage() - } - - if method == "verify" { - input_file, err := os.Open(input) - if err != nil { - log.Fatal(err) - } - defer input_file.Close() - - resp, err := verify.Verify(input_file) - log.Println(resp) - if err != nil { - log.Println(err) - } - } - - if method == "sign" { - if len(flag.Args()) < 5 { - usage() - } - - output := flag.Arg(2) - if len(output) == 0 { - usage() - } - - certificate_data, err := ioutil.ReadFile(flag.Arg(3)) - if err != nil { - log.Fatal(err) - - } - certificate_data_block, _ := pem.Decode(certificate_data) - if certificate_data_block == nil { - log.Fatal(errors.New("failed to parse PEM block containing the certificate")) - } - - cert, err := x509.ParseCertificate(certificate_data_block.Bytes) - if err != nil { - log.Fatal(err) - } - - key_data, err := ioutil.ReadFile(flag.Arg(4)) - if err != nil { - log.Fatal(err) - } - key_data_block, _ := pem.Decode(key_data) - if key_data_block == nil { - log.Fatal(errors.New("failed to parse PEM block containing the private key")) - } - - pkey, err := x509.ParsePKCS1PrivateKey(key_data_block.Bytes) - if err != nil { - log.Fatal(err) - } - - certificate_chains := make([][]*x509.Certificate, 0) - - if flag.Arg(5) != "" { - certificate_pool := x509.NewCertPool() - if err != nil { - log.Fatal(err) - } - - chain_data, err := ioutil.ReadFile(flag.Arg(5)) - if err != nil { - log.Fatal(err) - } - - certificate_pool.AppendCertsFromPEM(chain_data) - certificate_chains, err = cert.Verify(x509.VerifyOptions{ - Intermediates: certificate_pool, - CurrentTime: cert.NotBefore, - KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageAny}, - }) - if err != nil { - log.Fatal(err) - } - } - - err = sign.SignFile(input, output, sign.SignData{ - Signature: sign.SignDataSignature{ - Info: sign.SignDataSignatureInfo{ - Name: "Jeroen Bobbeldijk", - Location: "Rotterdam", - Reason: "Test", - ContactInfo: "Geen", - Date: time.Now().Local(), - }, - CertType: 2, - Approval: false, - }, - Signer: pkey, - Certificate: cert, - CertificateChains: certificate_chains, - TSA: sign.TSA{ - URL: "http://aatl-timestamp.globalsign.com/tsa/aohfewat2389535fnasgnlg5m23", - }, - RevocationData: revocation.InfoArchival{}, - RevocationFunction: sign.DefaultEmbedRevocationStatusFunction, - }) - if err != nil { - log.Println(err) - } else { - log.Println("Signed PDF written to " + output) - } + switch os.Args[1] { + case "sign": + simpleSign() + case "verifyPDF": + verifyPDF() + case "serve": + case "watch": + default: + fmt.Printf("%q is not valid command.\n", os.Args[1]) + os.Exit(2) + } +} + +func verifyPDF() { + verifyCommand := flag.NewFlagSet("verifyPDF", flag.ExitOnError) + input := verifyCommand.String("in", "", "") + + input_file, err := os.Open(*input) + if err != nil { + log.Fatal(err) + } + defer input_file.Close() + + resp, err := verify.Verify(input_file) + log.Println(resp) + if err != nil { + log.Println(err) + } +} + +func simpleSign() { + signCommand := flag.NewFlagSet("sign", flag.ExitOnError) + input := signCommand.String("in", "", "Input PDF file") + output := signCommand.String("out", "", "Output PDF file") + crt := signCommand.String("crt", "", "Certificate") + key := signCommand.String("key", "", "Private key") + crtChain := signCommand.String("chain", "", "Certificate chain") + help := signCommand.Bool("help", false, "Show this help") + + signCommand.Parse(os.Args[2:]) + usageText := `usageText: pdfsign sign -in input.pdf -out output.pdf -crt certificate.crt -key private_key.key [-chain chain.crt]\n\n") +Description +` + if *help == true { + fmt.Println(usageText) + signCommand.PrintDefaults() + return + } + + if signCommand.Parsed() == false || *input == "" || *output == "" || *crt == "" || *key == "" { + fmt.Println(usageText) + signCommand.PrintDefaults() + os.Exit(1) + } + + certificate_data, err := ioutil.ReadFile(*crt) + if err != nil { + log.Fatal(err) + } + certificate_data_block, _ := pem.Decode(certificate_data) + if certificate_data_block == nil { + log.Fatal(errors.New("failed to parse PEM block containing the certificate")) + } + cert, err := x509.ParseCertificate(certificate_data_block.Bytes) + if err != nil { + log.Fatal(err) + } + + key_data, err := ioutil.ReadFile(*key) + if err != nil { + log.Fatal(err) + } + key_data_block, _ := pem.Decode(key_data) + if key_data_block == nil { + log.Fatal(errors.New("failed to parse PEM block containing the private key")) + } + pkey, err := x509.ParsePKCS1PrivateKey(key_data_block.Bytes) + if err != nil { + log.Fatal(err) + } + + certificate_chains, err := getCertificateChains(*crtChain, cert) + if err != nil { + log.Fatal(err) + } + + var data sign.SignData + data.Signer = pkey + data.Certificate = cert + data.CertificateChains = certificate_chains + signWithConfig(*input, *output, data) +} + +func p11sign() { + //if len(flag.Args()) < 2 { + // usage() + //} + // + //method := flag.Arg(0) + //if method != "sign" && method != "verifyPDF" { + // usage() + //} + // + //input := flag.Arg(1) + //if len(input) == 0 { + // usage() + //} + // + //if method == "verifyPDF" { + // input_file, err := os.Open(input) + // if err != nil { + // log.Fatal(err) + // } + // defer input_file.Close() + // + // resp, err := verify.Verify(input_file) + // log.Println(resp) + // if err != nil { + // log.Println(err) + // } + //} + // + //if method == "sign" { + // if len(flag.Args()) < 4 { + // usage() + // } + // + // output := flag.Arg(2) + // if len(output) == 0 { + // usage() + // } + // + // // pkcs11 key + // lib, err := pkcs11.FindLib("/lib64/libeTPkcs11.so") + // if err != nil { + // log.Fatal(err) + // } + // + // // Load Library + // ctx := pkcs11.New(lib) + // if ctx == nil { + // log.Fatal("Failed to load library") + // } + // err = ctx.Initialize() + // if err != nil { + // log.Fatal(err) + // } + // // login + // session, err := pkcs11.CreateSession(ctx, 0, flag.Arg(3), false) + // if err != nil { + // log.Fatal(err) + // } + // // select the first certificate + // cert, ckaId, err := pkcs11.GetCert(ctx, session, nil) + // if err != nil { + // log.Fatal(err) + // } + // + // // private key + // pkey, err := pkcs11.InitPrivateKey(ctx, session, ckaId) + // if err != nil { + // log.Fatal(err) + // } +} + +func getCertificateChains(crtChain string, cert *x509.Certificate) ([][]*x509.Certificate, error) { + certificate_chains := make([][]*x509.Certificate, 0) + if crtChain == "" { + return certificate_chains, nil + } + + chain_data, err := ioutil.ReadFile(crtChain) + if err != nil { + log.Fatal(err) + } + certificate_pool := x509.NewCertPool() + certificate_pool.AppendCertsFromPEM(chain_data) + certificate_chains, err = cert.Verify(x509.VerifyOptions{ + Intermediates: certificate_pool, + CurrentTime: cert.NotBefore, + KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageAny}, + }) + + return certificate_chains, err +} + +func signWithConfig(input, output string, data sign.SignData) { + err := sign.SignFile(input, output, sign.SignData{ + Signature: sign.SignDataSignature{ + Info: sign.SignDataSignatureInfo{ + Name: "Jeroen Bobbeldijk", + Location: "Rotterdam", + Reason: "Test", + ContactInfo: "Geen", + Date: time.Now().Local(), + }, + CertType: 2, + Approval: false, + }, + Signer: data.Signer, + Certificate: data.Certificate, + CertificateChains: data.CertificateChains, + TSA: sign.TSA{ + URL: "http://aatl-timestamp.globalsign.com/tsa/aohfewat2389535fnasgnlg5m23", + }, + RevocationData: revocation.InfoArchival{}, + RevocationFunction: sign.DefaultEmbedRevocationStatusFunction, + }) + if err != nil { + log.Println(err) + } else { + log.Println("Signed PDF written to " + output) } } diff --git a/verify/verify.go b/verify/verify.go index 06b7e89..8e4fd12 100644 --- a/verify/verify.go +++ b/verify/verify.go @@ -12,11 +12,11 @@ import ( "bitbucket.org/digitorus/pdf" "bitbucket.org/digitorus/pdfsign/revocation" + "crypto" "github.com/digitorus/pkcs7" "github.com/digitorus/timestamp" - "log" "golang.org/x/crypto/ocsp" - "crypto" + "log" ) type Response struct { From 201c69e414f438f6fd3bb1ae0100a44e79d888fe Mon Sep 17 00:00:00 2001 From: tim <24852530+cleancodeninja@users.noreply.github.com> Date: Wed, 6 Sep 2017 02:55:02 +0200 Subject: [PATCH 2/4] Clean up --- sign.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/sign.go b/sign.go index d947829..b8f89c7 100644 --- a/sign.go +++ b/sign.go @@ -20,12 +20,12 @@ import ( // usage is a usage function for the flags package. func usage() { - fmt.Fprintf(os.Stderr, "Pdfsign is a tool to sign and verifyPDF PDF digital signatures\n\n") + fmt.Fprintf(os.Stderr, "Pdfsign is a tool to sign and verify PDF digital signatures\n\n") fmt.Fprintf(os.Stderr, "Usage:\n\n") fmt.Fprintf(os.Stderr, "\tpdfsign command [arguments]\n\n") fmt.Fprintf(os.Stderr, "The commands are:\n\n") fmt.Fprintf(os.Stderr, "\tsign \t\tsign single PDF document\n") - fmt.Fprintf(os.Stderr, "\tverifyPDF \t\tverifyPDF signature of single PDF document\n") + fmt.Fprintf(os.Stderr, "\tverify \t\tverify signature of single PDF document\n") fmt.Fprintf(os.Stderr, "\tserve \t\tserve web API with signing capabilities. API documentation url\n") fmt.Fprintf(os.Stderr, "\twatch \t\tautomatically sign PDF files inside a folder\n") fmt.Fprintf(os.Stderr, "\n\n") @@ -43,7 +43,7 @@ func main() { switch os.Args[1] { case "sign": simpleSign() - case "verifyPDF": + case "verify": verifyPDF() case "serve": case "watch": @@ -54,7 +54,7 @@ func main() { } func verifyPDF() { - verifyCommand := flag.NewFlagSet("verifyPDF", flag.ExitOnError) + verifyCommand := flag.NewFlagSet("verify", flag.ExitOnError) input := verifyCommand.String("in", "", "") input_file, err := os.Open(*input) @@ -139,7 +139,7 @@ func p11sign() { //} // //method := flag.Arg(0) - //if method != "sign" && method != "verifyPDF" { + //if method != "sign" && method != "verify" { // usage() //} // @@ -148,7 +148,7 @@ func p11sign() { // usage() //} // - //if method == "verifyPDF" { + //if method == "verify" { // input_file, err := os.Open(input) // if err != nil { // log.Fatal(err) From 02b6ee71469534dc21e611dddd238d915eba0478 Mon Sep 17 00:00:00 2001 From: tim <24852530+cleancodeninja@users.noreply.github.com> Date: Thu, 7 Sep 2017 01:44:30 +0200 Subject: [PATCH 3/4] WIP CLI --- config/config.go | 30 +---- config/config_test.go | 15 +-- sign.go | 288 ++++++++++++++++++++++++++---------------- 3 files changed, 186 insertions(+), 147 deletions(-) diff --git a/config/config.go b/config/config.go index 3728493..15bd5d7 100644 --- a/config/config.go +++ b/config/config.go @@ -4,14 +4,10 @@ import ( "log" "os" + "bitbucket.org/digitorus/pdfsign/sign" "github.com/BurntSushi/toml" - "github.com/asaskevich/govalidator" ) -func init() { - govalidator.SetFieldsRequiredByDefault(true) -} - var ( DefaultLocation string = "./pdfsign.conf" // Default location of the config file Settings Config // Initialized once inside Read method Settings are stored in memory. @@ -19,24 +15,8 @@ var ( // Config is the root of the config type Config struct { - //Info: - //Name: "Jeroen Bobbeldijk", - //Location: "Rotterdam", - //Reason: "Test", - //ContactInfo: "Geen", - //CertType: 2, - //Approval: false, - //TSA: sign.TSA{ - //URL: "http://aatl-timestamp.globalsign.com/tsa/aohfewat2389535fnasgnlg5m23", -} - -// ValidateFields validates all the fields of the config -func (c Config) ValidateFields() error { - _, err := govalidator.ValidateStruct(c) - if err != nil { - return err - } - return nil + Info sign.SignDataSignatureInfo + TSA sign.TSA } func Read(configfile string) { @@ -50,9 +30,5 @@ func Read(configfile string) { if _, err := toml.DecodeFile(configfile, &c); err != nil { } - if err := c.ValidateFields(); err != nil { - log.Fatal("Config is not valid: ", err) - } - Settings = c } diff --git a/config/config_test.go b/config/config_test.go index ad3e1b7..3000fa0 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -20,18 +20,5 @@ staticPath = "../static" } // Root - assert.Equal(t, "../static", c.StaticPath) - -} - -func TestValidation(t *testing.T) { - const configContent = `` - - var c config.Config - if _, err := toml.Decode(configContent, &c); err != nil { - t.Error(err) - } - - err := c.ValidateFields() - assert.NotNil(t, err) + assert.Equal(t, "../static", c) } diff --git a/sign.go b/sign.go index b8f89c7..5e50cb2 100644 --- a/sign.go +++ b/sign.go @@ -13,9 +13,13 @@ import ( "fmt" + "crypto" + + "bitbucket.org/digitorus/pdfsign/config" "bitbucket.org/digitorus/pdfsign/revocation" "bitbucket.org/digitorus/pdfsign/sign" "bitbucket.org/digitorus/pdfsign/verify" + "bitbucket.org/digitorus/pkcs11" ) // usage is a usage function for the flags package. @@ -70,17 +74,52 @@ func verifyPDF() { } } +func getSignDataFlags(f *flag.FlagSet) sign.SignData { + // Signature info + name := f.String("info-name", config.Settings.Info.Name, "Signature info name") + location := f.String("info-location", config.Settings.Info.Location, "Signature info location") + reason := f.String("info-reason", config.Settings.Info.Reason, "Signature info reason") + contact := f.String("info-contact", config.Settings.Info.ContactInfo, "Signature info contact") + // Signature other + approval := f.Bool("approval", false, "Approval") + certType := f.Uint("type", 0, "Certificate type") + + // TSA + tsaUrl := f.String("tsa-url", "", "tsaUrl") + tsaUsername := f.String("tsa-username", "", "tsaUsername") + tsaPassword := f.String("tsa-password", "", "tsaPassword") + + var sd sign.SignData + sd.Signature.Info.Name = *name + sd.Signature.Info.Location = *location + sd.Signature.Info.Reason = *reason + sd.Signature.Info.ContactInfo = *contact + sd.Signature.CertType = uint32(*certType) + sd.Signature.Approval = *approval + sd.TSA.URL = *tsaUrl + sd.TSA.Username = *tsaUsername + sd.TSA.Password = *tsaPassword + return sd +} + func simpleSign() { signCommand := flag.NewFlagSet("sign", flag.ExitOnError) - input := signCommand.String("in", "", "Input PDF file") - output := signCommand.String("out", "", "Output PDF file") - crt := signCommand.String("crt", "", "Certificate") - key := signCommand.String("key", "", "Private key") - crtChain := signCommand.String("chain", "", "Certificate chain") - help := signCommand.Bool("help", false, "Show this help") - + configPath := signCommand.String("config", "", "Path to config file") signCommand.Parse(os.Args[2:]) - usageText := `usageText: pdfsign sign -in input.pdf -out output.pdf -crt certificate.crt -key private_key.key [-chain chain.crt]\n\n") + if *configPath != "" { + config.Read(*configPath) + } + + inputPath := signCommand.String("in", "", "Input PDF file") + outputPath := signCommand.String("out", "", "Output PDF file") + crtPath := signCommand.String("crt", "", "Certificate") + keyPath := signCommand.String("key", "", "Private key") + crtChainPath := signCommand.String("chain", "", "Certificate chain") + help := signCommand.Bool("help", false, "Show this help") + signData := getSignDataFlags(signCommand) + signCommand.Parse(os.Args[2:]) + + usageText := `usage: pdfsign sign -in input.pdf -out output.pdf -crt certificate.crt -key private_key.key [-chain chain.crt]") Description ` if *help == true { @@ -89,34 +128,104 @@ Description return } - if signCommand.Parsed() == false || *input == "" || *output == "" || *crt == "" || *key == "" { + if signCommand.Parsed() == false || *inputPath == "" || *outputPath == "" || *crtPath == "" || *keyPath == "" { fmt.Println(usageText) signCommand.PrintDefaults() - os.Exit(1) + os.Exit(2) } - certificate_data, err := ioutil.ReadFile(*crt) + cert, signer, err := getCertSignerPair(*crtPath, *keyPath) if err != nil { log.Fatal(err) } + + certificate_chains, err := getCertificateChains(*crtChainPath, cert) + if err != nil { + log.Fatal(err) + } + + signData.Signer = signer + signData.Certificate = cert + signData.CertificateChains = certificate_chains + if err := signWithConfig(*inputPath, *outputPath, signData); err != nil { + log.Println(err) + } else { + log.Println("Signed PDF written to " + *outputPath) + } +} + +func getCertSignerPair(crtPath, keyPath string) (*x509.Certificate, crypto.Signer, error) { + var certificate *x509.Certificate + var signer crypto.Signer + + // Set certificate + certificate_data, err := ioutil.ReadFile(crtPath) + if err != nil { + return certificate, signer, err + log.Fatal(err) + } certificate_data_block, _ := pem.Decode(certificate_data) if certificate_data_block == nil { - log.Fatal(errors.New("failed to parse PEM block containing the certificate")) + return certificate, signer, errors.New("failed to parse PEM block containing the certificate") } cert, err := x509.ParseCertificate(certificate_data_block.Bytes) if err != nil { - log.Fatal(err) + return certificate, signer, err } + certificate = cert - key_data, err := ioutil.ReadFile(*key) + // Set key + key_data, err := ioutil.ReadFile(keyPath) if err != nil { - log.Fatal(err) + return certificate, signer, err } key_data_block, _ := pem.Decode(key_data) if key_data_block == nil { - log.Fatal(errors.New("failed to parse PEM block containing the private key")) + return certificate, signer, errors.New("failed to parse PEM block containing the private key") } pkey, err := x509.ParsePKCS1PrivateKey(key_data_block.Bytes) + if err != nil { + return certificate, signer, err + } + signer = pkey + + return certificate, signer, nil +} + +func p11sign() { + signCommand := flag.NewFlagSet("sign", flag.ExitOnError) + configPath := signCommand.String("config", "", "Path to config file") + signCommand.Parse(os.Args[2:]) + if *configPath != "" { + config.Read(*configPath) + } + + input := signCommand.String("in", "", "Input PDF file") + output := signCommand.String("out", "", "Output PDF file") + libPath := signCommand.String("lib", "", "Path to PKCS11 library") + pass := signCommand.String("pass", "", "PKCS11 password") + crtChain := signCommand.String("chain", "", "Certificate chain") + help := signCommand.Bool("help", false, "Show this help") + + signCommand.Parse(os.Args[2:]) + usageText := `Usage: pdfsign sign -in input.pdf -out output.pdf -pass pkcs11-password [-chain chain.crt]") + +Description + +` + if *help == true { + fmt.Println(usageText) + signCommand.PrintDefaults() + return + } + + if signCommand.Parsed() == false || *input == "" || *output == "" || *pass == "" { + fmt.Println(usageText) + signCommand.PrintDefaults() + os.Exit(2) + } + + cert, signer, err := getP11CertSignerPair(*libPath, *pass) if err != nil { log.Fatal(err) } @@ -127,82 +236,55 @@ Description } var data sign.SignData - data.Signer = pkey data.Certificate = cert + data.Signer = signer data.CertificateChains = certificate_chains - signWithConfig(*input, *output, data) + if err := signWithConfig(*input, *output, data); err != nil { + log.Println(err) + } else { + log.Println("Signed PDF written to " + *output) + } } -func p11sign() { - //if len(flag.Args()) < 2 { - // usage() - //} - // - //method := flag.Arg(0) - //if method != "sign" && method != "verify" { - // usage() - //} - // - //input := flag.Arg(1) - //if len(input) == 0 { - // usage() - //} - // - //if method == "verify" { - // input_file, err := os.Open(input) - // if err != nil { - // log.Fatal(err) - // } - // defer input_file.Close() - // - // resp, err := verify.Verify(input_file) - // log.Println(resp) - // if err != nil { - // log.Println(err) - // } - //} - // - //if method == "sign" { - // if len(flag.Args()) < 4 { - // usage() - // } - // - // output := flag.Arg(2) - // if len(output) == 0 { - // usage() - // } - // - // // pkcs11 key - // lib, err := pkcs11.FindLib("/lib64/libeTPkcs11.so") - // if err != nil { - // log.Fatal(err) - // } - // - // // Load Library - // ctx := pkcs11.New(lib) - // if ctx == nil { - // log.Fatal("Failed to load library") - // } - // err = ctx.Initialize() - // if err != nil { - // log.Fatal(err) - // } - // // login - // session, err := pkcs11.CreateSession(ctx, 0, flag.Arg(3), false) - // if err != nil { - // log.Fatal(err) - // } - // // select the first certificate - // cert, ckaId, err := pkcs11.GetCert(ctx, session, nil) - // if err != nil { - // log.Fatal(err) - // } - // - // // private key - // pkey, err := pkcs11.InitPrivateKey(ctx, session, ckaId) - // if err != nil { - // log.Fatal(err) - // } +func getP11CertSignerPair(libPath, pass string) (*x509.Certificate, crypto.Signer, error) { + var certificate *x509.Certificate + var signer crypto.Signer + + // pkcs11 key + lib, err := pkcs11.FindLib(libPath) + if err != nil { + return certificate, signer, err + } + + // Load Library + ctx := pkcs11.New(lib) + if ctx == nil { + return certificate, signer, errors.New("Failed to load library") + } + err = ctx.Initialize() + if err != nil { + return certificate, signer, err + } + // login + session, err := pkcs11.CreateSession(ctx, 0, pass, false) + if err != nil { + return certificate, signer, err + } + // select the first certificate + cert, ckaId, err := pkcs11.GetCert(ctx, session, nil) + if err != nil { + return certificate, signer, err + } + certificate = cert + + // private key + pkey, err := pkcs11.InitPrivateKey(ctx, session, ckaId) + if err != nil { + return certificate, signer, err + } + signer = pkey + + return certificate, signer, nil } func getCertificateChains(crtChain string, cert *x509.Certificate) ([][]*x509.Certificate, error) { @@ -226,31 +308,25 @@ func getCertificateChains(crtChain string, cert *x509.Certificate) ([][]*x509.Ce return certificate_chains, err } -func signWithConfig(input, output string, data sign.SignData) { +func signWithConfig(input, output string, d sign.SignData) error { err := sign.SignFile(input, output, sign.SignData{ Signature: sign.SignDataSignature{ Info: sign.SignDataSignatureInfo{ - Name: "Jeroen Bobbeldijk", - Location: "Rotterdam", - Reason: "Test", - ContactInfo: "Geen", + Name: d.Signature.Info.Name, + Location: d.Signature.Info.Location, + Reason: d.Signature.Info.Reason, + ContactInfo: d.Signature.Info.ContactInfo, Date: time.Now().Local(), }, - CertType: 2, - Approval: false, - }, - Signer: data.Signer, - Certificate: data.Certificate, - CertificateChains: data.CertificateChains, - TSA: sign.TSA{ - URL: "http://aatl-timestamp.globalsign.com/tsa/aohfewat2389535fnasgnlg5m23", + CertType: d.Signature.CertType, + Approval: d.Signature.Approval, }, + Signer: d.Signer, + Certificate: d.Certificate, + CertificateChains: d.CertificateChains, + TSA: d.TSA, RevocationData: revocation.InfoArchival{}, RevocationFunction: sign.DefaultEmbedRevocationStatusFunction, }) - if err != nil { - log.Println(err) - } else { - log.Println("Signed PDF written to " + output) - } + return err } From 2896b6a5545c089d45004ab561abcbf912b33c08 Mon Sep 17 00:00:00 2001 From: tim <24852530+cleancodeninja@users.noreply.github.com> Date: Thu, 7 Sep 2017 11:34:01 +0200 Subject: [PATCH 4/4] WIP CLI, Add signer struct --- sign.go | 162 ++---------------------------------------------------- signer.go | 154 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 160 insertions(+), 156 deletions(-) create mode 100644 signer.go diff --git a/sign.go b/sign.go index 5e50cb2..0980cfc 100644 --- a/sign.go +++ b/sign.go @@ -4,22 +4,12 @@ import ( "flag" "log" "os" - "time" - - "crypto/x509" - "encoding/pem" - "errors" - "io/ioutil" "fmt" - "crypto" - "bitbucket.org/digitorus/pdfsign/config" - "bitbucket.org/digitorus/pdfsign/revocation" "bitbucket.org/digitorus/pdfsign/sign" "bitbucket.org/digitorus/pdfsign/verify" - "bitbucket.org/digitorus/pkcs11" ) // usage is a usage function for the flags package. @@ -134,64 +124,18 @@ Description os.Exit(2) } - cert, signer, err := getCertSignerPair(*crtPath, *keyPath) + s, err := newSigner(*crtPath, *keyPath, *crtChainPath) if err != nil { log.Fatal(err) } - certificate_chains, err := getCertificateChains(*crtChainPath, cert) - if err != nil { - log.Fatal(err) - } - - signData.Signer = signer - signData.Certificate = cert - signData.CertificateChains = certificate_chains - if err := signWithConfig(*inputPath, *outputPath, signData); err != nil { + if err := s.sign(*inputPath, *outputPath, signData); err != nil { log.Println(err) } else { log.Println("Signed PDF written to " + *outputPath) } } -func getCertSignerPair(crtPath, keyPath string) (*x509.Certificate, crypto.Signer, error) { - var certificate *x509.Certificate - var signer crypto.Signer - - // Set certificate - certificate_data, err := ioutil.ReadFile(crtPath) - if err != nil { - return certificate, signer, err - log.Fatal(err) - } - certificate_data_block, _ := pem.Decode(certificate_data) - if certificate_data_block == nil { - return certificate, signer, errors.New("failed to parse PEM block containing the certificate") - } - cert, err := x509.ParseCertificate(certificate_data_block.Bytes) - if err != nil { - return certificate, signer, err - } - certificate = cert - - // Set key - key_data, err := ioutil.ReadFile(keyPath) - if err != nil { - return certificate, signer, err - } - key_data_block, _ := pem.Decode(key_data) - if key_data_block == nil { - return certificate, signer, errors.New("failed to parse PEM block containing the private key") - } - pkey, err := x509.ParsePKCS1PrivateKey(key_data_block.Bytes) - if err != nil { - return certificate, signer, err - } - signer = pkey - - return certificate, signer, nil -} - func p11sign() { signCommand := flag.NewFlagSet("sign", flag.ExitOnError) configPath := signCommand.String("config", "", "Path to config file") @@ -204,9 +148,9 @@ func p11sign() { output := signCommand.String("out", "", "Output PDF file") libPath := signCommand.String("lib", "", "Path to PKCS11 library") pass := signCommand.String("pass", "", "PKCS11 password") - crtChain := signCommand.String("chain", "", "Certificate chain") + crtChainPath := signCommand.String("chain", "", "Certificate chain") help := signCommand.Bool("help", false, "Show this help") - + signData := getSignDataFlags(signCommand) signCommand.Parse(os.Args[2:]) usageText := `Usage: pdfsign sign -in input.pdf -out output.pdf -pass pkcs11-password [-chain chain.crt]") @@ -225,108 +169,14 @@ Description os.Exit(2) } - cert, signer, err := getP11CertSignerPair(*libPath, *pass) + s, err := newP11Signer(*libPath, *pass, *crtChainPath) if err != nil { log.Fatal(err) } - certificate_chains, err := getCertificateChains(*crtChain, cert) - if err != nil { - log.Fatal(err) - } - - var data sign.SignData - data.Certificate = cert - data.Signer = signer - data.CertificateChains = certificate_chains - if err := signWithConfig(*input, *output, data); err != nil { + if err := s.sign(*input, *output, signData); err != nil { log.Println(err) } else { log.Println("Signed PDF written to " + *output) } } - -func getP11CertSignerPair(libPath, pass string) (*x509.Certificate, crypto.Signer, error) { - var certificate *x509.Certificate - var signer crypto.Signer - - // pkcs11 key - lib, err := pkcs11.FindLib(libPath) - if err != nil { - return certificate, signer, err - } - - // Load Library - ctx := pkcs11.New(lib) - if ctx == nil { - return certificate, signer, errors.New("Failed to load library") - } - err = ctx.Initialize() - if err != nil { - return certificate, signer, err - } - // login - session, err := pkcs11.CreateSession(ctx, 0, pass, false) - if err != nil { - return certificate, signer, err - } - // select the first certificate - cert, ckaId, err := pkcs11.GetCert(ctx, session, nil) - if err != nil { - return certificate, signer, err - } - certificate = cert - - // private key - pkey, err := pkcs11.InitPrivateKey(ctx, session, ckaId) - if err != nil { - return certificate, signer, err - } - signer = pkey - - return certificate, signer, nil -} - -func getCertificateChains(crtChain string, cert *x509.Certificate) ([][]*x509.Certificate, error) { - certificate_chains := make([][]*x509.Certificate, 0) - if crtChain == "" { - return certificate_chains, nil - } - - chain_data, err := ioutil.ReadFile(crtChain) - if err != nil { - log.Fatal(err) - } - certificate_pool := x509.NewCertPool() - certificate_pool.AppendCertsFromPEM(chain_data) - certificate_chains, err = cert.Verify(x509.VerifyOptions{ - Intermediates: certificate_pool, - CurrentTime: cert.NotBefore, - KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageAny}, - }) - - return certificate_chains, err -} - -func signWithConfig(input, output string, d sign.SignData) error { - err := sign.SignFile(input, output, sign.SignData{ - Signature: sign.SignDataSignature{ - Info: sign.SignDataSignatureInfo{ - Name: d.Signature.Info.Name, - Location: d.Signature.Info.Location, - Reason: d.Signature.Info.Reason, - ContactInfo: d.Signature.Info.ContactInfo, - Date: time.Now().Local(), - }, - CertType: d.Signature.CertType, - Approval: d.Signature.Approval, - }, - Signer: d.Signer, - Certificate: d.Certificate, - CertificateChains: d.CertificateChains, - TSA: d.TSA, - RevocationData: revocation.InfoArchival{}, - RevocationFunction: sign.DefaultEmbedRevocationStatusFunction, - }) - return err -} diff --git a/signer.go b/signer.go new file mode 100644 index 0000000..abc75b9 --- /dev/null +++ b/signer.go @@ -0,0 +1,154 @@ +package main + +import ( + "crypto" + "crypto/x509" + "encoding/pem" + "errors" + "io/ioutil" + "log" + "time" + + "bitbucket.org/digitorus/pdfsign/revocation" + "bitbucket.org/digitorus/pdfsign/sign" + "bitbucket.org/digitorus/pkcs11" +) + +type signer struct { + certificate *x509.Certificate + signer crypto.Signer + certificateChains [][]*x509.Certificate +} + +func newSigner(crtPath, keyPath, crtChainPath string) (*signer, error) { + var s signer + + // Set certificate + certificate_data, err := ioutil.ReadFile(crtPath) + if err != nil { + return &s, err + log.Fatal(err) + } + certificate_data_block, _ := pem.Decode(certificate_data) + if certificate_data_block == nil { + return &s, errors.New("failed to parse PEM block containing the certificate") + } + cert, err := x509.ParseCertificate(certificate_data_block.Bytes) + if err != nil { + return &s, err + } + s.certificate = cert + + // Set key + key_data, err := ioutil.ReadFile(keyPath) + if err != nil { + return &s, err + } + key_data_block, _ := pem.Decode(key_data) + if key_data_block == nil { + return &s, errors.New("failed to parse PEM block containing the private key") + } + pkey, err := x509.ParsePKCS1PrivateKey(key_data_block.Bytes) + if err != nil { + return &s, err + } + s.signer = pkey + + certificate_chains, err := getCertificateChains(crtChainPath, cert) + if err != nil { + return &s, err + } + s.certificateChains = certificate_chains + + return &s, nil +} + +func newP11Signer(libPath, pass, crtChainPath string) (*signer, error) { + var s signer + + // pkcs11 key + lib, err := pkcs11.FindLib(libPath) + if err != nil { + return &s, err + } + + // Load Library + ctx := pkcs11.New(lib) + if ctx == nil { + return &s, errors.New("Failed to load library") + } + err = ctx.Initialize() + if err != nil { + return &s, err + } + // login + session, err := pkcs11.CreateSession(ctx, 0, pass, false) + if err != nil { + return &s, err + } + // select the first certificate + cert, ckaId, err := pkcs11.GetCert(ctx, session, nil) + if err != nil { + return &s, err + } + s.certificate = cert + + // private key + pkey, err := pkcs11.InitPrivateKey(ctx, session, ckaId) + if err != nil { + return &s, err + } + s.signer = pkey + + certificate_chains, err := getCertificateChains(crtChainPath, cert) + if err != nil { + return &s, err + } + s.certificateChains = certificate_chains + + return &s, nil +} + +func getCertificateChains(crtChainPath string, cert *x509.Certificate) ([][]*x509.Certificate, error) { + certificate_chains := make([][]*x509.Certificate, 0) + if crtChainPath == "" { + return certificate_chains, nil + } + + chain_data, err := ioutil.ReadFile(crtChainPath) + if err != nil { + log.Fatal(err) + } + certificate_pool := x509.NewCertPool() + certificate_pool.AppendCertsFromPEM(chain_data) + certificate_chains, err = cert.Verify(x509.VerifyOptions{ + Intermediates: certificate_pool, + CurrentTime: cert.NotBefore, + KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageAny}, + }) + + return certificate_chains, err +} + +func (s *signer) sign(input, output string, d sign.SignData) error { + err := sign.SignFile(input, output, sign.SignData{ + Signature: sign.SignDataSignature{ + Info: sign.SignDataSignatureInfo{ + Name: d.Signature.Info.Name, + Location: d.Signature.Info.Location, + Reason: d.Signature.Info.Reason, + ContactInfo: d.Signature.Info.ContactInfo, + Date: time.Now().Local(), + }, + CertType: d.Signature.CertType, + Approval: d.Signature.Approval, + }, + Signer: s.signer, + Certificate: s.certificate, + CertificateChains: s.certificateChains, + TSA: d.TSA, + RevocationData: revocation.InfoArchival{}, + RevocationFunction: sign.DefaultEmbedRevocationStatusFunction, + }) + return err +}