Files
pdfsign/sign.go
Jeroen Bobbeldijk 9b09dfada8 Revert "WIP CLI, Add signer struct"
This reverts commit 2896b6a554.
2017-09-17 10:48:00 +02:00

333 lines
9.0 KiB
Go

package main
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.
func usage() {
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, "\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")
flag.PrintDefaults()
os.Exit(2)
}
func main() {
// if no flags provided print usage
if len(os.Args) == 1 {
usage()
return
}
switch os.Args[1] {
case "sign":
simpleSign()
case "verify":
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("verify", 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 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)
configPath := signCommand.String("config", "", "Path to config file")
signCommand.Parse(os.Args[2:])
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 {
fmt.Println(usageText)
signCommand.PrintDefaults()
return
}
if signCommand.Parsed() == false || *inputPath == "" || *outputPath == "" || *crtPath == "" || *keyPath == "" {
fmt.Println(usageText)
signCommand.PrintDefaults()
os.Exit(2)
}
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 {
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")
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)
}
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 {
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
}