Skip to content

Commit c415379

Browse files
authored
Merge pull request #291 from 3ace/us-1146-custom-client
[US-1146] Using custom `http.Client` in digital signature timestamp service
2 parents 64d4070 + 1282e92 commit c415379

10 files changed

+190
-26
lines changed
Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
/*
2+
* This example showcases how to create a digital signature for a PDF file using a custom timestamp client.
3+
*
4+
* $ ./pdf_sign_custom_client <FILE.PFX> <PASSWORD> <FILE.PEM> <INPUT_PDF_PATH> <OUTPUT_PDF_PATH>
5+
*/
6+
package main
7+
8+
import (
9+
"bytes"
10+
"crypto/x509"
11+
"encoding/pem"
12+
"fmt"
13+
"log"
14+
"net/http"
15+
"os"
16+
"time"
17+
18+
"github.com/unidoc/unipdf/v4/annotator"
19+
"github.com/unidoc/unipdf/v4/common/license"
20+
"github.com/unidoc/unipdf/v4/core"
21+
"github.com/unidoc/unipdf/v4/model"
22+
"github.com/unidoc/unipdf/v4/model/sighandler"
23+
"github.com/unidoc/unipdf/v4/model/sigutil"
24+
"golang.org/x/crypto/pkcs12"
25+
)
26+
27+
func init() {
28+
// Make sure to load your metered License API key prior to using the library.
29+
// If you need a key, you can sign up and create a free one at https://cloud.unidoc.io
30+
err := license.SetMeteredKey(os.Getenv(`UNIDOC_LICENSE_API_KEY`))
31+
if err != nil {
32+
panic(err)
33+
}
34+
}
35+
36+
const usagef = "Usage: %s PFX_FILE PASSWORD PEM_FILE INPUT_PDF_PATH OUTPUT_PDF_PATH\n"
37+
38+
func main() {
39+
args := os.Args
40+
if len(args) < 6 {
41+
fmt.Printf(usagef, os.Args[0])
42+
return
43+
}
44+
pfxPath := args[1]
45+
password := args[2]
46+
pemPath := args[3]
47+
inputPath := args[4]
48+
outputPath := args[5]
49+
50+
// Get private key and X509 certificate from the PFX file.
51+
pfxData, err := os.ReadFile(pfxPath)
52+
if err != nil {
53+
log.Fatal("Fail: %v\n", err)
54+
}
55+
56+
priv, cert, err := pkcs12.Decode(pfxData, password)
57+
if err != nil {
58+
log.Fatal("Fail: %v\n", err)
59+
}
60+
61+
// Get CA Certificate from the PEM file.
62+
caCertF, err := os.ReadFile(pemPath)
63+
if err != nil {
64+
log.Fatal("Fail: %v\n", err)
65+
}
66+
67+
certDERBlock, _ := pem.Decode(caCertF)
68+
69+
cacert, err := x509.ParseCertificate(certDERBlock.Bytes)
70+
71+
if err != nil {
72+
log.Fatal("Fail: %v\n", err)
73+
}
74+
75+
// Create reader.
76+
file, err := os.Open(inputPath)
77+
if err != nil {
78+
log.Fatal("Fail: %v\n", err)
79+
}
80+
defer file.Close()
81+
82+
reader, err := model.NewPdfReader(file)
83+
if err != nil {
84+
log.Fatal("Fail: %v\n", err)
85+
}
86+
87+
// Create appender.
88+
appender, err := model.NewPdfAppender(reader)
89+
if err != nil {
90+
log.Fatal("Fail: %v\n", err)
91+
}
92+
93+
// Set timestamp server.
94+
timestampServerURL := "https://freetsa.org/tsr"
95+
96+
// Create PAdES signature handler.
97+
padEs := sighandler.NewEtsiPAdES(sighandler.LevelLT)
98+
padEs.SetCertificate(cert)
99+
padEs.SetPrivateKey(priv)
100+
padEs.SetCA(cacert)
101+
padEs.SetTimestampServerURL(timestampServerURL)
102+
padEs.SetAppender(appender)
103+
104+
// Set a custom timestamp client with a custom HTTP client (with timeout).
105+
// This is optional. If not set, a default client will be used.
106+
// Here we set a timeout of 30 seconds for the HTTP client (the default is 5 seconds).
107+
padEs.SetTimestampClient(&sigutil.TimestampClient{
108+
HTTPClient: &http.Client{
109+
Timeout: 30 * time.Second,
110+
},
111+
})
112+
113+
var handler model.SignatureHandler
114+
handler = padEs
115+
116+
// Create signature.
117+
signature := model.NewPdfSignature(handler)
118+
signature.SetName("PAdES B-LT Signature PDF")
119+
signature.SetReason("TestPAdESPDF")
120+
signature.SetDate(time.Now(), "")
121+
122+
if err := signature.Initialize(); err != nil {
123+
log.Fatal("Fail: %v\n", err)
124+
}
125+
126+
// Create signature field and appearance.
127+
opts := annotator.NewSignatureFieldOpts()
128+
opts.FontSize = 10
129+
opts.Rect = []float64{10, 25, 75, 60}
130+
131+
field, err := annotator.NewSignatureField(
132+
signature,
133+
[]*annotator.SignatureLine{
134+
annotator.NewSignatureLine("Name", "John Doe"),
135+
annotator.NewSignatureLine("Date", "2025.10.15"),
136+
annotator.NewSignatureLine("Reason", "PAdES signature with custom client"),
137+
},
138+
opts,
139+
)
140+
field.T = core.MakeString("Self signed PDF")
141+
142+
if err = appender.Sign(1, field); err != nil {
143+
log.Fatal("Fail: %v\n", err)
144+
}
145+
146+
// Write output to buffer.
147+
buffer := bytes.NewBuffer(nil)
148+
err = appender.Write(buffer)
149+
if err != nil {
150+
log.Fatal("Fail: %v\n", err)
151+
}
152+
153+
// We need the second pass to correctly save DSS/VRI information.
154+
pdf2, err := model.NewPdfReader(bytes.NewReader(buffer.Bytes()))
155+
if err != nil {
156+
log.Fatal("Fail: %v\n", err)
157+
}
158+
159+
appender2, err := model.NewPdfAppender(pdf2)
160+
if err != nil {
161+
log.Fatal("Fail: %v\n", err)
162+
}
163+
164+
appender2.SetDSS(appender.GetDSS())
165+
166+
// Write output to the PDF file.
167+
err = appender2.WriteToFile(outputPath)
168+
if err != nil {
169+
log.Fatal("Fail: %v\n", err)
170+
}
171+
172+
log.Printf("PDF file successfully signed. Output path: %s\n", outputPath)
173+
}

