Closed acha-bill closed 1 year ago
Welcome! Thanks for posting your first issue. The way things work here is that while customer issues are prioritized, other issues go into our backlog where they are assessed and fitted into the roadmap when suitable. If you need to get this done, consider buying a license which also enables you to use it in your commercial products. More information can be found on https://unidoc.io/
Hi @acha-bill,
Could you share a runnable code snippet and the PDF file also? This the article about editing the signed PDF https://helpx.adobe.com/acrobat/kb/edit-signed-PDF.html hope this help.
Best regards, Alip
@sampila here you go: https://github.com/acha-bill/unidoc-test
package main
import (
"bytes"
"crypto/sha512"
"encoding/hex"
"fmt"
"hash"
"image"
"io"
"net/http"
"os"
"time"
"github.com/unidoc/unipdf/v3/annotator"
"github.com/unidoc/unipdf/v3/core"
"github.com/unidoc/unipdf/v3/model"
)
func main() {
if err := sign(); err != nil {
os.Exit(1)
}
}
type signatureHandler struct {
sign func(hash string) ([]byte, error)
}
func newSignatureHandler(sign func(hash string) ([]byte, error)) *signatureHandler {
return &signatureHandler{
sign: sign,
}
}
func (t *signatureHandler) IsApplicable(sig *model.PdfSignature) bool {
return true
}
func (t *signatureHandler) Validate(sig *model.PdfSignature, digest model.Hasher) (model.SignatureValidationResult, error) {
return model.SignatureValidationResult{
IsSigned: true,
IsVerified: true,
}, nil
}
func (t *signatureHandler) InitSignature(sig *model.PdfSignature) error {
return nil
}
func (t *signatureHandler) NewDigest(sig *model.PdfSignature) (model.Hasher, error) {
return sha512.New(), nil
}
func (t *signatureHandler) Sign(sig *model.PdfSignature, hasher model.Hasher) error {
h := hasher.(hash.Hash)
digest := hex.EncodeToString(h.Sum(nil))
signature, err := t.sign(digest)
if err != nil {
return err
}
data := make([]byte, len(signature))
copy(data, signature)
sig.Contents = core.MakeHexString(string(data))
return nil
}
func sign() error {
f, err := os.Open("./input.pdf")
if err != nil {
return err
}
defer f.Close()
pdfReader, err := model.NewPdfReader(f)
if err != nil {
return err
}
now := time.Now()
handler := newSignatureHandler(func(hash string) ([]byte, error) {
return nil, nil
})
signature := model.NewPdfSignature(handler)
signature.SetDate(now, "")
// some image online
c := http.Client{}
resp, err := c.Get("https://www.freepnglogos.com/uploads/signature-png/signature-download-clip-art-20.png")
if err != nil {
return err
}
defer resp.Body.Close()
sigImage, _, err := image.Decode(resp.Body)
if err != nil {
return err
}
imgEncoder := core.NewFlateEncoder()
sigFieldOpts := annotator.NewSignatureFieldOpts()
sigFieldOpts.Rect = []float64{10, 25, 110, 75}
sigFieldOpts.WatermarkImage = sigImage
sigFieldOpts.Encoder = imgEncoder
sigField, err := annotator.NewSignatureField(signature, nil, sigFieldOpts)
if err != nil {
return err
}
sigField.T = core.MakeString("signature")
pdfAppender, err := model.NewPdfAppender(pdfReader)
if err != nil {
return err
}
if err = pdfAppender.Sign(2, sigField); err != nil {
return err
}
buffer := bytes.NewBuffer(nil)
if err = pdfAppender.Write(buffer); err != nil {
return err
}
outputFile, err := os.Create("./output.pdf")
if err != nil {
return fmt.Errorf("couldn't create output file: %s", err)
}
defer outputFile.Close()
_, err = io.Copy(outputFile, bytes.NewReader(buffer.Bytes()))
if err != nil {
return fmt.Errorf("couldn't copy document buffer to output file: %s", err)
}
return nil
}
Here is the input: input.pdf
I also noticed that just opening the file with pdfAppender modifies the document.
package main
import (
"bytes"
"fmt"
"io"
"os"
"github.com/unidoc/unipdf/v3/model"
)
func main() {
if err := open(); err != nil {
os.Exit(1)
}
}
func open() error {
f, err := os.Open("./input.pdf")
if err != nil {
return err
}
defer f.Close()
pdfReader, err := model.NewPdfReader(f)
if err != nil {
return err
}
pdfAppender, err := model.NewPdfAppender(pdfReader)
if err != nil {
return err
}
buffer := bytes.NewBuffer(nil)
if err = pdfAppender.Write(buffer); err != nil {
return err
}
outputFile, err := os.Create("./output.pdf")
if err != nil {
return fmt.Errorf("couldn't create output file: %s", err)
}
defer outputFile.Close()
_, err = io.Copy(outputFile, bytes.NewReader(buffer.Bytes()))
if err != nil {
return fmt.Errorf("couldn't copy document buffer to output file: %s", err)
}
return nil
}
Here is the input: input.pdf
Thank you for reporting, currently we investigating this issue and trying to fix this.
Adding pdfAppender.ReplaceAcroForm(nil)
just before pdfAppender.Write
fixes the above "miscellaneous change" issue.
But the original signature issue remains.
Thank you for reporting, currently we investigating this issue and trying to fix this.
Which one? The modified page after signing or the miscellanous change after read/write with appender? :)
We are trying to fixes the signature being invalid when opened on Acrobat after the operation with appender, this should fix the miscellaneous changes with appender also.
Thanks. Is there any timeline for this fix?
Hi @acha-bill,
Currently the fixes is under review process.
Thanks
Hi @sampila Is there any update on this issue or release timeline?
Hi @acha-bill,
The fixes are passed the review and planned to be available on the next version. We will inform you when we released the new version of UniPDF.
Hi @sampila A key customer project is reaching user acceptance testing in mid-January and we will fail if this bug isn't fixed by then. We need to be at least able to share a timeline for the fix. Do you expect to release the next UniPDF version by January 13th? If not, by when do you expect to release?
Hi @stephalba,
We are planning to release the new UniPDF version at this weekend (7-8 January).
Hi @sampila That's great news, thanks!
You are welcome @stephalba
Hi @stephalba and @acha-bill,
We released new UniPDF version https://github.com/unidoc/unipdf/releases/tag/v3.42.0, could you try and confirm if this issue solved?
Thanks
Hi @sampila
Thanks for the update! We will test and get back to you.
Hi @sampila
Thanks for the release. Acrobat still says the document has been modified (even though the "Page Modified" flag is no longer shown) See the comparison below
Hi @acha-bill,
Are you trying to create signature fields on signed PDF or trying to sign signed PDF?
Here's the code I use, this modified from your repo https://github.com/acha-bill/unidoc-test
package main
import (
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"crypto/x509/pkix"
"image"
"math/big"
"os"
"time"
"github.com/unidoc/unipdf/v3/annotator"
"github.com/unidoc/unipdf/v3/common/license"
"github.com/unidoc/unipdf/v3/core"
"github.com/unidoc/unipdf/v3/model"
"github.com/unidoc/unipdf/v3/model/sighandler"
)
func LoadUniPDFLicense() error {
err := license.SetMeteredKey(os.Getenv("UNIDOC_LICENSE_API_KEY"))
if err != nil {
return err
}
return nil
}
func main() {
if err := LoadUniPDFLicense(); err != nil {
os.Exit(1)
}
if err := sign(); err != nil {
os.Exit(1)
}
}
func sign() error {
f, err := os.Open("./github_issue_503.pdf")
if err != nil {
return err
}
defer f.Close()
pdfReader, err := model.NewPdfReader(f)
if err != nil {
return err
}
now := time.Now()
// Generate private key.
priv, cert, err := generateKeys()
if err != nil {
return err
}
// Sign input file.
handler, err := sighandler.NewAdobePKCS7Detached(priv, cert)
signature := model.NewPdfSignature(handler)
signature.SetDate(now, "")
if err := signature.Initialize(); err != nil {
return err
}
// Create the image
imgFile, err := os.Open("./signature-download-clip-art-20.png")
if err != nil {
return err
}
defer imgFile.Close()
sigImage, _, err := image.Decode(imgFile)
if err != nil {
return err
}
sigFieldOpts := annotator.NewSignatureFieldOpts()
sigFieldOpts.Rect = []float64{10, 25, 110, 75}
sigFieldOpts.WatermarkImage = sigImage
sigField, err := annotator.NewSignatureField(signature, nil, sigFieldOpts)
if err != nil {
return err
}
sigField.T = core.MakeString("signature")
pdfAppender, err := model.NewPdfAppender(pdfReader)
if err != nil {
return err
}
if err = pdfAppender.Sign(2, sigField); err != nil {
return err
}
if err = pdfAppender.WriteToFile("./signed-results.pdf"); err != nil {
return err
}
return nil
}
func generateKeys() (*rsa.PrivateKey, *x509.Certificate, error) {
now := time.Now()
// Generate private key.
priv, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return nil, nil, err
}
// Initialize X509 certificate template.
template := x509.Certificate{
SerialNumber: big.NewInt(1),
Subject: pkix.Name{
Organization: []string{"Test Company"},
},
NotBefore: now.Add(-time.Hour),
NotAfter: now.Add(time.Hour * 24 * 365),
KeyUsage: x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
BasicConstraintsValid: true,
}
// Generate X509 certificate.
certData, err := x509.CreateCertificate(rand.Reader, &template, &template, priv.Public(), priv)
if err != nil {
return nil, nil, err
}
cert, err := x509.ParseCertificate(certData)
if err != nil {
return nil, nil, err
}
return priv, cert, nil
}
Results signed-results.pdf
Hi, I'm trying to sign
I found out that it works when I use sighandler.NewAdobePKCS7Detached
but does not work with a custom handler.
type CustomSignatureHandler struct {
}
func NewCustomSignatureHandler() *CustomSignatureHandler {
return &CustomSignatureHandler{}
}
func (s *CustomSignatureHandler) IsApplicable(sig *model.PdfSignature) bool {
return true
}
func (s *CustomSignatureHandler) Validate(sig *model.PdfSignature, digest model.Hasher) (model.SignatureValidationResult, error) {
return model.SignatureValidationResult{
IsSigned: true,
IsVerified: true,
}, nil
}
func (s *CustomSignatureHandler) InitSignature(sig *model.PdfSignature) error {
return nil
}
func (s *CustomSignatureHandler) NewDigest(sig *model.PdfSignature) (model.Hasher, error) {
return sha512.New(), nil
}
func (s *CustomSignatureHandler) Sign(sig *model.PdfSignature, hasher model.Hasher) error {
sig.Contents = core.MakeHexString("test")
return nil
}
...
handler := NewCustomSignatureHandler()
signature := model.NewPdfSignature(handler)
...
Even if the handler implemention is not correct, I still don't expect the input document to be modified.
Could you try this code and see if the results is correct?
package main
import (
"bytes"
"crypto/sha512"
"errors"
"image"
"io/ioutil"
"log"
"os"
"time"
"github.com/unidoc/unipdf/v3/annotator"
"github.com/unidoc/unipdf/v3/common"
"github.com/unidoc/unipdf/v3/common/license"
"github.com/unidoc/unipdf/v3/core"
"github.com/unidoc/unipdf/v3/model"
"github.com/unidoc/unipdf/v3/model/sighandler"
)
func LoadUniPDFLicense() error {
err := license.SetMeteredKey(os.Getenv("UNIDOC_LICENSE_API_KEY"))
if err != nil {
return err
}
common.SetLogger(common.NewConsoleLogger(common.LogLevelInfo))
return nil
}
var now = time.Now()
func main() {
if err := LoadUniPDFLicense(); err != nil {
os.Exit(1)
}
if err := sign(); err != nil {
log.Printf("err: %v", err.Error())
os.Exit(1)
}
}
func sign() error {
inputPath := "./github_issue_503.pdf"
outputPath := "./results-sign-signed.pdf"
// Generate PDF file signed with empty signature.
handler, err := sighandler.NewEmptyAdobePKCS7Detached(8192)
if err != nil {
log.Fatal("Fail: %v\n", err)
}
pdfData, signature, err := generateSignedFile(inputPath, handler)
if err != nil {
log.Fatal("Fail: %v\n", err)
}
// Parse signature byte range.
byteRange, err := parseByteRange(signature.ByteRange)
if err != nil {
log.Fatal("Fail: %v\n", err)
}
// This would be the time to send the PDF buffer to a signing device or
// signing web service and get back the signature. We will simulate this by
// signing the PDF using UniDoc and returning the signature data.
signatureData, err := getExternalSignature(inputPath)
if err != nil {
log.Fatal("Fail: %v\n", err)
}
// Apply external signature to the PDF data buffer.
// Overwrite the generated empty signature with the signature
// bytes retrieved from the external service.
sigBytes := make([]byte, 8192)
copy(sigBytes, signatureData)
sig := core.MakeHexString(string(sigBytes)).WriteString()
copy(pdfData[byteRange[1]:byteRange[2]], []byte(sig))
// Write output file.
if err := ioutil.WriteFile(outputPath, pdfData, os.ModePerm); err != nil {
log.Fatal("Fail: %v\n", err)
}
log.Printf("PDF file successfully signed. Output path: %s\n", outputPath)
return nil
}
func generateSignedFile(inputPath string, handler model.SignatureHandler) ([]byte, *model.PdfSignature, error) {
// Create reader.
file, err := os.Open(inputPath)
if err != nil {
return nil, nil, err
}
defer file.Close()
reader, err := model.NewPdfReader(file)
if err != nil {
return nil, nil, err
}
// Create appender.
appender, err := model.NewPdfAppender(reader)
if err != nil {
return nil, nil, err
}
signature := model.NewPdfSignature(handler)
signature.SetName("Signer 2")
signature.SetDate(now, "")
if err := signature.Initialize(); err != nil {
log.Fatal("Fail: %v\n", err)
}
// Create the image
imgFile, err := os.Open("./signature-download-clip-art-20.png")
if err != nil {
return nil, nil, err
}
defer imgFile.Close()
sigImage, _, err := image.Decode(imgFile)
if err != nil {
return nil, nil, err
}
sigFieldOpts := annotator.NewSignatureFieldOpts()
sigFieldOpts.Rect = []float64{10, 25, 110, 200}
sigFieldOpts.Image = sigImage
sigField, err := annotator.NewSignatureField(signature, nil, sigFieldOpts)
if err != nil {
return nil, nil, err
}
sigField.T = core.MakeString("newSignature")
if err = appender.Sign(2, sigField); err != nil {
return nil, nil, err
}
// Write PDF file to buffer.
pdfBuf := bytes.NewBuffer(nil)
if err = appender.Write(pdfBuf); err != nil {
return nil, nil, err
}
return pdfBuf.Bytes(), signature, nil
}
// getExternalSignature simulates an external service which signs the specified
// PDF file and returns its signature.
func getExternalSignature(inputPath string) ([]byte, error) {
// Initiate custom signature handler.
handler := NewCustomSignatureHandler()
_, signature, err := generateSignedFile(inputPath, handler)
if err != nil {
return nil, err
}
return signature.Contents.Bytes(), nil
}
// parseByteRange parses the ByteRange value of the signature field.
func parseByteRange(byteRange *core.PdfObjectArray) ([]int64, error) {
if byteRange == nil {
return nil, errors.New("byte range cannot be nil")
}
if byteRange.Len() != 4 {
return nil, errors.New("invalid byte range length")
}
s1, err := core.GetNumberAsInt64(byteRange.Get(0))
if err != nil {
return nil, errors.New("invalid byte range value")
}
l1, err := core.GetNumberAsInt64(byteRange.Get(1))
if err != nil {
return nil, errors.New("invalid byte range value")
}
s2, err := core.GetNumberAsInt64(byteRange.Get(2))
if err != nil {
return nil, errors.New("invalid byte range value")
}
l2, err := core.GetNumberAsInt64(byteRange.Get(3))
if err != nil {
return nil, errors.New("invalid byte range value")
}
return []int64{s1, s1 + l1, s2, s2 + l2}, nil
}
// Custom Signature handler.
type CustomSignatureHandler struct {
}
func NewCustomSignatureHandler() *CustomSignatureHandler {
return &CustomSignatureHandler{}
}
func (s *CustomSignatureHandler) IsApplicable(sig *model.PdfSignature) bool {
return true
}
func (s *CustomSignatureHandler) Validate(sig *model.PdfSignature, digest model.Hasher) (model.SignatureValidationResult, error) {
return model.SignatureValidationResult{
IsSigned: true,
IsVerified: true,
}, nil
}
func (s *CustomSignatureHandler) InitSignature(sig *model.PdfSignature) error {
return nil
}
func (s *CustomSignatureHandler) NewDigest(sig *model.PdfSignature) (model.Hasher, error) {
return sha512.New(), nil
}
func (s *CustomSignatureHandler) Sign(sig *model.PdfSignature, hasher model.Hasher) error {
sig.Contents = core.MakeHexString("test")
return nil
}
Here's the results I got
results-sign-signed.pdf
I get the same outut as you and it is correct. I'll look for differences between the code above and mine.
Thanks!
You need to use the, which creates a spaces allocation for empty signature.
handler, err := sighandler.NewEmptyAdobePKCS7Detached(8192)
and
// parseByteRange parses the ByteRange value of the signature field.
func parseByteRange(byteRange *core.PdfObjectArray) ([]int64, error) {
if byteRange == nil {
return nil, errors.New("byte range cannot be nil")
}
if byteRange.Len() != 4 {
return nil, errors.New("invalid byte range length")
}
s1, err := core.GetNumberAsInt64(byteRange.Get(0))
if err != nil {
return nil, errors.New("invalid byte range value")
}
l1, err := core.GetNumberAsInt64(byteRange.Get(1))
if err != nil {
return nil, errors.New("invalid byte range value")
}
s2, err := core.GetNumberAsInt64(byteRange.Get(2))
if err != nil {
return nil, errors.New("invalid byte range value")
}
l2, err := core.GetNumberAsInt64(byteRange.Get(3))
if err != nil {
return nil, errors.New("invalid byte range value")
}
return []int64{s1, s1 + l1, s2, s2 + l2}, nil
}
Hope it helps.
Thanks! 💪🏾
You are welcome, we closing this issue for now, feel free to re-open the issue if this still not resolved.
Description
When I sign a document that has existing signature, the existing signatures become invalid. Adobe Acrobat says "page modified"![Screenshot 2022-12-12 at 18 15 31](https://user-images.githubusercontent.com/57879913/207110234-d034927c-f014-46f8-8589-ef966ddb2740.png)
Expected Behavior
Existing signatures should still be valid
Version
v3.40.0
Actual Behavior
Attachments
Include a self-contained reproducible code snippet and PDF file that demonstrates the issue. input.pdf
Reproducable repo: https://github.com/acha-bill/unidoc-test