m32/endesive

Embed Signed hash and certificate to PDF

shyjuk opened this issue · 12 comments

Is there any way to sign a PDF using endesive if I have pdf file, signed hash of pdf file and certificate with me.

m32 commented

Unfortunately no.

@m32 Thank you for your reply. I will explain the question in another way. I'm using a signing service provider's web service which accepts pdf file hash as input and it releases signed hash and public certificate as output. I'm looking for a library to embed the signed hash and certificate to the pdf. Does endesive support this? or do you have a recommend any other library?

m32 commented

I do not know of a library that would allow to suspend the signing process and return to it with new parameters.

AWS, Google and certainly other big players have their HSM services in the cloud and they are probably not expensive services, so I would be interested in them.

epdxb commented

@m32
In pdf-sign-cms-hash.py we have used the below configuration.
pdfname = 'pdf.pdf' if len (sys.argv) > 1: pdfname = sys.argv[1] try: config = open(pdfname + ".json", "rt").read() config = json.loads(config) except FileNotFoundError: config = { 'when': datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), 'certificate': open("pubkey.pem", "rt").read(), 'signed_bytes': open("response.bin", 'rb').read(), 'tosign': open(pdfname, "rb").read(), }

File "pdf-sign-cms-hash.py", line 97, in main
signed_bytes = base64.decodebytes(signed_bytes.encode('ascii'))
AttributeError: 'bytes' object has no attribute 'encode'

Please find the certificate,signed_bytes(response.bin) and tosign(pdf.pdf) from the below links

tosign
certificate
signed_bytes

m32 commented

Check the compatibility of variable types in the json file.
In my signed_bytes it's a binary base64 encoded, in yours it's just a binary value.
Also show the entire program, not just a piece that is not in my code.

m32 commented

pdf.pdf.json looks like:
{
"when": "2022-01-16 00:47:00",
"certificate": "-----BEGIN CERTIFICATE-----\nMIICljCCAYACA2ZmkDALBgkqhkiG9w0BAQswETEPMA0GA1UEAwwGaHNtIENBMB4X\nDTIxMDEyNTA2NDEyMFoXDTIyMDEyNjA2NDEyMFowFTETMBEGA1UEAwwKaHNtIFVT\nRVIgMTCCA
SAwCwYJKoZIhvcNAQEBA4IBDwAwggEKAoIBAQCX6aFBlzQuX77NBDEa\nTRXhlfoTpytEMbf3wi63QReQvX4lYxnlfK5AVBZGdI9FL2q05aTIwbwTCVKzB+4t\nCvpR6mr+3r2Dlmf8+fKPqtpMGBEGkalLZ8z7zM3orbCR+hNZTqD8G2KM0RMK/o/K\n4
eAel/qxAr9HZKMUL9fsPMuxb3MIP7JjgBwuwn4uwWy8FN4fHWStNrVI+08CTZdh\n/c9xl+kxJwem5xShj9xPfrenvj4BX7yClCck3CtuM3lgMRKP1VKB+/NyrjEwJR8x\ntxXjKJcVJd8Iq5WRorMhLsTyKRAM6jsTiz030iVvO35RW6wTEtYtInwLdyR
clI6K\nNYrxAgMBAAEwCwYJKoZIhvcNAQELA4IBAQAjw2ePtK0mwTI53CaZ7wkdIjpV8dvc\n1c4i/IKQjD/jvhGVlGCm5hmP0BMdmVQgaR7w1o1o8m4raaMVEvpTTo/vQCTlXhPs\ntngIeAJTqwfrh4hqdF+EDgqFFHc7yyjJij8aventUN7yTEt1x15
Uq/UdwSdvuE5s\nQgJ0NMzUDZKf3W7UFx7TT3hVQ4Wibq92AconMAkR5hP8rf3eWSp5WLwLrisnpWwR\nAwlAyTbb2e3k/ufAlIyIiqTQjw0Q8aA24aiNfpwPNiw68Y8Kmgl4EWvzKL2DhF/M\nGvnOzkNLU6lIN4FmaEeFOtJnCZdX/EuLKoJn4h8P7Vs
66uwtzyOzc631\n-----END CERTIFICATE-----\n",
"signed_bytes": "fVbU2JsyvgrJBs5+YyvmbWiH9TbuLxDbr41N/rRrVn5sPqKBJu2pvBRrD2WOYkePH5h/Z/bNGz3ZtiX6boE6isBl/qsYUxtm+IvwvOUiaRaCYcF21y+TJHeMYVzb7mkB8JUeu/FYqZAaWlz7ybRM6MgKS7da1TMS46jJPjdhb
9gEfqswLoCbdVWuMAAujGLq8ab6J+RhekuEjrj3j8NCAsXNO9xNij24QV6vRuBLtdwt/7CUFU/p4q7J9B3Vuw7nLe+29G9C/Y3E3/FN3ncK3bCOHLcbIt/Q8sIGFE/s5+JMJ2FclNRvlz4CbQ6bIrk3PRJ+vS9fLJ7Bgm5Vff17Uw==",
"tosign": "MWkwGAYJKoZIhvcNAQkDMQsGCSqGSIb3DQEHATAcBgkqhkiG9w0BCQUxDxcNMjIwMTE2MDA0NzAwWjAvBgkqhkiG9w0BCQQxIgQgk+Lqwj9OWvjBbgXQLZvj1GnoFPvmDAZXNt29LwzHt00="
}

