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 }