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 }