epdxb commented

I have updated pdf-sign-cms-hash.py as shown below, but I have received the below error.

-----Error---------
datas = cls.sign(datau, dct, None, None, [], "sha256", clshsm, mode="sign")
TypeError: sign() missing 1 required positional argument: 'timestampurl'

-----------pdf-sign-cms-hash.py-------------

#!/usr/bin/env vpython3
import sys
import datetime
import base64
import json
from asn1crypto import cms, core, util
from endesive import pdf

class Signer:
def init(self, cert, sig, tosign):
self.cert = cert
self.sig = sig
self.tosign = tosign
self.mech = None

def certificate(self):
    return 1, self.cert

def sign(self, keyid, data, mech):
    if self.tosign:
        assert self.tosign == data
    self.tosign = data
    self.mech = mech
    if self.sig is None:
        sig = None
        if mech == "sha256":
            sig = b"\0" * 256
        return sig
    return self.sig

def main():
def attrs(signed_value):
result = [
cms.CMSAttribute(
{"type": cms.CMSAttributeType("content_type"), "values": ("data",)}
),
cms.CMSAttribute(
{
"type": cms.CMSAttributeType("message_digest"),
"values": (signed_value,),
}
),
cms.CMSAttribute(
{
"type": cms.CMSAttributeType("signing_time"),
"values": (cms.Time({"utc_time": core.UTCTime(signed_time)}),),
}
),
]
return result

dct = {
    "sigflags": 3,
    "sigpage": 0,
    "sigbutton": True,
    "contact": "mak@trisoft.com.pl",
    "location": "Szczecin",
    "reason": "Dokument podpisany cyfrowo",
    "signature": "Dokument podpisany cyfrowo",
    "signaturebox": (0, 0, 100, 100),
    "aligned": 16384,
    "attrs": attrs,
    "newid": "1",
}

pdfname = 'pdf.pdf'
if len (sys.argv) > 1:
    pdfname = sys.argv[1]
try:
    config = open(pdfname + ".json", "rt").read()
    config = json.loads(config)
