Sign pdf files in Go (Golang) using digitalorus/pdfsign

WBOY
Release: 2024-02-09 10:00:11
forward
596 people have browsed it

使用 digitalorus/pdfsign 在 Go (Golang) 中签署 pdf 文件

Signing PDF files in Go language is a common need, and this function can be easily achieved using the digitalorus/pdfsign library. PHP editor Youzi will introduce you to how to use this library. Whether in business applications or personal projects, signing PDF files is a common operation. The digitalorus/pdfsign library provides a simple and easy-to-use interface, making signing PDF files in Go language simple and fast. Through this article, you will learn how to use the digitalorus/pdfsign library in the Go language to complete the signing operation of PDF files. Let’s explore together!

Question content

In go (golang), I need to sign a pdf document, but unlike other languages, there isn't any library that makes the job easier. I found a few paid ones, but they weren't an option.

First, I have a PKCS certificate (.p12) from which I have extracted the private key and x509 certificate using this package: https://pkg.go.dev/software.sslmate.com/src/go -pkcs12

But when I want to sign a pdf document, I'm stuck because I don't know how to properly pass parameters to a function that does such an operation. The package used is https://pkg.go.dev/github.com/digitorus/pdfsign

My complete code is:

package main

import (
    "crypto"
    "fmt"
    "os"
    "time"

    "github.com/digitorus/pdf"
    "github.com/digitorus/pdfsign/revocation"
    "github.com/digitorus/pdfsign/sign"
    gopkcs12 "software.sslmate.com/src/go-pkcs12"
)

func main() { 
    certBytes, err := os.ReadFile("certificate.p12") 

    if err != nil { 
        fmt.Println(err) 
        return
    }

    privateKey, certificate, chainCerts, err := gopkcs12.DecodeChain(certBytes, "MyPassword")

    if err != nil {
        fmt.Println(err)
        return
    }

    input_file, err := os.Open("input-file.pdf")
    if err != nil {
        fmt.Println(err)
        return
    }
    defer input_file.Close()
        
    output_file, err := os.Create("output-file.pdf")
    if err != nil {
        fmt.Println(err)
        return
    }
    defer output_file.Close()
    
    finfo, err := input_file.Stat()
    if err != nil {
        fmt.Println(err)
        return
    }
    size := finfo.Size()
    
    rdr, err := pdf.NewReader(input_file, size)
    if err != nil {
        fmt.Println(err)
        return
    }

    err = sign.Sign(input_file, output_file, rdr, size, 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,
        },
        Signer:            privateKey,    // crypto.Signer
        DigestAlgorithm:   crypto.SHA256, // hash algorithm for the digest creation
        Certificate:       certificate,   // x509.Certificate
        CertificateChains: chainCerts,    // 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,
    })
    if err != nil {
        fmt.Println(err)
    } else {
        fmt.Println("Signed PDF written to output.pdf")
    }
}
Copy after login

To be precise, they are the Signer and CertificateChains parameters of my question. I don't know how to use privateKey and chainCerts variables correctly.

The message error is:

  • Cannot use privateKey (variable of type interface{}) as a crypto.Signer value in a struct literal: interface{} does not implement crypto.Signer (missing Public method)
  • Unable to use chainCertificates (variable of type []*x509.Certificate) as the [][]*x509.Certificate value in a structure literal

I'm new to this language, so I still don't understand the in-depth concepts and data types.

Thanks for telling me what else I should do or what steps are missing to be successful. Or if anyone knows how I can sign a pdf based on a pkcs certificate.

Workaround

Signing a PDF using a digital signature involves generating a pair of keys using public key cryptography. The private key is used to encrypt the data related to the signature and only the signer can access it, while the public key is used to decrypt the signature data for verification. If it is not issued by a trusted certificate authority, the said public key certificate must Add it to the certificate store to make it trusted. In the given example, this signature data is stored inside a structure called sign.SignData, which is part of the pdfsign library and requires an x509 certificate and a signer that implements the crypto.Signer interface.

The first step is to generate a pair of keys using the crypto/ecdsa package in the Go standard library. GenerateKey will save the private and public keys into the privateKey variable. This returned privateKey variable implements the crypto.Signer interface, which is required to solve the problem you are currently facing.

You can check this by reading line 142 of the ecdsa.go code.

You are currently using gopkcs12.DecodeChain to return the private key, but it does not implement the crypto.Signer interface, so you will get an error. You may need to implement a custom one, but that's another question.

concept:

ECDSA stands for Elliptic Curve Digital Signature Algorithm. It is a public key encryption algorithm used for digital signatures. See the Go standard library documentation and the NIST website for more information.

NIST P-384: P-384 is one of the elliptic curves recommended by the National Institute of Standards and Technology (NIST) with a key length of 384 bits. For more information about digital signatures and more recommended elliptic curves, see the NIST website. I use the P-384 as a working solution.

