digitorus / pdfsign Goto Github PK
View Code? Open in Web Editor NEWAdd/verify Advanced Electronic Signature (AES) and Qualified Electronic Signature (QES) in PDF (usign pure Go)
License: BSD 2-Clause "Simplified" License
Add/verify Advanced Electronic Signature (AES) and Qualified Electronic Signature (QES) in PDF (usign pure Go)
License: BSD 2-Clause "Simplified" License
How does the RevocationData cache works?
Should it be cleared once in a while? Is there some expiry after which the OCSP response or CRL response is no longer valid?
In the EU checker, I see
Is the current time in the validity range of the signer's certificate?
And there, I see the entire validity of the cert. So the OCSP response can always be reused, as long as the certificate stays the same? I think it can, just making sure
I am testing this library/utility and noticed support for visual signatures, however when attempting to sign a sample PDF, the visual signature doesn't appear at least in the latest version of Adobe. Has this been tested?
When I open PDF created by this tool in Acrobat, I see "Signature is not LTV enabled"
What does that mean, how to enable LTV?
I'm trying to sign a PDF with a zip file attached. The execution looks good and no errors are shown. Also, the signed PDF size is a bit higher than the original one, so the attachment should be there, but I cannot see it with any reader after signing.
Steps to reproduce:
$ rm -rf files_original files_signed && mkdir files_original files_signed
$ pdftk simple.pdf attach_files file.zip output simple_with_file.pdf
$ pdftk simple_with_file.pdf unpack_files output files_original
$ ls -hs files_original
total 876K
876K file.zip
$ ./pdfsign -name "Jon Doe" sign simple_with_file.pdf signed.pdf certificate.crt private_key.key
2023/07/03 11:59:45 Signed PDF written to signed.pdf
$ ./pdfsign verify signed.pdf | jq . | head -25
{
"Error": "",
"DocumentInfo": {
"author": "",
"creator": "",
"hash": "",
"name": "Jon Doe",
"permission": "",
"producer": "pdfcpu v0.4.1 dev",
"subject": "",
"title": "",
"pages": 0,
"keywords": null,
"mod_date": "2023-07-03T11:59:44+02:00",
"creation_date": "2023-07-03T11:02:31+02:00"
},
"Signers": [
{
"name": "Jon Doe",
"reason": "",
"location": "",
"contact_info": "",
"valid_signature": true,
"trusted_issuer": true,
"revoked_certificate": false,
$ pdftk signed.pdf unpack_files output files_signed
$ ls -hs files_signed
total 0
$ ls -hs simple.pdf file.zip simple_with_file.pdf signed.pdf
876K file.zip 928K signed.pdf 32K simple.pdf 904K simple_with_file.pdf
Hi im trying to sign a pdf file from a certificate x509.Certificate that i get from a token or smartcard, the certificates storage fine but the output pdf is empty.... here is my code
import (
"crypto/x509"
"log"
"os"
"time"
"github.com/digitorus/pdf"
"github.com/digitorus/pdfsign/revocation"
"github.com/digitorus/pdfsign/sign"
"github.com/miekg/pkcs11"
)
func main() {
// PKCS11 Configuration
libPath := "C:\\Windows\\System32\\eps2003csp11.dll" // Library path of the token driver
label := "Macroseguridad.org" // Label of the token
pin := "password" // PIN of the token
// Open the PKCS11 library
p := pkcs11.New(libPath)
if p == nil {
log.Fatalf("Failed to load PKCS11 library: %s", libPath)
}
defer p.Destroy()
// Initialize the library
err := p.Initialize()
if err != nil {
log.Fatalf("Failed to initialize PKCS11 library: %v", err)
}
defer p.Finalize()
// Get the slot list
slots, err := p.GetSlotList(true)
if err != nil {
log.Fatalf("Failed to get slots: %v", err)
}
// Find the slot for the token
var slot uint
for _, s := range slots {
info, err := p.GetTokenInfo(s)
if err != nil {
log.Fatalf("Failed to get token info: %v", err)
}
if info.Label == label {
slot = s
break
}
}
if slot == 0 {
log.Fatalf("Failed to find slot for token: %s", label)
}
// Open a session to the slot
session, err := p.OpenSession(slot, pkcs11.CKF_SERIAL_SESSION|pkcs11.CKF_RW_SESSION)
if err != nil {
log.Fatalf("Failed to open session: %v", err)
}
defer p.CloseSession(session)
// Login to the token
err = p.Login(session, pkcs11.CKU_USER, pin)
if err != nil {
log.Fatalf("Failed to login: %v", err)
}
defer p.Logout(session)
// Configurar la plantilla para encontrar la clave privada
privateKeyTemplate := []*pkcs11.Attribute{
pkcs11.NewAttribute(pkcs11.CKA_CLASS, pkcs11.CKO_PRIVATE_KEY),
pkcs11.NewAttribute(pkcs11.CKA_KEY_TYPE, pkcs11.CKK_RSA),
pkcs11.NewAttribute(pkcs11.CKA_TOKEN, true),
pkcs11.NewAttribute(pkcs11.CKA_SIGN, true),
}
err = p.FindObjectsInit(session, privateKeyTemplate)
if err != nil {
log.Fatalf("Error al inicializar la búsqueda de la clave privada: %s", err)
}
defer p.FindObjectsFinal(session)
obj, _, err := p.FindObjects(session, 1)
if err != nil {
log.Fatalf("Error al buscar la clave privada: %s", err)
}
if len(obj) == 0 {
log.Fatal("No se encontró la clave privada")
}
privateKeyHandle := obj[0]
println(privateKeyHandle)
/****/
// Get the slot list
slots, err = p.GetSlotList(true)
if err != nil {
log.Fatalf("Failed to get slots: %v", err)
}
// Find the slot for the token
var slot2 uint
for _, s := range slots {
info, err := p.GetTokenInfo(s)
if err != nil {
log.Fatalf("Failed to get token info: %v", err)
}
if info.Label == label {
slot2 = s
break
}
}
if slot2 == 0 {
log.Fatalf("Failed to find slot for token: %s", label)
}
// Open a session to the slot
session, err = p.OpenSession(slot2, pkcs11.CKF_SERIAL_SESSION|pkcs11.CKF_RW_SESSION)
if err != nil {
log.Fatalf("Failed to open session: %v", err)
}
defer p.CloseSession(session)
// Login to the token
/*****/
println(pkcs11.CKO_CERTIFICATE)
var searchTemplate = []*pkcs11.Attribute{
pkcs11.NewAttribute(pkcs11.CKA_CLASS, pkcs11.CKO_CERTIFICATE),
}
var attrTemplate = []*pkcs11.Attribute{
pkcs11.NewAttribute(pkcs11.CKA_ID, nil),
pkcs11.NewAttribute(pkcs11.CKA_LABEL, nil),
pkcs11.NewAttribute(pkcs11.CKA_VALUE, nil),
}
err = p.FindObjectsInit(session, searchTemplate)
if err != nil {
log.Fatal("FindObjectsInit: %v", err)
}
hObjects, _, err := p.FindObjects(session, 1024)
if err != nil {
log.Fatal("FindObjectsInit: %v", err)
}
var cert *x509.Certificate
//var sffg *rsa.PrivateKey
for _, hObject := range hObjects {
attrs, err := p.GetAttributeValue(session, hObject, attrTemplate)
if err != nil {
continue
}
cert, err = x509.ParseCertificate(attrs[2].Value)
if err != nil {
continue
}
if cert.IsCA {
continue
}
}
//println(cert.DNSNames)
_ = p.FindObjectsFinal(session)
// Inicializar fichero de salida
outFile, err := os.Create("output5.pdf")
if err != nil {
panic(err)
}
defer outFile.Close()
signData := sign.SignData{}
roots := x509.NewCertPool()
roots.AddCert(cert)
opts := x509.VerifyOptions{
Roots: roots,
KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageAny},
}
ver, err := cert.Verify(opts)
println(ver)
if err != nil {
panic(err)
}
signData = sign.SignData{
Signature: sign.SignDataSignature{
Info: sign.SignDataSignatureInfo{
Name: "John Doe",
Location: "Somewhere on the globe",
Reason: "My season for siging this document",
ContactInfo: "How you like",
Date: time.Now().Local(),
},
CertType: sign.CertificationSignature,
DocMDPPerm: sign.AllowFillingExistingFormFieldsAndSignaturesPerms,
},
Certificate: cert, // x509.Certificate
CertificateChains: ver, // x509.Certificate.Verify()
TSA: sign.TSA{
URL: "https://freetsa.org/tsr",
Username: "",
Password: "",
},
// The follow options are likely to change in a future release
//
// cache revocation data when bulk signing
RevocationData: revocation.InfoArchival{},
// custom revocation lookup
RevocationFunction: sign.DefaultEmbedRevocationStatusFunction,
}
/*signData.CertificateChains = ver
signData.Signature.Info.Name = "victoria"
signData.Certificate = cert
signData.Signature.Info.Reason = "Firma"
signData.Signature.Info.Date = time.Now().Local()
// The follow options are likely to change in a future release
//
// cache revocation data when bulk signing
signData.Signature.CertType = sign.CertificationSignature */
input_file, err := os.Open("outconf.pdf")
if err != nil {
log.Fatal("Error initializing object search: %v", err)
}
defer input_file.Close()
output_file, err := os.Create("output45.pdf")
if err != nil {
log.Fatal("Error initializing object search: %v", err)
}
defer output_file.Close()
finfo, err := input_file.Stat()
if err != nil {
log.Fatal("Error initializing object search: %v", err)
}
size := finfo.Size()
println(size)
rdr, err := pdf.NewReader(input_file, size)
if err != nil {
log.Fatal("Error initializing object search: %v", err)
}
err = sign.Sign(input_file, output_file, rdr, size, signData)
}
With the following code, the signed PDF is invalid.
package main
import (
"crypto"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"fmt"
"log"
"os"
"time"
"github.com/digitorus/pdfsign/sign"
cli "github.com/jawher/mow.cli"
)
func main() {
app := cli.App("pdfsigner", "sign pdfs with ease")
app.Spec = "[--cert][--key] [--name][--location][--reason][--contact] INPUT OUTPUT"
cert := app.StringOpt("cert", "./cert.pem", "PEM-encoded certificate file")
key := app.StringOpt("key", "./privkey.pem", "PEM-encoded private key")
name := app.StringOpt("name", "Accounting", "name of the signer")
location := app.StringOpt("location", "Company\nStreet Address\nPostcode Gröditz", "PEM-encoded private key")
reason := app.StringOpt("reason", "authenticating document validity", "PEM-encoded private key")
contact := app.StringOpt("contact", "Head Accountant", "PEM-encoded private key")
sdata := sign.SignDataSignatureInfo{
Name: *name,
Location: *location,
Reason: *reason,
ContactInfo: *contact,
Date: time.Now().Local(),
}
input := app.StringArg("INPUT", "", "the file to sign")
output := app.StringArg("OUTPUT", "", "the path for the signed pdf")
app.Action = runSign(cert, key, input, output, sdata)
app.Run(os.Args)
}
func runSign(
certPath *string,
keyPath *string,
in *string,
out *string,
signatureInformation sign.SignDataSignatureInfo,
) func() {
return func() {
cert, err := readCert(*certPath)
if err != nil {
log.Fatalf("failed to parse certificate: %v", err)
}
key, err := readKey(*keyPath)
if err != nil {
log.Fatalf("failed to parse private key: %v", err)
}
err = sign.SignFile(*in, *out, sign.SignData{
Signature: sign.SignDataSignature{
Info: signatureInformation,
CertType: sign.CertificationSignature,
DocMDPPerm: sign.AllowFillingExistingFormFieldsAndSignaturesAndCRUDAnnotationsPerms,
},
Signer: key,
DigestAlgorithm: crypto.SHA256,
Certificate: cert,
RevocationFunction: sign.DefaultEmbedRevocationStatusFunction,
})
if err != nil {
log.Fatalf("failed to sign file: %v", err)
}
}
}
func readCert(certPath string) (*x509.Certificate, error) {
certContent, err := os.ReadFile(certPath)
if err != nil {
return nil, fmt.Errorf("failed to read file '%s': %w", certPath, err)
}
certData, _ := pem.Decode(certContent)
if certData == nil {
return nil, fmt.Errorf("failed to parse PEM encoded data")
}
cert, err := x509.ParseCertificate(certData.Bytes)
if err != nil {
return nil, fmt.Errorf("failed to parse certificate data: %w", err)
}
return cert, nil
}
func readKey(keyPath string) (*rsa.PrivateKey, error) {
keyContent, err := os.ReadFile(keyPath)
if err != nil {
return nil, fmt.Errorf("failed to read file '%s': %w", keyPath, err)
}
keyData, _ := pem.Decode(keyContent)
if keyData == nil {
return nil, fmt.Errorf("failed to parse PEM encoded data")
}
key, err := x509.ParsePKCS8PrivateKey(keyData.Bytes)
if err != nil {
return nil, fmt.Errorf("failed to parse private key data: %w", err)
}
switch key.(type) {
case *rsa.PrivateKey:
return key.(*rsa.PrivateKey), nil
default:
return nil, fmt.Errorf("key is not a RSA key. Sorry, but we need the ancient ones. (it's type %T)", key)
}
}
When you run pdfsign verify Acrobat_DigitalSignatures_in_PDF_signed.pdf
the error "Failed to open file: malformed PDF: malformed xref table" is returned.
Original: Acrobat_DigitalSignatures_in_PDF.pdf
Signed: Acrobat_DigitalSignatures_in_PDF_signed.pdf
I can only find suppliers offering a smartcard or USB token to create a QES. From where do I get a key and a certificate to create a valid QES signature with this tool?
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.