Maison  >  Article  >  développement back-end  >  Signez des fichiers PDF dans Go (Golang) en utilisant digitalorus/pdfsign

Signez des fichiers PDF dans Go (Golang) en utilisant digitalorus/pdfsign

WBOY
WBOYavant
2024-02-09 10:00:11601parcourir

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

La signature de fichiers PDF en langage Go est un besoin courant, et cette fonction peut être facilement réalisée à l'aide de la bibliothèque digitalorus/pdfsign. L'éditeur PHP Youzi vous présentera comment utiliser cette bibliothèque. Que ce soit dans des applications métiers ou des projets personnels, signer des fichiers PDF est une opération courante. La bibliothèque digitalorus/pdfsign fournit une interface simple et facile à utiliser, rendant la signature de fichiers PDF en langage Go simple et rapide. A travers cet article, vous apprendrez à utiliser la bibliothèque digitalorus/pdfsign en langage Go pour réaliser l'opération de signature des fichiers PDF. Explorons ensemble !

Contenu de la question

En go (golang), j'ai besoin de signer un document pdf, mais contrairement à d'autres langages, il n'y a pas de bibliothèque pour faciliter le travail. J'en ai trouvé quelques payants, mais ce n'était pas une option.

Tout d'abord, j'ai un certificat PKCS (.p12) dont j'ai extrait la clé privée et le certificat x509 à l'aide de ce package : https://pkg.go.dev/software.sslmate.com/src/go -pkcs12

Mais quand je veux signer un document pdf, je suis bloqué car je ne sais pas comment passer correctement des paramètres à une fonction qui fait une telle opération. Le package utilisé est https://pkg.go.dev/github.com/digitorus/pdfsign

Mon code complet est :

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")
    }
}

Pour être précis, ce sont les paramètres Signer et CertificateChains de ma question. Je ne sais pas comment utiliser correctement les variables privateKey et chainCerts.

Le message d'erreur est :

  • Impossible d'utiliser privateKey (variable de type interface{}) comme valeur crypto.Signer dans un littéral de structure : interface{} n'implémente pas crypto.Signer (méthode publique manquante)
  • Impossible d'utiliser chainCertificates (variable de type []*x509.Certificate) comme valeur [][]*x509.Certificate dans une structure littérale

Je suis nouveau dans ce langage, donc je ne comprends toujours pas les concepts approfondis et les types de données.

Merci de m'avoir dit ce que je devrais faire d'autre ou quelles étapes il me manque pour réussir. Ou si quelqu'un sait comment signer un pdf basé sur un certificat pkcs.

Solution de contournement

Signer un PDF à l'aide d'une signature numérique implique de générer une paire de clés à l'aide d'une cryptographie à clé publique. La clé privée est utilisée pour crypter les données liées à la signature et seul le signataire peut y accéder, tandis que la clé publique est utilisée pour déchiffrer les données de signature à des fins de vérification. Si elle n'est pas émise par une autorité de certification de confiance, ladite clé publique est utilisée. Le certificat doit l'ajouter au magasin de certificats pour le rendre fiable. Dans l'exemple donné, ces données de signature sont stockées dans une structure appelée sign.SignData, qui fait partie de la bibliothèque pdfsign et nécessite un certificat x509 et un signataire qui implémente l'interface crypto.Signer.

La première étape consiste à générer une paire de clés à l'aide du package crypto/ecdsa dans la bibliothèque standard Go. GenerateKey enregistrera les clés privées et publiques dans la variable privateKey. Cette variable privateKey renvoyée implémente l'interface crypto.Signer, qui est nécessaire pour résoudre le problème auquel vous êtes actuellement confronté.

Vous pouvez le vérifier en lisant la ligne 142 du code ecdsa.go.

Vous utilisez actuellement gopkcs12.DecodeChain pour renvoyer la clé privée, mais il n'implémente pas l'interface crypto.Signer, d'où l'erreur. Vous devrez peut-être en implémenter un personnalisé, mais c'est une autre question.

Concept :

ECDSA signifie Elliptic Curve Digital Signature Algorithm. Il s'agit d'un algorithme de cryptage à clé publique utilisé pour les signatures numériques. Consultez la documentation de la bibliothèque standard Go et le site Web du NIST pour plus d'informations.

NIST P-384 : P-384 est l'une des courbes elliptiques recommandées par le National Institute of Standards and Technology (NIST) avec une longueur de clé de 384 bits. Pour plus d'informations sur les signatures numériques et les courbes elliptiques recommandées, consultez le site Web du NIST. J'utilise le P-384 comme solution de travail.

第二步是使用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")
    }
}

结果:

go run main.go

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

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer