1
2
3
4
5
6
7
8
9
10
11
12
13
14 package tsclient
15
16 import (
17 "context"
18 "crypto/tls"
19 "errors"
20 "fmt"
21 "io/ioutil"
22 "log"
23 "net/http"
24 "time"
25
26 "github.com/sassoftware/relic/config"
27 "github.com/sassoftware/relic/lib/pkcs7"
28 "github.com/sassoftware/relic/lib/pkcs9"
29 "github.com/sassoftware/relic/lib/pkcs9/ratelimit"
30 "github.com/sassoftware/relic/lib/pkcs9/timestampcache"
31 "github.com/sassoftware/relic/lib/x509tools"
32 )
33
34 type tsClient struct {
35 conf *config.TimestampConfig
36 client *http.Client
37 }
38
39 func New(conf *config.TimestampConfig) (t pkcs9.Timestamper, err error) {
40 tlsconf := &tls.Config{}
41 if err := x509tools.LoadCertPool(conf.CaCert, tlsconf); err != nil {
42 return nil, err
43 }
44 client := &http.Client{
45 Timeout: time.Second * time.Duration(conf.Timeout),
46 Transport: &http.Transport{
47 TLSClientConfig: tlsconf,
48 },
49 }
50 t = tsClient{conf, client}
51 if conf.RateLimit != 0 {
52 t = ratelimit.New(t, conf.RateLimit, conf.RateBurst)
53 }
54 if len(conf.Memcache) != 0 {
55 t, err = timestampcache.New(t, conf.Memcache)
56 if err != nil {
57 return nil, err
58 }
59 }
60 return
61 }
62
63 func (c tsClient) Timestamp(ctx context.Context, req *pkcs9.Request) (*pkcs7.ContentInfoSignedData, error) {
64 var urls []string
65 if req.Legacy {
66 urls = c.conf.MsURLs
67 if len(urls) == 0 {
68 return nil, errors.New("timestamp.msurls is empty")
69 }
70 } else {
71 urls = c.conf.URLs
72 if len(urls) == 0 {
73 return nil, errors.New("timestamp.urls is empty")
74 }
75 }
76 imprint := req.EncryptedDigest
77 if !req.Legacy {
78 d := req.Hash.New()
79 d.Write(imprint)
80 imprint = d.Sum(nil)
81 }
82 var err error
83 for _, url := range urls {
84 if err != nil {
85 log.Printf("warning: timestamping failed: %s\n trying next server %s...\n", err, url)
86 }
87 var token *pkcs7.ContentInfoSignedData
88 token, err = c.do(ctx, url, req, imprint)
89 if err == nil {
90 return token, nil
91 }
92 }
93 return nil, fmt.Errorf("timestamping failed: %s", err)
94 }
95
96 func (c tsClient) do(ctx context.Context, url string, req *pkcs9.Request, imprint []byte) (*pkcs7.ContentInfoSignedData, error) {
97 var msg *pkcs9.TimeStampReq
98 var httpReq *http.Request
99 var err error
100 if !req.Legacy {
101 msg, httpReq, err = pkcs9.NewRequest(url, req.Hash, imprint)
102 } else {
103 httpReq, err = pkcs9.NewLegacyRequest(url, imprint)
104 }
105 if err != nil {
106 return nil, err
107 }
108 httpReq.Header.Set("User-Agent", config.UserAgent)
109 resp, err := c.client.Do(httpReq.WithContext(ctx))
110 if err != nil {
111 return nil, err
112 }
113 body, err := ioutil.ReadAll(resp.Body)
114 resp.Body.Close()
115 if err != nil {
116 return nil, err
117 } else if resp.StatusCode != 200 {
118 return nil, fmt.Errorf("%s: HTTP %s\n%s", url, resp.Status, body)
119 }
120 if req.Legacy {
121 return pkcs9.ParseLegacyResponse(body)
122 }
123 return msg.ParseResponse(body)
124 }
125
View as plain text