github.com/decred/politeia@v1.4.0/politeiawww/client/records.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 client
     6  
     7  import (
     8  	"bytes"
     9  	"encoding/base64"
    10  	"encoding/hex"
    11  	"encoding/json"
    12  	"errors"
    13  	"fmt"
    14  	"io"
    15  	"net/http"
    16  	"strconv"
    17  	"strings"
    18  
    19  	"github.com/decred/politeia/politeiad/api/v1/identity"
    20  	backend "github.com/decred/politeia/politeiad/backendv2"
    21  	"github.com/decred/politeia/politeiad/plugins/usermd"
    22  	rcv1 "github.com/decred/politeia/politeiawww/api/records/v1"
    23  	v1 "github.com/decred/politeia/politeiawww/api/records/v1"
    24  	"github.com/decred/politeia/util"
    25  	"github.com/google/uuid"
    26  )
    27  
    28  // RecordPolicy sends a records v1 Policy request to politeiawww.
    29  func (c *Client) RecordPolicy() (*rcv1.PolicyReply, error) {
    30  	resBody, err := c.makeReq(http.MethodPost,
    31  		rcv1.APIRoute, rcv1.RoutePolicy, nil)
    32  	if err != nil {
    33  		return nil, err
    34  	}
    35  
    36  	var pr rcv1.PolicyReply
    37  	err = json.Unmarshal(resBody, &pr)
    38  	if err != nil {
    39  		return nil, err
    40  	}
    41  
    42  	return &pr, nil
    43  }
    44  
    45  // RecordNew sends a records v1 New request to politeiawww.
    46  func (c *Client) RecordNew(n rcv1.New) (*rcv1.NewReply, error) {
    47  	resBody, err := c.makeReq(http.MethodPost,
    48  		rcv1.APIRoute, rcv1.RouteNew, n)
    49  	if err != nil {
    50  		return nil, err
    51  	}
    52  
    53  	var nr rcv1.NewReply
    54  	err = json.Unmarshal(resBody, &nr)
    55  	if err != nil {
    56  		return nil, err
    57  	}
    58  
    59  	return &nr, nil
    60  }
    61  
    62  // RecordEdit sends a records v1 Edit request to politeiawww.
    63  func (c *Client) RecordEdit(e rcv1.Edit) (*rcv1.EditReply, error) {
    64  	resBody, err := c.makeReq(http.MethodPost,
    65  		rcv1.APIRoute, rcv1.RouteEdit, e)
    66  	if err != nil {
    67  		return nil, err
    68  	}
    69  
    70  	var er rcv1.EditReply
    71  	err = json.Unmarshal(resBody, &er)
    72  	if err != nil {
    73  		return nil, err
    74  	}
    75  
    76  	return &er, nil
    77  }
    78  
    79  // RecordSetStatus sends a records v1 SetStatus request to politeiawww.
    80  func (c *Client) RecordSetStatus(ss rcv1.SetStatus) (*rcv1.SetStatusReply, error) {
    81  	resBody, err := c.makeReq(http.MethodPost,
    82  		rcv1.APIRoute, rcv1.RouteSetStatus, ss)
    83  	if err != nil {
    84  		return nil, err
    85  	}
    86  
    87  	var ssr rcv1.SetStatusReply
    88  	err = json.Unmarshal(resBody, &ssr)
    89  	if err != nil {
    90  		return nil, err
    91  	}
    92  
    93  	return &ssr, nil
    94  }
    95  
    96  // RecordDetails sends a records v1 Details request to politeiawww.
    97  func (c *Client) RecordDetails(d rcv1.Details) (*rcv1.Record, error) {
    98  	resBody, err := c.makeReq(http.MethodPost,
    99  		rcv1.APIRoute, rcv1.RouteDetails, d)
   100  	if err != nil {
   101  		return nil, err
   102  	}
   103  
   104  	var dr rcv1.DetailsReply
   105  	err = json.Unmarshal(resBody, &dr)
   106  	if err != nil {
   107  		return nil, err
   108  	}
   109  
   110  	return &dr.Record, nil
   111  }
   112  
   113  // RecordTimestamps sends a records v1 Timestamps request to politeiawww.
   114  func (c *Client) RecordTimestamps(t rcv1.Timestamps) (*rcv1.TimestampsReply, error) {
   115  	resBody, err := c.makeReq(http.MethodPost,
   116  		rcv1.APIRoute, rcv1.RouteTimestamps, t)
   117  	if err != nil {
   118  		return nil, err
   119  	}
   120  
   121  	var tr rcv1.TimestampsReply
   122  	err = json.Unmarshal(resBody, &tr)
   123  	if err != nil {
   124  		return nil, err
   125  	}
   126  
   127  	return &tr, nil
   128  }
   129  
   130  // Records sends a records v1 Records request to politeiawww.
   131  func (c *Client) Records(r rcv1.Records) (map[string]rcv1.Record, error) {
   132  	resBody, err := c.makeReq(http.MethodPost,
   133  		rcv1.APIRoute, rcv1.RouteRecords, r)
   134  	if err != nil {
   135  		return nil, err
   136  	}
   137  
   138  	var rr rcv1.RecordsReply
   139  	err = json.Unmarshal(resBody, &rr)
   140  	if err != nil {
   141  		return nil, err
   142  	}
   143  
   144  	return rr.Records, nil
   145  }
   146  
   147  // RecordInventory sends a records v1 Inventory request to politeiawww.
   148  func (c *Client) RecordInventory(i rcv1.Inventory) (*rcv1.InventoryReply, error) {
   149  	resBody, err := c.makeReq(http.MethodPost,
   150  		rcv1.APIRoute, rcv1.RouteInventory, i)
   151  	if err != nil {
   152  		return nil, err
   153  	}
   154  
   155  	var ir rcv1.InventoryReply
   156  	err = json.Unmarshal(resBody, &ir)
   157  	if err != nil {
   158  		return nil, err
   159  	}
   160  
   161  	return &ir, nil
   162  }
   163  
   164  // RecordInventoryOrdered sends a records v1 InventoryOrdered request to
   165  // politeiawww.
   166  func (c *Client) RecordInventoryOrdered(i rcv1.InventoryOrdered) (*rcv1.InventoryOrderedReply, error) {
   167  	resBody, err := c.makeReq(http.MethodPost,
   168  		rcv1.APIRoute, rcv1.RouteInventoryOrdered, i)
   169  	if err != nil {
   170  		return nil, err
   171  	}
   172  
   173  	var ir rcv1.InventoryOrderedReply
   174  	err = json.Unmarshal(resBody, &ir)
   175  	if err != nil {
   176  		return nil, err
   177  	}
   178  
   179  	return &ir, nil
   180  }
   181  
   182  // UserRecords sends a records v1 UserRecords request to politeiawww.
   183  func (c *Client) UserRecords(ur rcv1.UserRecords) (*rcv1.UserRecordsReply, error) {
   184  	resBody, err := c.makeReq(http.MethodPost,
   185  		rcv1.APIRoute, rcv1.RouteUserRecords, ur)
   186  	if err != nil {
   187  		return nil, err
   188  	}
   189  
   190  	var urr rcv1.UserRecordsReply
   191  	err = json.Unmarshal(resBody, &urr)
   192  	if err != nil {
   193  		return nil, err
   194  	}
   195  
   196  	return &urr, nil
   197  }
   198  
   199  // digestsVerify verifies that all file digests match the calculated SHA256
   200  // digests of the file payloads.
   201  func digestsVerify(files []rcv1.File) error {
   202  	for _, f := range files {
   203  		b, err := base64.StdEncoding.DecodeString(f.Payload)
   204  		if err != nil {
   205  			return fmt.Errorf("file: %v decode payload err %v",
   206  				f.Name, err)
   207  		}
   208  		digest := util.Digest(b)
   209  		d, ok := util.ConvertDigest(f.Digest)
   210  		if !ok {
   211  			return fmt.Errorf("file: %v invalid digest %v",
   212  				f.Name, f.Digest)
   213  		}
   214  		if !bytes.Equal(digest, d[:]) {
   215  			return fmt.Errorf("file: %v digests do not match",
   216  				f.Name)
   217  		}
   218  	}
   219  	return nil
   220  }
   221  
   222  // CensorshipRecordVerify verifies the censorship record of a records v1
   223  // Record.
   224  func CensorshipRecordVerify(r rcv1.Record, serverPubKey string) error {
   225  	if r.Status == rcv1.RecordStatusCensored {
   226  		// The files of a censored record will be deleted.
   227  		// There is nothing to verify.
   228  		return nil
   229  	}
   230  
   231  	// Verify censorship record merkle root
   232  	if len(r.Files) > 0 {
   233  		// Verify digests
   234  		err := digestsVerify(r.Files)
   235  		if err != nil {
   236  			return err
   237  		}
   238  		// Verify merkle root
   239  		digests := make([]string, 0, len(r.Files))
   240  		for _, v := range r.Files {
   241  			digests = append(digests, v.Digest)
   242  		}
   243  		mr, err := util.MerkleRoot(digests)
   244  		if err != nil {
   245  			return err
   246  		}
   247  		if hex.EncodeToString(mr[:]) != r.CensorshipRecord.Merkle {
   248  			return fmt.Errorf("merkle roots do not match")
   249  		}
   250  	}
   251  
   252  	// Verify censorship record signature
   253  	id, err := identity.PublicIdentityFromString(serverPubKey)
   254  	if err != nil {
   255  		return err
   256  	}
   257  	s, err := util.ConvertSignature(r.CensorshipRecord.Signature)
   258  	if err != nil {
   259  		return err
   260  	}
   261  	msg := []byte(r.CensorshipRecord.Merkle + r.CensorshipRecord.Token)
   262  	if !id.VerifyMessage(msg, s) {
   263  		return fmt.Errorf("invalid censorship record signature")
   264  	}
   265  
   266  	return nil
   267  }
   268  
   269  // RecordVerify verfifies the contents of a record. This includes verifying
   270  // the censorship record, the user metadata, and any status changes that are
   271  // present.
   272  //
   273  // **Note** partial record's merkle root is not verifiable - when generating
   274  // the record's merkle all files must be present.
   275  func RecordVerify(r rcv1.Record, serverPubKey string) error {
   276  	// Verify censorship record
   277  	err := CensorshipRecordVerify(r, serverPubKey)
   278  	if err != nil {
   279  		return fmt.Errorf("verify censorship record: %v", err)
   280  	}
   281  
   282  	// Verify user metadata
   283  	um, err := UserMetadataDecode(r.Metadata)
   284  	if err != nil {
   285  		return err
   286  	}
   287  	err = UserMetadataVerify(*um, r.CensorshipRecord.Merkle)
   288  	if err != nil {
   289  		return fmt.Errorf("verify user metadata: %v", err)
   290  	}
   291  
   292  	// Verify status changes
   293  	sc, err := StatusChangesDecode(r.Metadata)
   294  	if err != nil {
   295  		return err
   296  	}
   297  	err = StatusChangesVerify(sc)
   298  	if err != nil {
   299  		return fmt.Errorf("verify status changes: %v", err)
   300  	}
   301  
   302  	return nil
   303  }
   304  
   305  // RecordTimestampVerify verifies a records v1 API timestamp. This proves
   306  // inclusion of the data in the merkle root that was timestamped onto the dcr
   307  // blockchain.
   308  func RecordTimestampVerify(t rcv1.Timestamp) error {
   309  	return backend.VerifyTimestamp(convertRecordTimestamp(t))
   310  }
   311  
   312  // RecordTimestampsVerify verifies all timestamps in a records v1 API
   313  // timestamps reply. This proves the inclusion of the data in the merkle root
   314  // that was timestamped onto the dcr blockchain.
   315  func RecordTimestampsVerify(tr rcv1.TimestampsReply) error {
   316  	err := RecordTimestampVerify(tr.RecordMetadata)
   317  	if err != nil {
   318  		return fmt.Errorf("could not verify record metadata timestamp: %v", err)
   319  	}
   320  	for pluginID, v := range tr.Metadata {
   321  		for streamID, ts := range v {
   322  			err = RecordTimestampVerify(ts)
   323  			if err != nil {
   324  				return fmt.Errorf("could not verify metadata %v %v timestamp: %v",
   325  					pluginID, streamID, err)
   326  			}
   327  		}
   328  	}
   329  	for k, v := range tr.Files {
   330  		err = RecordTimestampVerify(v)
   331  		if err != nil {
   332  			return fmt.Errorf("could not verify file %v timestamp: %v", k, err)
   333  		}
   334  	}
   335  	return nil
   336  }
   337  
   338  // UserMetadataDecode decodes and returns the UserMetadata from the provided
   339  // metadata streams. An error is returned if a UserMetadata is not found.
   340  func UserMetadataDecode(ms []v1.MetadataStream) (*rcv1.UserMetadata, error) {
   341  	var ump *rcv1.UserMetadata
   342  	for _, v := range ms {
   343  		if v.PluginID != usermd.PluginID ||
   344  			v.StreamID != usermd.StreamIDUserMetadata {
   345  			// Not user metadata
   346  			continue
   347  		}
   348  		var um rcv1.UserMetadata
   349  		err := json.Unmarshal([]byte(v.Payload), &um)
   350  		if err != nil {
   351  			return nil, err
   352  		}
   353  		ump = &um
   354  		break
   355  	}
   356  	if ump == nil {
   357  		return nil, fmt.Errorf("user metadata not found")
   358  	}
   359  	return ump, nil
   360  }
   361  
   362  // UserMetadataVerify verifies that the UserMetadata contains a valid user ID,
   363  // a valid public key, and that this signature is a valid signature of the
   364  // record merkle root.
   365  func UserMetadataVerify(um v1.UserMetadata, merkleRoot string) error {
   366  	// Verify user ID
   367  	_, err := uuid.Parse(um.UserID)
   368  	if err != nil {
   369  		return fmt.Errorf("invalid user id: %v", err)
   370  	}
   371  
   372  	// Verify signature
   373  	err = util.VerifySignature(um.Signature, um.PublicKey, merkleRoot)
   374  	if err != nil {
   375  		return fmt.Errorf("invalid user metadata: %v", err)
   376  	}
   377  
   378  	return nil
   379  }
   380  
   381  // StatusChangesDecode decodes and returns the status changes metadata stream
   382  // from the provided metadata. An error IS NOT returned is status change
   383  // metadata is not found.
   384  func StatusChangesDecode(metadata []v1.MetadataStream) ([]v1.StatusChange, error) {
   385  	statuses := make([]v1.StatusChange, 0, 16)
   386  	for _, v := range metadata {
   387  		if v.PluginID != usermd.PluginID ||
   388  			v.StreamID != usermd.StreamIDStatusChanges {
   389  			// Not status change metadata
   390  			continue
   391  		}
   392  		d := json.NewDecoder(strings.NewReader(v.Payload))
   393  		for {
   394  			var sc v1.StatusChange
   395  			err := d.Decode(&sc)
   396  			if errors.Is(err, io.EOF) {
   397  				break
   398  			} else if err != nil {
   399  				return nil, err
   400  			}
   401  			statuses = append(statuses, sc)
   402  		}
   403  		break
   404  	}
   405  	return statuses, nil
   406  }
   407  
   408  // StatusChanges verifies the signatures on all status change metadata.
   409  func StatusChangesVerify(sc []v1.StatusChange) error {
   410  	// Verify signatures
   411  	for _, v := range sc {
   412  		var (
   413  			status  = strconv.FormatUint(uint64(v.Status), 10)
   414  			version = strconv.FormatUint(uint64(v.Version), 10)
   415  			msg     = v.Token + version + status + v.Reason
   416  		)
   417  		err := util.VerifySignature(v.Signature, v.PublicKey, msg)
   418  		if err != nil {
   419  			return fmt.Errorf("invalid status change signature %v %v: %v",
   420  				v.Token, v1.RecordStatuses[v.Status], err)
   421  		}
   422  	}
   423  
   424  	return nil
   425  }
   426  
   427  func convertRecordProof(p rcv1.Proof) backend.Proof {
   428  	return backend.Proof{
   429  		Type:       p.Type,
   430  		Digest:     p.Digest,
   431  		MerkleRoot: p.MerkleRoot,
   432  		MerklePath: p.MerklePath,
   433  		ExtraData:  p.ExtraData,
   434  	}
   435  }
   436  
   437  func convertRecordTimestamp(t rcv1.Timestamp) backend.Timestamp {
   438  	proofs := make([]backend.Proof, 0, len(t.Proofs))
   439  	for _, v := range t.Proofs {
   440  		proofs = append(proofs, convertRecordProof(v))
   441  	}
   442  	return backend.Timestamp{
   443  		Data:       t.Data,
   444  		Digest:     t.Digest,
   445  		TxID:       t.TxID,
   446  		MerkleRoot: t.MerkleRoot,
   447  		Proofs:     proofs,
   448  	}
   449  }