package sign import ( "bytes" "crypto" "crypto/x509" "encoding/asn1" "encoding/hex" "errors" "fmt" "io" "log" "net/http" "strconv" "github.com/digitorus/pkcs7" "github.com/digitorus/timestamp" "golang.org/x/crypto/cryptobyte" cryptobyte_asn1 "golang.org/x/crypto/cryptobyte/asn1" ) const signatureByteRangePlaceholder = "/ByteRange[0 ********** ********** **********]" func (context *SignContext) createSignaturePlaceholder() []byte { // Using a buffer because it's way faster than concatenating. var signature_buffer bytes.Buffer signature_buffer.WriteString("<<\n") signature_buffer.WriteString(" /Type /Sig\n") signature_buffer.WriteString(" /Filter /Adobe.PPKLite\n") signature_buffer.WriteString(" /SubFilter /adbe.pkcs7.detached\n") signature_buffer.WriteString(context.createPropBuild()) // Create a placeholder for the byte range string, we will replace it later. signature_buffer.WriteString(" " + signatureByteRangePlaceholder) // Create a placeholder for the actual signature content, we will replace it later. signature_buffer.WriteString(" /Contents<") signature_buffer.Write(bytes.Repeat([]byte("0"), int(context.SignatureMaxLength))) signature_buffer.WriteString(">\n") switch context.SignData.Signature.CertType { case CertificationSignature, UsageRightsSignature: signature_buffer.WriteString(" /Reference [\n") // start array of signature reference dictionaries signature_buffer.WriteString(" << /Type /SigRef\n") } switch context.SignData.Signature.CertType { // Certification signature (also known as an author signature) case CertificationSignature: signature_buffer.WriteString(" /TransformMethod /DocMDP\n") // Entries in the DocMDP transform parameters dictionary (Table 257) signature_buffer.WriteString(" /TransformParams <<\n") // Type [name]: (Optional) The type of PDF object that this dictionary describes; // if present, shall be TransformParams for a transform parameters dictionary. signature_buffer.WriteString(" /Type /TransformParams\n") // (Optional) The access permissions granted for this document. Changes to // a PDF that are incremental updates which include only the data necessary // to add DSS’s 12.8.4.3, "Document Security Store (DSS)" and/or document // timestamps 12.8.5, "Document timestamp (DTS) dictionary" to the // document shall not be considered as changes to the document as defined // in the choices below. // // Valid values shall be: // 1 No changes to the document shall be permitted; any change to the document // shall invalidate the signature. // 2 Permitted changes shall be filling in forms, instantiating page templates, // and signing; other changes shall invalidate the signature. // 3 Permitted changes shall be the same as for 2, as well as annotation creation, // deletion, and modification; other changes shall invalidate the signature. // // (Default value: 2.) signature_buffer.WriteString(" /P " + strconv.Itoa(int(context.SignData.Signature.DocMDPPerm))) // V [name]: (Optional) The DocMDP transform parameters dictionary version. The only valid value shall be 1.2. // Default value: 1.2. (This value is a name object, not a number.) signature_buffer.WriteString(" /V /1.2\n") // Usage rights signature (deprecated in PDF 2.0) case UsageRightsSignature: signature_buffer.WriteString(" /TransformMethod /UR3\n") // Entries in the UR transform parameters dictionary (Table 258) signature_buffer.WriteString(" /TransformParams <<\n") signature_buffer.WriteString(" /Type /TransformParams\n") signature_buffer.WriteString(" /V /2.2\n") // Approval signatures (also known as recipient signatures) case ApprovalSignature: // Used to detect modifications to a list of form fields specified in TransformParams; see // 12.8.2.4, "FieldMDP" signature_buffer.WriteString(" /TransformMethod /FieldMDP\n") // Entries in the FieldMDP transform parameters dictionary (Table 259) signature_buffer.WriteString(" /TransformParams <<\n") // Type [name]: (Optional) The type of PDF object that this dictionary describes; // if present, shall be TransformParams for a transform parameters dictionary. signature_buffer.WriteString(" /Type /TransformParams\n") // Action [name]: (Required) A name that, along with the Fields array, describes // which form fields do not permit changes after the signature is applied. // Valid values shall be: // All - All form fields // Include - Only those form fields specified in Fields. // Exclude - Only those form fields not specified in Fields. signature_buffer.WriteString(" /Action /All\n") // V [name]: (Optional; required for PDF 1.5 and later) The transform parameters // dictionary version. The value for PDF 1.5 and later shall be 1.2. // Default value: 1.2. (This value is a name object, not a number.) signature_buffer.WriteString(" /V /1.2\n") } // (Required) A name identifying the algorithm that shall be used when computing the digest if not specified in the // certificate. Valid values are MD5, SHA1 SHA256, SHA384, SHA512 and RIPEMD160 switch context.SignData.DigestAlgorithm { case crypto.MD5: signature_buffer.WriteString(" /DigestMethod /MD5\n") case crypto.SHA1: signature_buffer.WriteString(" /DigestMethod /SHA1\n") case crypto.SHA256: signature_buffer.WriteString(" /DigestMethod /SHA256\n") case crypto.SHA384: signature_buffer.WriteString(" /DigestMethod /SHA384\n") case crypto.SHA512: signature_buffer.WriteString(" /DigestMethod /SHA512\n") case crypto.RIPEMD160: signature_buffer.WriteString(" /DigestMethod /RIPEMD160\n") } switch context.SignData.Signature.CertType { case CertificationSignature, UsageRightsSignature: signature_buffer.WriteString(" >>\n") // close TransformParams signature_buffer.WriteString(" >>") // close SigRef signature_buffer.WriteString(" ]") // end of reference } switch context.SignData.Signature.CertType { case ApprovalSignature: signature_buffer.WriteString(" >>\n") } if context.SignData.Signature.Info.Name != "" { signature_buffer.WriteString(" /Name ") signature_buffer.WriteString(pdfString(context.SignData.Signature.Info.Name)) signature_buffer.WriteString("\n") } if context.SignData.Signature.Info.Location != "" { signature_buffer.WriteString(" /Location ") signature_buffer.WriteString(pdfString(context.SignData.Signature.Info.Location)) signature_buffer.WriteString("\n") } if context.SignData.Signature.Info.Reason != "" { signature_buffer.WriteString(" /Reason ") signature_buffer.WriteString(pdfString(context.SignData.Signature.Info.Reason)) signature_buffer.WriteString("\n") } if context.SignData.Signature.Info.ContactInfo != "" { signature_buffer.WriteString(" /ContactInfo ") signature_buffer.WriteString(pdfString(context.SignData.Signature.Info.ContactInfo)) signature_buffer.WriteString("\n") } // (Optional) The time of signing. Depending on the signature handler, this may // be a normal unverified computer time or a time generated in a verifiable way // from a secure time server. // // This value should be used only when the time of signing is not available in the // signature. If SubFilter is ETSI.RFC3161, this entry should not be used and // should be ignored by a PDF processor. // // A timestamp can be embedded in a CMS binary data object (see 12.8.3.3, "CMS // (PKCS #7) signatures"). if context.SignData.TSA.URL == "" && !context.SignData.Signature.Info.Date.IsZero() { signature_buffer.WriteString(" /M ") signature_buffer.WriteString(pdfDateTime(context.SignData.Signature.Info.Date)) signature_buffer.WriteString("\n") } signature_buffer.WriteString(">>\n") return signature_buffer.Bytes() } func (context *SignContext) createTimestampPlaceholder() []byte { var timestamp_buffer bytes.Buffer timestamp_buffer.WriteString("<<\n") timestamp_buffer.WriteString(" /Type /DocTimeStamp\n") timestamp_buffer.WriteString(" /Filter /Adobe.PPKLite\n") timestamp_buffer.WriteString(" /SubFilter /ETSI.RFC3161\n") timestamp_buffer.WriteString(context.createPropBuild()) // Create a placeholder for the byte range string, we will replace it later. timestamp_buffer.WriteString(" " + signatureByteRangePlaceholder) timestamp_buffer.WriteString(" /Contents<") timestamp_buffer.Write(bytes.Repeat([]byte("0"), int(context.SignatureMaxLength))) timestamp_buffer.WriteString(">\n") timestamp_buffer.WriteString(">>\n") return timestamp_buffer.Bytes() } func (context *SignContext) fetchRevocationData() error { if context.SignData.RevocationFunction != nil { if context.SignData.CertificateChains != nil && (len(context.SignData.CertificateChains) > 0) { certificate_chain := context.SignData.CertificateChains[0] if certificate_chain != nil && (len(certificate_chain) > 0) { for i, certificate := range certificate_chain { if i < len(certificate_chain)-1 { err := context.SignData.RevocationFunction(certificate, certificate_chain[i+1], &context.SignData.RevocationData) if err != nil { return err } } else { err := context.SignData.RevocationFunction(certificate, nil, &context.SignData.RevocationData) if err != nil { return err } } } } } } // Calculate space needed for signature. for _, crl := range context.SignData.RevocationData.CRL { context.SignatureMaxLength += uint32(hex.EncodedLen(len(crl.FullBytes))) } for _, ocsp := range context.SignData.RevocationData.OCSP { context.SignatureMaxLength += uint32(hex.EncodedLen(len(ocsp.FullBytes))) } return nil } func (context *SignContext) createSigningCertificateAttribute() (*pkcs7.Attribute, error) { hash := context.SignData.DigestAlgorithm.New() hash.Write(context.SignData.Certificate.Raw) var b cryptobyte.Builder b.AddASN1(cryptobyte_asn1.SEQUENCE, func(b *cryptobyte.Builder) { // SigningCertificate b.AddASN1(cryptobyte_asn1.SEQUENCE, func(b *cryptobyte.Builder) { // []ESSCertID, []ESSCertIDv2 b.AddASN1(cryptobyte_asn1.SEQUENCE, func(b *cryptobyte.Builder) { // ESSCertID, ESSCertIDv2 if context.SignData.DigestAlgorithm.HashFunc() != crypto.SHA1 && context.SignData.DigestAlgorithm.HashFunc() != crypto.SHA256 { // default SHA-256 b.AddASN1(cryptobyte_asn1.SEQUENCE, func(b *cryptobyte.Builder) { // AlgorithmIdentifier b.AddASN1ObjectIdentifier(getOIDFromHashAlgorithm(context.SignData.DigestAlgorithm)) }) } b.AddASN1OctetString(hash.Sum(nil)) // certHash }) }) }) sse, err := b.Bytes() if err != nil { return nil, err } signingCertificate := pkcs7.Attribute{ Type: asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 16, 2, 47}, // SigningCertificateV2 Value: asn1.RawValue{FullBytes: sse}, } if context.SignData.DigestAlgorithm.HashFunc() == crypto.SHA1 { signingCertificate.Type = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 16, 2, 12} // SigningCertificate } return &signingCertificate, nil } func (context *SignContext) createSignature() ([]byte, error) { if _, err := context.OutputBuffer.Seek(0, 0); err != nil { return nil, err } // Sadly we can't efficiently sign a file, we need to read all the bytes we want to sign. file_content := context.OutputBuffer.Buff.Bytes() // Collect the parts to sign. sign_content := make([]byte, 0) sign_content = append(sign_content, file_content[context.ByteRangeValues[0]:(context.ByteRangeValues[0]+context.ByteRangeValues[1])]...) sign_content = append(sign_content, file_content[context.ByteRangeValues[2]:(context.ByteRangeValues[2]+context.ByteRangeValues[3])]...) // Return the timestamp if we are signing a timestamp. if context.SignData.Signature.CertType == TimeStampSignature { // ETSI EN 319 142-1 V1.2.1 // // Contents [Byte string ]: (Required) When the value of SubFilter is ETSI.RFC3161, // the value of Contents shall be the hexadecimal string (as defined in clause // 7.3.4.3 in ISO 32000-1 [1]) representing the value of TimeStampToken as // specified in IETF RFC 3161 [6] updated by IETF RFC 5816 [8]. The value of the // messageImprint field within the TimeStampToken shall be a hash of the bytes // of the document indicated by the ByteRange. The ByteRange shall cover the // entire document, including the Document Time-stamp dictionary but excluding // the TimeStampToken itself (the entry with key Contents). timestamp_response, err := context.GetTSA(sign_content) if err != nil { return nil, fmt.Errorf("get timestamp: %w", err) } ts, err := timestamp.ParseResponse(timestamp_response) if err != nil { return nil, fmt.Errorf("parse timestamp: %w", err) } return ts.RawToken, nil } // Initialize pkcs7 signer. signed_data, err := pkcs7.NewSignedData(sign_content) if err != nil { return nil, fmt.Errorf("new signed data: %w", err) } signed_data.SetDigestAlgorithm(getOIDFromHashAlgorithm(context.SignData.DigestAlgorithm)) signingCertificate, err := context.createSigningCertificateAttribute() if err != nil { return nil, fmt.Errorf("new signed data: %w", err) } signer_config := pkcs7.SignerInfoConfig{ ExtraSignedAttributes: []pkcs7.Attribute{ { Type: asn1.ObjectIdentifier{1, 2, 840, 113583, 1, 1, 8}, Value: context.SignData.RevocationData, }, *signingCertificate, }, } // Add the first certificate chain without our own certificate. var certificate_chain []*x509.Certificate if len(context.SignData.CertificateChains) > 0 && len(context.SignData.CertificateChains[0]) > 1 { certificate_chain = context.SignData.CertificateChains[0][1:] } // Add the signer and sign the data. if err := signed_data.AddSignerChain(context.SignData.Certificate, context.SignData.Signer, certificate_chain, signer_config); err != nil { return nil, fmt.Errorf("add signer chain: %w", err) } // PDF needs a detached signature, meaning the content isn't included. signed_data.Detach() if context.SignData.TSA.URL != "" { signature_data := signed_data.GetSignedData() timestamp_response, err := context.GetTSA(signature_data.SignerInfos[0].EncryptedDigest) if err != nil { return nil, fmt.Errorf("get timestamp: %w", err) } ts, err := timestamp.ParseResponse(timestamp_response) if err != nil { return nil, fmt.Errorf("parse timestamp: %w", err) } _, err = pkcs7.Parse(ts.RawToken) if err != nil { return nil, fmt.Errorf("parse timestamp token: %w", err) } timestamp_attribute := pkcs7.Attribute{ Type: asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 16, 2, 14}, Value: asn1.RawValue{FullBytes: ts.RawToken}, } if err := signature_data.SignerInfos[0].SetUnauthenticatedAttributes([]pkcs7.Attribute{timestamp_attribute}); err != nil { return nil, err } } return signed_data.Finish() } func (context *SignContext) GetTSA(sign_content []byte) (timestamp_response []byte, err error) { sign_reader := bytes.NewReader(sign_content) ts_request, err := timestamp.CreateRequest(sign_reader, ×tamp.RequestOptions{ Hash: context.SignData.DigestAlgorithm, Certificates: true, }) if err != nil { return nil, fmt.Errorf("failed to create request: %w", err) } ts_request_reader := bytes.NewReader(ts_request) req, err := http.NewRequest("POST", context.SignData.TSA.URL, ts_request_reader) if err != nil { return nil, fmt.Errorf("failed to prepare request (%s): %w", context.SignData.TSA.URL, err) } req.Header.Add("Content-Type", "application/timestamp-query") req.Header.Add("Content-Transfer-Encoding", "binary") if context.SignData.TSA.Username != "" && context.SignData.TSA.Password != "" { req.SetBasicAuth(context.SignData.TSA.Username, context.SignData.TSA.Password) } client := &http.Client{} resp, err := client.Do(req) code := 0 if resp != nil { code = resp.StatusCode } if err != nil || (code < 200 || code > 299) { if err == nil { defer resp.Body.Close() body, _ := io.ReadAll(resp.Body) return nil, errors.New("non success response (" + strconv.Itoa(code) + "): " + string(body)) } return nil, errors.New("non success response (" + strconv.Itoa(code) + ")") } defer resp.Body.Close() timestamp_response_body, err := io.ReadAll(resp.Body) if err != nil { return nil, fmt.Errorf("failed to read response: %w", err) } return timestamp_response_body, nil } func (context *SignContext) replaceSignature() error { signature, err := context.createSignature() if err != nil { return fmt.Errorf("failed to create signature: %w", err) } dst := make([]byte, hex.EncodedLen(len(signature))) hex.Encode(dst, signature) if uint32(len(dst)) > context.SignatureMaxLength { log.Println("Signature too long, retrying with increased buffer size.") // set new base and try signing again context.SignatureMaxLengthBase += (uint32(len(dst)) - context.SignatureMaxLength) + 1 return context.SignPDF() } if _, err := context.OutputBuffer.Seek(0, 0); err != nil { return err } file_content := context.OutputBuffer.Buff.Bytes() // Write the file content up to the signature if _, err := context.OutputBuffer.Write(file_content[context.ByteRangeValues[0]:context.ByteRangeValues[1]]); err != nil { return err } // Write new signature if _, err := context.OutputBuffer.Write([]byte("<")); err != nil { return err } if _, err := context.OutputBuffer.Write([]byte(dst)); err != nil { return err } // Write 0s to ensure the signature remains the same size zeroPadding := bytes.Repeat([]byte("0"), int(context.SignatureMaxLength)-len(dst)) if _, err := context.OutputBuffer.Write(zeroPadding); err != nil { return err } if _, err := context.OutputBuffer.Write([]byte(">")); err != nil { return err } if _, err := context.OutputBuffer.Write(file_content[context.ByteRangeValues[2] : context.ByteRangeValues[2]+context.ByteRangeValues[3]]); err != nil { return err } return nil } func (context *SignContext) fetchExistingSignatures() ([]SignData, error) { var signatures []SignData acroForm := context.PDFReader.Trailer().Key("Root").Key("AcroForm") if acroForm.IsNull() { return signatures, nil } fields := acroForm.Key("Fields") if fields.IsNull() { return signatures, nil } for i := 0; i < fields.Len(); i++ { field := fields.Index(i) if field.Key("FT").Name() == "Sig" { ptr := field.GetPtr() sig := SignData{ objectId: uint32(ptr.GetID()), } signatures = append(signatures, sig) } } return signatures, nil } func (context *SignContext) createPropBuild() string { var buffer bytes.Buffer // Prop_Build [dictionary]: (Optional; PDF 1.5) A dictionary that may be used by a signature handler to // record information that captures the state of the computer environment used // for signing, such as the name of the handler used to create the signature, // software build date, version, and operating system. // The use of this dictionary is defined by Adobe PDF Signature Build Dictionary // Specification, which provides implementation guidelines. buffer.WriteString(" /Prop_Build <<\n") buffer.WriteString(" /App << /Name /Digitorus#20PDFSign >>\n") buffer.WriteString(" >>\n") return buffer.String() }