// Copyright © SAS Institute Inc. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package tsclient import ( "context" "crypto/tls" "errors" "fmt" "io/ioutil" "log" "net/http" "time" "github.com/sassoftware/relic/config" "github.com/sassoftware/relic/lib/pkcs7" "github.com/sassoftware/relic/lib/pkcs9" "github.com/sassoftware/relic/lib/pkcs9/ratelimit" "github.com/sassoftware/relic/lib/pkcs9/timestampcache" "github.com/sassoftware/relic/lib/x509tools" ) type tsClient struct { conf *config.TimestampConfig client *http.Client } func New(conf *config.TimestampConfig) (t pkcs9.Timestamper, err error) { tlsconf := &tls.Config{} if err := x509tools.LoadCertPool(conf.CaCert, tlsconf); err != nil { return nil, err } client := &http.Client{ Timeout: time.Second * time.Duration(conf.Timeout), Transport: &http.Transport{ TLSClientConfig: tlsconf, }, } t = tsClient{conf, client} if conf.RateLimit != 0 { t = ratelimit.New(t, conf.RateLimit, conf.RateBurst) } if len(conf.Memcache) != 0 { t, err = timestampcache.New(t, conf.Memcache) if err != nil { return nil, err } } return } func (c tsClient) Timestamp(ctx context.Context, req *pkcs9.Request) (*pkcs7.ContentInfoSignedData, error) { var urls []string if req.Legacy { urls = c.conf.MsURLs if len(urls) == 0 { return nil, errors.New("timestamp.msurls is empty") } } else { urls = c.conf.URLs if len(urls) == 0 { return nil, errors.New("timestamp.urls is empty") } } imprint := req.EncryptedDigest if !req.Legacy { d := req.Hash.New() d.Write(imprint) imprint = d.Sum(nil) } var err error for _, url := range urls { if err != nil { log.Printf("warning: timestamping failed: %s\n trying next server %s...\n", err, url) } var token *pkcs7.ContentInfoSignedData token, err = c.do(ctx, url, req, imprint) if err == nil { return token, nil } } return nil, fmt.Errorf("timestamping failed: %s", err) } func (c tsClient) do(ctx context.Context, url string, req *pkcs9.Request, imprint []byte) (*pkcs7.ContentInfoSignedData, error) { var msg *pkcs9.TimeStampReq var httpReq *http.Request var err error if !req.Legacy { msg, httpReq, err = pkcs9.NewRequest(url, req.Hash, imprint) } else { httpReq, err = pkcs9.NewLegacyRequest(url, imprint) } if err != nil { return nil, err } httpReq.Header.Set("User-Agent", config.UserAgent) resp, err := c.client.Do(httpReq.WithContext(ctx)) if err != nil { return nil, err } body, err := ioutil.ReadAll(resp.Body) resp.Body.Close() if err != nil { return nil, err } else if resp.StatusCode != 200 { return nil, fmt.Errorf("%s: HTTP %s\n%s", url, resp.Status, body) } if req.Legacy { return pkcs9.ParseLegacyResponse(body) } return msg.ParseResponse(body) }