signatures/pdf_sign_external_google_cloud_kms.go

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,14 @@ import (
2121
"fmt"
2222
"hash/crc32"
2323
"io"
24-
"io/ioutil"
2524
"log"
2625
"math/big"
2726
"os"
2827
"time"
2928

3029
kms "cloud.google.com/go/kms/apiv1"
30+
kmspb "cloud.google.com/go/kms/apiv1/kmspb"
3131
gcOption "google.golang.org/api/option"
32-
kmspb "google.golang.org/genproto/googleapis/cloud/kms/v1"
3332
"google.golang.org/protobuf/types/known/wrapperspb"
3433

3534
"github.com/unidoc/pkcs7"
@@ -104,7 +103,7 @@ func main() {
104103
copy(pdfData[byteRange[1]:byteRange[2]], sig)
105104

106105
// Write output file.
107-
if err := ioutil.WriteFile(outputPath, pdfData, os.ModePerm); err != nil {
106+
if err := os.WriteFile(outputPath, pdfData, os.ModePerm); err != nil {
108107
log.Fatalf("Fail: %v\n", err)
109108
}
110109

@@ -119,7 +118,7 @@ func generateSignedFile(inputPath string, handler model.SignatureHandler) ([]byt
119118
if err != nil {
120119
return nil, nil, err
121120
}
122-
defer file.Close()
121+
defer func() { _ = file.Close() }()
123122

124123
reader, err := model.NewPdfReader(file)
125124
if err != nil {
@@ -182,7 +181,7 @@ func getExternalSignatureAndSign(inputPath, credPath, keyName string) ([]byte, [
182181
if err != nil {
183182
return nil, nil, err
184183
}
185-
defer gcKmsSign.client.Close()
184+
defer func() { _ = gcKmsSign.client.Close() }()
186185

187186
now := time.Now()
188187

signatures/pdf_sign_pades_b_b.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import (
1010
"crypto/x509"
1111
"encoding/pem"
1212
"fmt"
13-
"io/ioutil"
1413
"log"
1514
"os"
1615
"time"
@@ -48,7 +47,7 @@ func main() {
4847
outputPath := args[5]
4948

5049
// Get private key and X509 certificate from the PFX file.
51-
pfxData, err := ioutil.ReadFile(pfxPath)
50+
pfxData, err := os.ReadFile(pfxPath)
5251
if err != nil {
5352
log.Fatal("Fail: %v\n", err)
5453
}
@@ -59,7 +58,7 @@ func main() {
5958
}
6059

6160
// Get cacert certificate from the PEM file.
62-
caCertF, err := ioutil.ReadFile(pemPath)
61+
caCertF, err := os.ReadFile(pemPath)
6362
if err != nil {
6463
log.Fatal("Fail: %v\n", err)
6564
}

signatures/pdf_sign_pades_b_lt.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import (
1111
"crypto/x509"
1212
"encoding/pem"
1313
"fmt"
14-
"io/ioutil"
1514
"log"
1615
"os"
1716
"time"
@@ -49,7 +48,7 @@ func main() {
4948
outputPath := args[5]
5049

5150
// Get private key and X509 certificate from the PFX file.
52-
pfxData, err := ioutil.ReadFile(pfxPath)
51+
pfxData, err := os.ReadFile(pfxPath)
5352
if err != nil {
5453
log.Fatal("Fail: %v\n", err)
5554
}
@@ -60,7 +59,7 @@ func main() {
6059
}
6160

6261
// Get CA Certificate from the PEM file.
63-
caCertF, err := ioutil.ReadFile(pemPath)
62+
caCertF, err := os.ReadFile(pemPath)
6463
if err != nil {
6564
log.Fatal("Fail: %v\n", err)
6665
}

signatures/pdf_sign_pades_b_lta.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import (
1212
"crypto/x509"
1313
"encoding/pem"
1414
"fmt"
15-
"io/ioutil"
1615
"log"
1716
"os"
1817
"time"
@@ -50,7 +49,7 @@ func main() {
5049
outputPath := args[5]
5150

5251
// Get private key and X509 certificate from the PFX file.
53-
pfxData, err := ioutil.ReadFile(pfxPath)
52+
pfxData, err := os.ReadFile(pfxPath)
5453
if err != nil {
5554
log.Fatal("Fail: %v\n", err)
5655
}
@@ -61,7 +60,7 @@ func main() {
6160
}
6261

6362
// Get CA Certificate from the PEM file.
64-
caCertF, err := ioutil.ReadFile(pemPath)
63+
caCertF, err := os.ReadFile(pemPath)
6564
if err != nil {
6665
log.Fatal("Fail: %v\n", err)
6766
}

signatures/pdf_sign_pades_b_t.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import (
1010
"crypto/x509"
1111
"encoding/pem"
1212
"fmt"
13-
"io/ioutil"
1413
"log"
1514
"os"
1615
"time"
@@ -48,7 +47,7 @@ func main() {
4847
outputPath := args[5]
4948

5049
// Get private key and X509 certificate from the PFX file.
51-
pfxData, err := ioutil.ReadFile(pfxPath)
50+
pfxData, err := os.ReadFile(pfxPath)
5251
if err != nil {
5352
log.Fatal("Fail: %v\n", err)
5453
}
@@ -59,7 +58,7 @@ func main() {
5958
}
6059

6160
// Get cacert certificate from the PEM file.
62-
caCertF, err := ioutil.ReadFile(pemPath)
61+
caCertF, err := os.ReadFile(pemPath)
6362
if err != nil {
6463
log.Fatal("Fail: %v\n", err)
6564
}

signatures/pdf_sign_pem_multicert.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ import (
2222
"errors"
2323
"fmt"
2424
"hash"
25-
"io/ioutil"
2625
"log"
2726
"os"
2827
"time"
@@ -82,7 +81,7 @@ func main() {
8281

8382
func loadPrivateKey(privateKeyPath string) (*rsa.PrivateKey, error) {
8483
// Read private key file contents.
85-
privateKeyData, err := ioutil.ReadFile(privateKeyPath)
84+
privateKeyData, err := os.ReadFile(privateKeyPath)
8685
if err != nil {
8786
log.Fatal(err)
8887
}
@@ -101,7 +100,7 @@ func loadPrivateKey(privateKeyPath string) (*rsa.PrivateKey, error) {
101100

102101
func loadCertificates(certPath string) (*x509.Certificate, *core.PdfObjectArray, error) {
103102
// Read certificate file contents.
104-
certData, err := ioutil.ReadFile(certPath)
103+
certData, err := os.ReadFile(certPath)
105104
if err != nil {
106105
return nil, nil, err
107106
}

signatures/pdf_sign_pkcs12.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ package main
99
import (
1010
"crypto/rsa"
1111
"fmt"
12-
"io/ioutil"
1312
"log"
1413
"os"
1514
"time"
@@ -46,7 +45,7 @@ func main() {
4645
outputPath := args[4]
4746

4847
// Get private key and X509 certificate from the P12 file.
49-
pfxData, err := ioutil.ReadFile(p12Path)
48+
pfxData, err := os.ReadFile(p12Path)
5049
if err != nil {
5150
log.Fatal("Fail: %v\n", err)
5251
}

signatures/pdf_sign_timestamp.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import (
1111
"crypto"
1212
"crypto/rsa"
1313
"fmt"
14-
"io/ioutil"
1514
"log"
1615
"os"
1716
"time"
@@ -48,7 +47,7 @@ func main() {
4847
outputPath := args[4]
4948

5049
// Get private key and X509 certificate from the P12 file.
51-
pfxData, err := ioutil.ReadFile(p12Path)
50+
pfxData, err := os.ReadFile(p12Path)
5251
if err != nil {
5352
log.Fatal("Fail: %v\n", err)
5453
}

signatures/pdf_sign_twice_visible_annotation.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import (
1313
"crypto/x509"
1414
"crypto/x509/pkix"
1515
"fmt"
16-
"io/ioutil"
1716
"log"
1817
"math/big"
1918
"os"
@@ -76,7 +75,7 @@ func main() {
7675
}
7776

7877
// Write the resulting file to output.pdf file.
79-
err = ioutil.WriteFile(outputPath, buf, 0666)
78+
err = os.WriteFile(outputPath, buf, 0666)
8079
if err != nil {
8180
log.Fatalf("Fail: %v\n", err)
8281
}

0 commit comments

Comments
 (0)