Giter Site home page Giter Site logo

digitorus / pdfsign Goto Github PK

View Code? Open in Web Editor NEW
62.0 62.0 15.0 1005 KB

Add/verify Advanced Electronic Signature (AES) and Qualified Electronic Signature (QES) in PDF (usign pure Go)

License: BSD 2-Clause "Simplified" License

Go 100.00%
advanced-electronic-signature aes eseal esignature go golang pades pdf qes qualified-electronic-signature signature signing

pdfsign's People

Contributors

dependabot[bot] avatar vanbroup avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

pdfsign's Issues

Should the revocation.RevocationData expire?

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

Visual signature support

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?

"Signature is not LTV enabled"

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?

Signing a PDF with attachment results in a signed PDF without attachment

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

output empty pdf, any ideas?

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)

}

Signing a PDF with multiple revisions results in malformed xref table

With the following code, the signed PDF is invalid.

main.go 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

Supplier of QES key/certificate

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?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.