第二步是使用Go标准库中的crypto/x509包通过其链生成器生成x509证书和证书链。这些是您在问题中寻求帮助的特定变量,但不属于您在错误消息中可以清楚看到的预期类型。只需遵循 lib 指令并使用 x509.Certificate.Verify() 就像我在工作解决方案中所做的那样,这将返回正确的类型 [][]*x509.Certificate。

请参阅 Go 标准库文档以获取更多信息。

第三步是打开输入 pdf 文件并使用 Go 标准库中的 os 包创建输出 pdf 文件。

第四步实际上是使用 digitalorus/pdfsign 库对 pdf 文件进行签名。

这是我今天编码和测试的一个有效解决方案,旨在让您回到正轨,进行一些研究并根据您的需求进行修改:

package main

import (
    "crypto"
    "crypto/ecdsa"
    "crypto/elliptic"
    "crypto/rand"
    "crypto/x509"
    "crypto/x509/pkix"
    "fmt"
    "github.com/digitorus/pdf"
    "github.com/digitorus/pdfsign/revocation"
    "github.com/digitorus/pdfsign/sign"
    "log"
    "math/big"
    "os"
    "time"
)

func main() {
    // First step

    privateKey, err := ecdsa.GenerateKey(elliptic.P384(), rand.Reader)

    if err != nil {
        panic(err)
    }

    // Second step

    x509RootCertificate := &x509.Certificate{
        SerialNumber: big.NewInt(2023),
        Subject: pkix.Name{
            Organization:  []string{"Your Organization"},
            Country:       []string{"Your Country"},
            Province:      []string{"Your Province"},
            Locality:      []string{"Your Locality"},
            StreetAddress: []string{"Your Street Address"},
            PostalCode:    []string{"Your Postal Code"},
        },
        NotBefore:             time.Now(),
        NotAfter:              time.Now().AddDate(10, 0, 0),
        IsCA:                  true,
        ExtKeyUsage:           []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},
        KeyUsage:              x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
        BasicConstraintsValid: true,
    }

    rootCertificateBytes, err := x509.CreateCertificate(rand.Reader, x509RootCertificate, x509RootCertificate, &privateKey.PublicKey, privateKey)

    if err != nil {
        panic(err)
    }

    rootCertificate, err := x509.ParseCertificate(rootCertificateBytes)

    if err != nil {
        panic(err)
    }

    roots := x509.NewCertPool()

    roots.AddCert(rootCertificate)

    certificateChain, err := rootCertificate.Verify(x509.VerifyOptions{
        Roots: roots,
    })

    if err != nil {
        panic(err)
    }

    // Third step

    outputFile, err := os.Create("output.pdf")

    if err != nil {
        panic(err)
    }

    defer func(outputFile *os.File) {
        err = outputFile.Close()

        if err != nil {
            log.Println(err)
        }

        fmt.Println("output file closed")
    }(outputFile)

    inputFile, err := os.Open("input.pdf")

    if err != nil {
        panic(err)
    }

    defer func(inputFile *os.File) {
        err = inputFile.Close()

        if err != nil {
            log.Println(err)
        }

        fmt.Println("input file closed")
    }(inputFile)

    fileInfo, err := inputFile.Stat()

    if err != nil {
        panic(err)
    }

    size := fileInfo.Size()

    pdfReader, err := pdf.NewReader(inputFile, size)

    if err != nil {
        panic(err)
    }

    // Fourth step

    err = sign.Sign(inputFile, outputFile, pdfReader, size, sign.SignData{
        Signature: sign.SignDataSignature{
            Info: sign.SignDataSignatureInfo{
                Name:        "Your name",
                Location:    "Your location",
                Reason:      "Your reason",
                ContactInfo: "Your contact info",
                Date:        time.Now().Local(),
            },
            CertType:   sign.CertificationSignature,
            DocMDPPerm: sign.AllowFillingExistingFormFieldsAndSignaturesPerms,
        },
        Signer:            privateKey,       // crypto.Signer
        DigestAlgorithm:   crypto.SHA256,    // hash algorithm for the digest creation
        Certificate:       rootCertificate,  // x509.Certificate
        CertificateChains: certificateChain, // x509.Certificate.Verify()
        TSA: sign.TSA{
            URL:      "",
            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,
    })

    if err != nil {
        log.Println(err)
    } else {
        log.Println("pdf signed")
    }
}
Copy after login

结果:

go run main.go

2023/12/01 21:53:37 pdf signed
input file closed
output file closed
Copy after login

The above is the detailed content of Sign pdf files in Go (Golang) using digitalorus/pdfsign. For more information, please follow other related articles on the PHP Chinese website!

Related labels:
source:stackoverflow.com
Statement of this Website
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn
Popular Tutorials
More>
Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template
About us Disclaimer Sitemap
php.cn:Public welfare online PHP training,Help PHP learners grow quickly!