github.com/decred/politeia@v1.4.0/politeiad/backend/gitbe/dcrtime.go (about)

     1  // Copyright (c) 2020-2021 The Decred developers
     2  // Use of this source code is governed by an ISC
     3  // license that can be found in the LICENSE file.
     4  
     5  package gitbe
     6  
     7  import (
     8  	"bytes"
     9  	"crypto/sha256"
    10  	"crypto/tls"
    11  	"encoding/hex"
    12  	"encoding/json"
    13  	"errors"
    14  	"fmt"
    15  	"io"
    16  	"net/http"
    17  	"time"
    18  
    19  	v1 "github.com/decred/dcrtime/api/v1"
    20  	"github.com/decred/dcrtime/merkle"
    21  )
    22  
    23  var (
    24  	skipVerify = false
    25  	httpClient = &http.Client{
    26  		Timeout: 1 * time.Minute,
    27  		Transport: &http.Transport{
    28  			IdleConnTimeout:       1 * time.Minute,
    29  			ResponseHeaderTimeout: 1 * time.Minute,
    30  			TLSClientConfig: &tls.Config{
    31  				InsecureSkipVerify: skipVerify,
    32  			},
    33  		},
    34  	}
    35  )
    36  
    37  type errNotAnchored struct {
    38  	err error
    39  }
    40  
    41  func (e errNotAnchored) Error() string {
    42  	return e.err.Error()
    43  }
    44  
    45  // isDigest determines if a string is a valid SHA256 digest.
    46  func isDigest(digest string) bool {
    47  	return v1.RegexpSHA256.MatchString(digest)
    48  }
    49  
    50  // getError returns the error that is embedded in a JSON reply.
    51  func getError(r io.Reader) (string, error) {
    52  	var e interface{}
    53  	decoder := json.NewDecoder(r)
    54  	if err := decoder.Decode(&e); err != nil {
    55  		return "", err
    56  	}
    57  	m, ok := e.(map[string]interface{})
    58  	if !ok {
    59  		return "", fmt.Errorf("Could not decode response")
    60  	}
    61  	rError, ok := m["error"]
    62  	if !ok {
    63  		return "", fmt.Errorf("No error response")
    64  	}
    65  	return fmt.Sprintf("%v", rError), nil
    66  }
    67  
    68  // timestamp sends a Timestamp request to the provided host.  The caller is
    69  // responsible for assembling the host string based on what net to use.
    70  func timestamp(id, host string, digests []*[sha256.Size]byte) error {
    71  	// batch uploads
    72  	ts := v1.Timestamp{
    73  		ID:      id,
    74  		Digests: make([]string, 0, len(digests)),
    75  	}
    76  	for _, digest := range digests {
    77  		ts.Digests = append(ts.Digests, hex.EncodeToString(digest[:]))
    78  	}
    79  	b, err := json.Marshal(ts)
    80  	if err != nil {
    81  		return err
    82  	}
    83  
    84  	r, err := httpClient.Post(host+v1.TimestampRoute, "application/json",
    85  		bytes.NewReader(b))
    86  	if err != nil {
    87  		return err
    88  	}
    89  	defer r.Body.Close()
    90  
    91  	if r.StatusCode != http.StatusOK {
    92  		e, err := getError(r.Body)
    93  		if err != nil {
    94  			return fmt.Errorf("%v", r.Status)
    95  		}
    96  		return fmt.Errorf("%v: %v", r.Status, e)
    97  	}
    98  
    99  	// Decode response.
   100  	var tsReply v1.TimestampReply
   101  	decoder := json.NewDecoder(r.Body)
   102  	if err := decoder.Decode(&tsReply); err != nil {
   103  		return fmt.Errorf("Could node decode TimestampReply: %v", err)
   104  	}
   105  
   106  	for i, result := range tsReply.Results {
   107  		if result == v1.ResultExistsError {
   108  			// Been alread anchored so ignore.
   109  			continue
   110  		}
   111  		if result != v1.ResultOK {
   112  			msg, ok := v1.Result[result]
   113  			if !ok {
   114  				msg = "UNKNOWN ERROR"
   115  			}
   116  			return fmt.Errorf("anchor (%v): %v", i, msg)
   117  		}
   118  	}
   119  
   120  	return nil
   121  }
   122  
   123  // verifyTimestamp sends a dcrtime Verify command to the provided host.  It
   124  // checks and validates the entire reply.  A single failure is considered
   125  // terminal and an error is returned.  If the reply is valid it is returned to
   126  // the caller for further processing.  This means that the caller can be
   127  // assured that all checks have been done and the data is readily usable.
   128  //
   129  // Note the Result in the reply will be set to OK as soon as the digest is
   130  // waiting to be anchored. The ChainInformation will be populated once the
   131  // digest has been included in a dcr transaction, except for the ChainTimestamp
   132  // field. The ChainTimestamp field is only populated once the dcr transaction
   133  // has 6 confirmations.
   134  func verifyTimestamp(id, host string, digests []string) (*v1.VerifyReply, error) {
   135  	ver := v1.Verify{
   136  		ID: id,
   137  	}
   138  
   139  	for _, digest := range digests {
   140  		if isDigest(digest) {
   141  			ver.Digests = append(ver.Digests, digest)
   142  			continue
   143  		}
   144  
   145  		return nil, fmt.Errorf("not a valid digest: %v", digest)
   146  	}
   147  
   148  	// Convert Verify to JSON
   149  	b, err := json.Marshal(ver)
   150  	if err != nil {
   151  		return nil, err
   152  	}
   153  
   154  	r, err := httpClient.Post(host+v1.VerifyRoute, "application/json",
   155  		bytes.NewReader(b))
   156  	if err != nil {
   157  		return nil, err
   158  	}
   159  	defer r.Body.Close()
   160  
   161  	if r.StatusCode != http.StatusOK {
   162  		e, err := getError(r.Body)
   163  		if err != nil {
   164  			return nil, fmt.Errorf("%v", r.Status)
   165  		}
   166  		return nil, fmt.Errorf("%v: %v", r.Status, e)
   167  	}
   168  
   169  	// Decode response.
   170  	var vr v1.VerifyReply
   171  	decoder := json.NewDecoder(r.Body)
   172  	if err := decoder.Decode(&vr); err != nil {
   173  		return nil, fmt.Errorf("Could node decode VerifyReply: %v", err)
   174  	}
   175  
   176  	for _, v := range vr.Digests {
   177  		if v.Result != v1.ResultOK {
   178  			// We only report not found since the other errors are
   179  			// not applicable.
   180  			if v.Result == v1.ResultDoesntExistError {
   181  				return nil, fmt.Errorf("Digest not found: %v",
   182  					v.Digest)
   183  			}
   184  			continue
   185  		}
   186  		_, ok := v1.Result[v.Result]
   187  		if !ok {
   188  			return nil, fmt.Errorf("%v invalid error code %v",
   189  				v.Digest, v.Result)
   190  		}
   191  
   192  		// Verify merkle path.
   193  		root, err := merkle.VerifyAuthPath(&v.ChainInformation.MerklePath)
   194  		if err != nil {
   195  			if !errors.Is(err, merkle.ErrEmpty) {
   196  				return nil, fmt.Errorf("%v invalid auth path "+
   197  					"%v", v.Digest, err)
   198  			}
   199  			return nil, errNotAnchored{
   200  				err: fmt.Errorf("%v Not anchored", v.Digest),
   201  			}
   202  		}
   203  
   204  		// Verify merkle root.
   205  		merkleRoot, err := hex.DecodeString(v.ChainInformation.MerkleRoot)
   206  		if err != nil {
   207  			return nil, fmt.Errorf("invalid merkle root: %v", err)
   208  		}
   209  		// This is silly since we check against returned root.
   210  		if !bytes.Equal(root[:], merkleRoot) {
   211  			return nil, fmt.Errorf("%v invalid merkle root",
   212  				v.Digest)
   213  		}
   214  
   215  		// All good
   216  	}
   217  
   218  	return &vr, nil
   219  }