WIP - Command line tool

This commit is contained in:
tim
2017-09-06 02:40:24 +02:00
parent 1c9a9d60b1
commit b478d6efe8
6 changed files with 329 additions and 119 deletions

3
.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
pdfsign
certs/
.realize/

58
config/config.go Normal file
View File

@@ -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
}

37
config/config_test.go Normal file
View File

@@ -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)
}

346
sign.go
View File

@@ -11,134 +11,246 @@ import (
"errors" "errors"
"io/ioutil" "io/ioutil"
"fmt"
"bitbucket.org/digitorus/pdfsign/revocation" "bitbucket.org/digitorus/pdfsign/revocation"
"bitbucket.org/digitorus/pdfsign/sign" "bitbucket.org/digitorus/pdfsign/sign"
"bitbucket.org/digitorus/pdfsign/verify" "bitbucket.org/digitorus/pdfsign/verify"
) )
// usage is a usage function for the flags package.
func usage() { 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() { func main() {
flag.Parse() // if no flags provided print usage
if len(os.Args) == 1 {
if len(flag.Args()) < 2 {
usage() usage()
return
} }
method := flag.Arg(0) switch os.Args[1] {
if method != "sign" && method != "verify" { case "sign":
usage() simpleSign()
} case "verifyPDF":
verifyPDF()
input := flag.Arg(1) case "serve":
if len(input) == 0 { case "watch":
usage() default:
} fmt.Printf("%q is not valid command.\n", os.Args[1])
os.Exit(2)
if method == "verify" { }
input_file, err := os.Open(input) }
if err != nil {
log.Fatal(err) func verifyPDF() {
} verifyCommand := flag.NewFlagSet("verifyPDF", flag.ExitOnError)
defer input_file.Close() input := verifyCommand.String("in", "", "")
resp, err := verify.Verify(input_file) input_file, err := os.Open(*input)
log.Println(resp) if err != nil {
if err != nil { log.Fatal(err)
log.Println(err) }
} defer input_file.Close()
}
resp, err := verify.Verify(input_file)
if method == "sign" { log.Println(resp)
if len(flag.Args()) < 5 { if err != nil {
usage() log.Println(err)
} }
}
output := flag.Arg(2)
if len(output) == 0 { func simpleSign() {
usage() signCommand := flag.NewFlagSet("sign", flag.ExitOnError)
} input := signCommand.String("in", "", "Input PDF file")
output := signCommand.String("out", "", "Output PDF file")
certificate_data, err := ioutil.ReadFile(flag.Arg(3)) crt := signCommand.String("crt", "", "Certificate")
if err != nil { key := signCommand.String("key", "", "Private key")
log.Fatal(err) crtChain := signCommand.String("chain", "", "Certificate chain")
help := signCommand.Bool("help", false, "Show this help")
}
certificate_data_block, _ := pem.Decode(certificate_data) signCommand.Parse(os.Args[2:])
if certificate_data_block == nil { usageText := `usageText: pdfsign sign -in input.pdf -out output.pdf -crt certificate.crt -key private_key.key [-chain chain.crt]\n\n")
log.Fatal(errors.New("failed to parse PEM block containing the certificate")) Description
} `
if *help == true {
cert, err := x509.ParseCertificate(certificate_data_block.Bytes) fmt.Println(usageText)
if err != nil { signCommand.PrintDefaults()
log.Fatal(err) return
} }
key_data, err := ioutil.ReadFile(flag.Arg(4)) if signCommand.Parsed() == false || *input == "" || *output == "" || *crt == "" || *key == "" {
if err != nil { fmt.Println(usageText)
log.Fatal(err) signCommand.PrintDefaults()
} os.Exit(1)
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")) certificate_data, err := ioutil.ReadFile(*crt)
} if err != nil {
log.Fatal(err)
pkey, err := x509.ParsePKCS1PrivateKey(key_data_block.Bytes) }
if err != nil { certificate_data_block, _ := pem.Decode(certificate_data)
log.Fatal(err) if certificate_data_block == nil {
} log.Fatal(errors.New("failed to parse PEM block containing the certificate"))
}
certificate_chains := make([][]*x509.Certificate, 0) cert, err := x509.ParseCertificate(certificate_data_block.Bytes)
if err != nil {
if flag.Arg(5) != "" { log.Fatal(err)
certificate_pool := x509.NewCertPool() }
if err != nil {
log.Fatal(err) key_data, err := ioutil.ReadFile(*key)
} if err != nil {
log.Fatal(err)
chain_data, err := ioutil.ReadFile(flag.Arg(5)) }
if err != nil { key_data_block, _ := pem.Decode(key_data)
log.Fatal(err) if key_data_block == nil {
} log.Fatal(errors.New("failed to parse PEM block containing the private key"))
}
certificate_pool.AppendCertsFromPEM(chain_data) pkey, err := x509.ParsePKCS1PrivateKey(key_data_block.Bytes)
certificate_chains, err = cert.Verify(x509.VerifyOptions{ if err != nil {
Intermediates: certificate_pool, log.Fatal(err)
CurrentTime: cert.NotBefore, }
KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageAny},
}) certificate_chains, err := getCertificateChains(*crtChain, cert)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
}
var data sign.SignData
err = sign.SignFile(input, output, sign.SignData{ data.Signer = pkey
Signature: sign.SignDataSignature{ data.Certificate = cert
Info: sign.SignDataSignatureInfo{ data.CertificateChains = certificate_chains
Name: "Jeroen Bobbeldijk", signWithConfig(*input, *output, data)
Location: "Rotterdam", }
Reason: "Test",
ContactInfo: "Geen", func p11sign() {
Date: time.Now().Local(), //if len(flag.Args()) < 2 {
}, // usage()
CertType: 2, //}
Approval: false, //
}, //method := flag.Arg(0)
Signer: pkey, //if method != "sign" && method != "verifyPDF" {
Certificate: cert, // usage()
CertificateChains: certificate_chains, //}
TSA: sign.TSA{ //
URL: "http://aatl-timestamp.globalsign.com/tsa/aohfewat2389535fnasgnlg5m23", //input := flag.Arg(1)
}, //if len(input) == 0 {
RevocationData: revocation.InfoArchival{}, // usage()
RevocationFunction: sign.DefaultEmbedRevocationStatusFunction, //}
}) //
if err != nil { //if method == "verifyPDF" {
log.Println(err) // input_file, err := os.Open(input)
} else { // if err != nil {
log.Println("Signed PDF written to " + output) // 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)
} }
} }

View File

@@ -12,11 +12,11 @@ import (
"bitbucket.org/digitorus/pdf" "bitbucket.org/digitorus/pdf"
"bitbucket.org/digitorus/pdfsign/revocation" "bitbucket.org/digitorus/pdfsign/revocation"
"crypto"
"github.com/digitorus/pkcs7" "github.com/digitorus/pkcs7"
"github.com/digitorus/timestamp" "github.com/digitorus/timestamp"
"log"
"golang.org/x/crypto/ocsp" "golang.org/x/crypto/ocsp"
"crypto" "log"
) )
type Response struct { type Response struct {