except FileNotFoundError:
    config = {
        'when': datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
        'certificate': '-----BEGIN CERTIFICATE-----\n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\n-----END CERTIFICATE-----\n',
        'signed_bytes': 'kKEWZUUVMAS45mziBJHFWAhB5cin2n7qVcq+zOpTY5QBoAoD4RIbh1Eqth/p82RQZY+v0O9DRQ6gfGjHMLh7hMZ/4WdmMIQw2QJZBpbRcVYHolvTr88AcNTWCCsPclF6DdbQssuVBQCRM81tz+0gGojPEnB1Y50kaGIs0JSRrWT/5UvMpOrNqdsuRvkfP34N/hsqDciSh4MXn5D/CltgYQ73wR2HlHAjTIbiGLDCATIXdgiKFbz2Q8PfVzpQ7slWiwR/+SPXBOP4DxsPuxDe0he99VfuWLMoegweiiHyKVVKQnwnkLprOkrhK9LibiatlFTpP5xzTwDbryUDTdS7sQ==',
        'tosign': 'JVBERi0xLjMNCiXi48/TDQoNCjEgMCBvYmoNCjw8DQovVHlwZSAvQ2F0YWxvZw0KL091dGxpbmVzIDIgMCBSDQovUGFnZXMgMyAwIFINCj4+DQplbmRvYmoNCg0KMiAwIG9iag0KPDwNCi9UeXBlIC9PdXRsaW5lcw0KL0NvdW50IDANCj4+DQplbmRvYmoNCg0KMyAwIG9iag0KPDwNCi9UeXBlIC9QYWdlcw0KL0NvdW50IDINCi9LaWRzIFsgNCAwIFIgNiAwIFIgXSANCj4+DQplbmRvYmoNCg0KNCAwIG9iag0KPDwNCi9UeXBlIC9QYWdlDQovUGFyZW50IDMgMCBSDQovUmVzb3VyY2VzIDw8DQovRm9udCA8PA0KL0YxIDkgMCBSIA0KPj4NCi9Qcm9jU2V0IDggMCBSDQo+Pg0KL01lZGlhQm94IFswIDAgNjEyLjAwMDAgNzkyLjAwMDBdDQovQ29udGVudHMgNSAwIFINCj4+DQplbmRvYmoNCg0KNSAwIG9iag0KPDwgL0xlbmd0aCAxMDc0ID4+DQpzdHJlYW0NCjIgSg0KQlQNCjAgMCAwIHJnDQovRjEgMDAyNyBUZg0KNTcuMzc1MCA3MjIuMjgwMCBUZA0KKCBBIFNpbXBsZSBQREYgRmlsZSApIFRqDQpFVA0KQlQNCi9GMSAwMDEwIFRmDQo2OS4yNTAwIDY4OC42MDgwIFRkDQooIFRoaXMgaXMgYSBzbWFsbCBkZW1vbnN0cmF0aW9uIC5wZGYgZmlsZSAtICkgVGoNCkVUDQpCVA0KL0YxIDAwMTAgVGYNCjY5LjI1MDAgNjY0LjcwNDAgVGQNCigganVzdCBmb3IgdXNlIGluIHRoZSBWaXJ0dWFsIE1lY2hhbmljcyB0dXRvcmlhbHMuIE1vcmUgdGV4dC4gQW5kIG1vcmUgKSBUag0KRVQNCkJUDQovRjEgMDAxMCBUZg0KNjkuMjUwMCA2NTIuNzUyMCBUZA0KKCB0ZXh0LiBBbmQgbW9yZSB0ZXh0LiBBbmQgbW9yZSB0ZXh0LiBBbmQgbW9yZSB0ZXh0LiApIFRqDQpFVA0KQlQNCi9GMSAwMDEwIFRmDQo2OS4yNTAwIDYyOC44NDgwIFRkDQooIEFuZCBtb3JlIHRleHQuIEFuZCBtb3JlIHRleHQuIEFuZCBtb3JlIHRleHQuIEFuZCBtb3JlIHRleHQuIEFuZCBtb3JlICkgVGoNCkVUDQpCVA0KL0YxIDAwMTAgVGYNCjY5LjI1MDAgNjE2Ljg5NjAgVGQNCiggdGV4dC4gQW5kIG1vcmUgdGV4dC4gQm9yaW5nLCB6enp6ei4gQW5kIG1vcmUgdGV4dC4gQW5kIG1vcmUgdGV4dC4gQW5kICkgVGoNCkVUDQpCVA0KL0YxIDAwMTAgVGYNCjY5LjI1MDAgNjA0Ljk0NDAgVGQNCiggbW9yZSB0ZXh0LiBBbmQgbW9yZSB0ZXh0LiBBbmQgbW9yZSB0ZXh0LiBBbmQgbW9yZSB0ZXh0LiBBbmQgbW9yZSB0ZXh0LiApIFRqDQpFVA0KQlQNCi9GMSAwMDEwIFRmDQo2OS4yNTAwIDU5Mi45OTIwIFRkDQooIEFuZCBtb3JlIHRleHQuIEFuZCBtb3JlIHRleHQuICkgVGoNCkVUDQpCVA0KL0YxIDAwMTAgVGYNCjY5LjI1MDAgNTY5LjA4ODAgVGQNCiggQW5kIG1vcmUgdGV4dC4gQW5kIG1vcmUgdGV4dC4gQW5kIG1vcmUgdGV4dC4gQW5kIG1vcmUgdGV4dC4gQW5kIG1vcmUgKSBUag0KRVQNCkJUDQovRjEgMDAxMCBUZg0KNjkuMjUwMCA1NTcuMTM2MCBUZA0KKCB0ZXh0LiBBbmQgbW9yZSB0ZXh0LiBBbmQgbW9yZSB0ZXh0LiBFdmVuIG1vcmUuIENvbnRpbnVlZCBvbiBwYWdlIDIgLi4uKSBUag0KRVQNCmVuZHN0cmVhbQ0KZW5kb2JqDQoNCjYgMCBvYmoNCjw8DQovVHlwZSAvUGFnZQ0KL1BhcmVudCAzIDAgUg0KL1Jlc291cmNlcyA8PA0KL0ZvbnQgPDwNCi9GMSA5IDAgUiANCj4+DQovUHJvY1NldCA4IDAgUg0KPj4NCi9NZWRpYUJveCBbMCAwIDYxMi4wMDAwIDc5Mi4wMDAwXQ0KL0NvbnRlbnRzIDcgMCBSDQo+Pg0KZW5kb2JqDQoNCjcgMCBvYmoNCjw8IC9MZW5ndGggNjc2ID4+DQpzdHJlYW0NCjIgSg0KQlQNCjAgMCAwIHJnDQovRjEgMDAyNyBUZg0KNTcuMzc1MCA3MjIuMjgwMCBUZA0KKCBTaW1wbGUgUERGIEZpbGUgMiApIFRqDQpFVA0KQlQNCi9GMSAwMDEwIFRmDQo2OS4yNTAwIDY4OC42MDgwIFRkDQooIC4uLmNvbnRpbnVlZCBmcm9tIHBhZ2UgMS4gWWV0IG1vcmUgdGV4dC4gQW5kIG1vcmUgdGV4dC4gQW5kIG1vcmUgdGV4dC4gKSBUag0KRVQNCkJUDQovRjEgMDAxMCBUZg0KNjkuMjUwMCA2NzYuNjU2MCBUZA0KKCBBbmQgbW9yZSB0ZXh0LiBBbmQgbW9yZSB0ZXh0LiBBbmQgbW9yZSB0ZXh0LiBBbmQgbW9yZSB0ZXh0LiBBbmQgbW9yZSApIFRqDQpFVA0KQlQNCi9GMSAwMDEwIFRmDQo2OS4yNTAwIDY2NC43MDQwIFRkDQooIHRleHQuIE9oLCBob3cgYm9yaW5nIHR5cGluZyB0aGlzIHN0dWZmLiBCdXQgbm90IGFzIGJvcmluZyBhcyB3YXRjaGluZyApIFRqDQpFVA0KQlQNCi9GMSAwMDEwIFRmDQo2OS4yNTAwIDY1Mi43NTIwIFRkDQooIHBhaW50IGRyeS4gQW5kIG1vcmUgdGV4dC4gQW5kIG1vcmUgdGV4dC4gQW5kIG1vcmUgdGV4dC4gQW5kIG1vcmUgdGV4dC4gKSBUag0KRVQNCkJUDQovRjEgMDAxMCBUZg0KNjkuMjUwMCA2NDAuODAwMCBUZA0KKCBCb3JpbmcuICBNb3JlLCBhIGxpdHRsZSBtb3JlIHRleHQuIFRoZSBlbmQsIGFuZCBqdXN0IGFzIHdlbGwuICkgVGoNCkVUDQplbmRzdHJlYW0NCmVuZG9iag0KDQo4IDAgb2JqDQpbL1BERiAvVGV4dF0NCmVuZG9iag0KDQo5IDAgb2JqDQo8PA0KL1R5cGUgL0ZvbnQNCi9TdWJ0eXBlIC9UeXBlMQ0KL05hbWUgL0YxDQovQmFzZUZvbnQgL0hlbHZldGljYQ0KL0VuY29kaW5nIC9XaW5BbnNpRW5jb2RpbmcNCj4+DQplbmRvYmoNCg0KMTAgMCBvYmoNCjw8DQovQ3JlYXRvciAoUmF2ZSBcKGh0dHA6Ly93d3cubmV2cm9uYS5jb20vcmF2ZVwpKQ0KL1Byb2R1Y2VyIChOZXZyb25hIERlc2lnbnMpDQovQ3JlYXRpb25EYXRlIChEOjIwMDYwMzAxMDcyODI2KQ0KPj4NCmVuZG9iag0KDQp4cmVmDQowIDExDQowMDAwMDAwMDAwIDY1NTM1IGYNCjAwMDAwMDAwMTkgMDAwMDAgbg0KMDAwMDAwMDA5MyAwMDAwMCBuDQowMDAwMDAwMTQ3IDAwMDAwIG4NCjAwMDAwMDAyMjIgMDAwMDAgbg0KMDAwMDAwMDM5MCAwMDAwMCBuDQowMDAwMDAxNTIyIDAwMDAwIG4NCjAwMDAwMDE2OTAgMDAwMDAgbg0KMDAwMDAwMjQyMyAwMDAwMCBuDQowMDAwMDAyNDU2IDAwMDAwIG4NCjAwMDAwMDI1NzQgMDAwMDAgbg0KDQp0cmFpbGVyDQo8PA0KL1NpemUgMTENCi9Sb290IDEgMCBSDQovSW5mbyAxMCAwIFINCj4+DQoNCnN0YXJ0eHJlZg0KMjcxNA0KJSVFT0YNCg==',
    }
    when = datetime.datetime.strptime(config['when'], "%Y-%m-%d %H:%M:%S")
