Files
pdfsign/sign.go
Tryanks 0d83ec6b6c Migrate repository to new hosting and cleanup CI/CD files
Switched module path and imports to gitea.tryanks.com. Removed GitHub-specific files like workflows, dependabot, and devcontainer as part of migration. This streamlines the codebase for the new hosting environment.
2025-05-12 22:33:48 +08:00

231 lines
5.3 KiB
Go

package main
import (
"crypto"
"crypto/x509"
"encoding/json"
"encoding/pem"
"errors"
"flag"
"fmt"
"log"
"os"
"time"
"gitea.tryanks.com/Tryanks/pdfsign/sign"
"gitea.tryanks.com/Tryanks/pdfsign/verify"
)
var (
infoName, infoLocation, infoReason, infoContact, tsa string
certType string
)
func usage() {
flag.PrintDefaults()
fmt.Println("\nExample usage:")
fmt.Printf("\t%s -name \"Jon Doe\" sign input.pdf output.pdf certificate.crt private_key.key [chain.crt]\n", os.Args[0])
fmt.Printf("\t%s -certType \"CertificationSignature\" -name \"Jon Doe\" sign input.pdf output.pdf certificate.crt private_key.key [chain.crt]\n", os.Args[0])
fmt.Printf("\t%s -certType \"TimeStampSignature\" input.pdf output.pdf\n", os.Args[0])
fmt.Printf("\t%s verify input.pdf\n", os.Args[0])
os.Exit(1)
}
func parseCertType(s string) (sign.CertType, error) {
switch s {
case sign.CertificationSignature.String():
return sign.CertificationSignature, nil
case sign.ApprovalSignature.String():
return sign.ApprovalSignature, nil
case sign.UsageRightsSignature.String():
return sign.UsageRightsSignature, nil
case sign.TimeStampSignature.String():
return sign.TimeStampSignature, nil
default:
return 0, fmt.Errorf("invalid certType value")
}
}
func main() {
flag.StringVar(&infoName, "name", "", "Name of the signatory")
flag.StringVar(&infoLocation, "location", "", "Location of the signatory")
flag.StringVar(&infoReason, "reason", "", "Reason for signing")
flag.StringVar(&infoContact, "contact", "", "Contact information for signatory")
flag.StringVar(&tsa, "tsa", "https://freetsa.org/tsr", "URL for Time-Stamp Authority")
flag.StringVar(&certType, "certType", "CertificationSignature", "Type of the certificate (CertificationSignature, ApprovalSignature, UsageRightsSignature, TimeStampSignature)")
flag.Parse()
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()
}
switch method {
case "verify":
verifyPDF(input)
case "sign":
signPDF(input)
}
}
func verifyPDF(input string) {
inputFile, err := os.Open(input)
if err != nil {
log.Fatal(err)
}
defer inputFile.Close()
resp, err := verify.File(inputFile)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
jsonData, err := json.Marshal(resp)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
fmt.Println(string(jsonData))
}
func signPDF(input string) {
certTypeValue, err := parseCertType(certType)
if err != nil {
log.Fatal(err)
}
if certTypeValue == sign.TimeStampSignature {
output := flag.Arg(2)
if len(output) == 0 {
usage()
}
timeStampPDF(input, output, tsa)
return
}
if len(flag.Args()) < 5 {
usage()
}
output := flag.Arg(2)
if len(output) == 0 {
usage()
}
cert, pkey, certificateChains := loadCertificatesAndKey(flag.Arg(3), flag.Arg(4), flag.Arg(5))
err = sign.SignFile(input, output, sign.SignData{
Signature: sign.SignDataSignature{
Info: sign.SignDataSignatureInfo{
Name: infoName,
Location: infoLocation,
Reason: infoReason,
ContactInfo: infoContact,
Date: time.Now().Local(),
},
CertType: certTypeValue,
DocMDPPerm: sign.AllowFillingExistingFormFieldsAndSignaturesPerms,
},
Signer: pkey,
DigestAlgorithm: crypto.SHA256,
Certificate: cert,
CertificateChains: certificateChains,
TSA: sign.TSA{
URL: tsa,
},
})
if err != nil {
log.Println(err)
} else {
log.Println("Signed PDF written to " + output)
}
}
func loadCertificatesAndKey(certPath, keyPath, chainPath string) (*x509.Certificate, crypto.Signer, [][]*x509.Certificate) {
certData, err := os.ReadFile(certPath)
if err != nil {
log.Fatal(err)
}
certBlock, _ := pem.Decode(certData)
if certBlock == nil {
log.Fatal(errors.New("failed to parse PEM block containing the certificate"))
}
cert, err := x509.ParseCertificate(certBlock.Bytes)
if err != nil {
log.Fatal(err)
}
keyData, err := os.ReadFile(keyPath)
if err != nil {
log.Fatal(err)
}
keyBlock, _ := pem.Decode(keyData)
if keyBlock == nil {
log.Fatal(errors.New("failed to parse PEM block containing the private key"))
}
pkey, err := x509.ParsePKCS1PrivateKey(keyBlock.Bytes)
if err != nil {
log.Fatal(err)
}
var certificateChains [][]*x509.Certificate
if chainPath != "" {
certificateChains = loadCertificateChain(chainPath, cert)
}
return cert, pkey, certificateChains
}
func loadCertificateChain(chainPath string, cert *x509.Certificate) [][]*x509.Certificate {
chainData, err := os.ReadFile(chainPath)
if err != nil {
log.Fatal(err)
}
certificatePool := x509.NewCertPool()
certificatePool.AppendCertsFromPEM(chainData)
certificateChains, err := cert.Verify(x509.VerifyOptions{
Intermediates: certificatePool,
CurrentTime: cert.NotBefore,
KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageAny},
})
if err != nil {
log.Fatal(err)
}
return certificateChains
}
func timeStampPDF(input, output, tsa string) {
err := sign.SignFile(input, output, sign.SignData{
Signature: sign.SignDataSignature{
CertType: sign.TimeStampSignature,
},
DigestAlgorithm: crypto.SHA256,
TSA: sign.TSA{
URL: tsa,
},
})
if err != nil {
log.Println(err)
} else {
log.Println("Signed PDF written to " + output)
}
}