dct["signingdate"] = when.strftime("%Y%m%d%H%M%S+00'00'").encode()
signed_time = datetime.datetime(
    when.year, when.month, when.day, when.hour, when.minute, when.second, 0, util.timezone.utc
)
cert = config['certificate'].encode('ascii')
signed_bytes = config['signed_bytes']
if signed_bytes is not None:
    signed_bytes = base64.decodebytes(signed_bytes.encode('ascii'))
tosign = config['tosign']
if tosign is not None:
    tosign = base64.decodebytes(tosign.encode('ascii'))

clshsm = Signer(cert, signed_bytes, tosign)

datau = open(pdfname, "rb").read()
cls = pdf.cms.SignedData()
datas = cls.sign(datau, dct, None, None, [], "sha256", clshsm, mode="sign")

if signed_bytes is None:
    config['tosign'] = b"".join(base64.encodebytes(clshsm.tosign).split()).decode('ascii')
    json.dump(config, open(pdfname + ".json", "wt"), indent=4)
else:
    fname = pdfname.replace(".pdf", "-signed-cms-hash.pdf")
    with open(fname, "wb") as fp:
        fp.write(datau)
        fp.write(datas)

main()

m32 commented

Parameter timestampurl has default value in head version, did You pull it ?
Did You run example ?

dear @m32 , I have Signed hash and Prehashed. what mode can I use? Thank you

m32 commented

Try: examples/pdf-sign-cms-hash.py
The first time you run it, it will create a .json file. The "tosign" is a document hashed value which need to be signed by an external program. After signing, fill "signed_bytes" (base64, signed hash) and "certificate" and run the example again.

@m32 thank you for your information. I'll try it

m32 commented

sample in #129