github.com/decred/politeia@v1.4.0/politeiawww/cmd/politeiaverify/politeiaverify.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 main 6 7 import ( 8 "encoding/hex" 9 "flag" 10 "fmt" 11 "os" 12 "path/filepath" 13 "regexp" 14 15 "github.com/decred/politeia/politeiad/api/v1/identity" 16 rcv1 "github.com/decred/politeia/politeiawww/api/records/v1" 17 "github.com/decred/politeia/politeiawww/client" 18 "github.com/decred/politeia/util" 19 ) 20 21 var ( 22 // CLI flags 23 publicKey = flag.String("k", "", "server public key") 24 token = flag.String("t", "", "record censorship token") 25 signature = flag.String("s", "", "record censorship signature") 26 ) 27 28 // loadFiles loads and returns a politeiawww records v1 File for each provided 29 // file path. 30 func loadFiles(paths []string) ([]rcv1.File, error) { 31 files := make([]rcv1.File, 0, len(paths)) 32 for _, fp := range paths { 33 fp = util.CleanAndExpandPath(fp) 34 mime, digest, payload, err := util.LoadFile(fp) 35 if err != nil { 36 return nil, err 37 } 38 files = append(files, rcv1.File{ 39 Name: filepath.Base(fp), 40 MIME: mime, 41 Digest: digest, 42 Payload: payload, 43 }) 44 } 45 return files, nil 46 } 47 48 // verifyCensorshipRecord verifies a censorship record signature for a politeia 49 // record submission. This requires passing in the server public key, the 50 // censorship token, the censorship record signature, and the file paths of all 51 // files that are part of the record. 52 func verifyCensorshipRecord(serverPubKey, token, signature string, filepaths []string) error { 53 // Verify all args are present 54 switch { 55 case serverPubKey == "": 56 return fmt.Errorf("server public key not provided") 57 case token == "": 58 return fmt.Errorf("censorship token not provided") 59 case signature == "": 60 return fmt.Errorf("censorship record signature not provided") 61 case len(filepaths) == 0: 62 return fmt.Errorf("record files not provided") 63 } 64 65 // Load record files 66 files, err := loadFiles(filepaths) 67 if err != nil { 68 return err 69 } 70 71 // Calc merkle root of files 72 digests := make([]string, 0, len(files)) 73 for _, v := range files { 74 digests = append(digests, v.Digest) 75 } 76 mr, err := util.MerkleRoot(digests) 77 if err != nil { 78 return err 79 } 80 merkle := hex.EncodeToString(mr[:]) 81 82 // Load identity 83 pid, err := identity.PublicIdentityFromString(serverPubKey) 84 if err != nil { 85 return err 86 } 87 88 // Verify record 89 r := rcv1.Record{ 90 Files: files, 91 CensorshipRecord: rcv1.CensorshipRecord{ 92 Token: token, 93 Merkle: merkle, 94 Signature: signature, 95 }, 96 } 97 err = client.RecordVerify(r, pid.String()) 98 if err != nil { 99 return err 100 } 101 102 fmt.Printf("Server key : %s\n", serverPubKey) 103 fmt.Printf("Token : %s\n", token) 104 fmt.Printf("Merkle root: %s\n", merkle) 105 fmt.Printf("Signature : %s\n\n", signature) 106 fmt.Println("Record successfully verified") 107 108 return nil 109 } 110 111 var ( 112 // Regexps for matching file bundles downloaded from politeiagui 113 expJSONFile = `.json$` 114 expRecord = `^[0-9a-f]{7,16}-v[\d]{1,2}.json$` 115 expRecordTimestamps = `^[0-9a-f]{7,16}-v[\d]{1,2}-timestamps.json$` 116 expComments = `^[0-9a-f]{7,16}-comments.json$` 117 expCommentTimestamps = `^[0-9a-f]{7,16}-comments-timestamps.json$` 118 expVotes = `^[0-9a-f]{7,16}-votes.json$` 119 expVoteTimestamps = `^[0-9a-f]{7,16}-votes-timestamps.json$` 120 121 regexpJSONFile = regexp.MustCompile(expJSONFile) 122 regexpRecord = regexp.MustCompile(expRecord) 123 regexpRecordTimestamps = regexp.MustCompile(expRecordTimestamps) 124 regexpComments = regexp.MustCompile(expComments) 125 regexpCommentTimestamps = regexp.MustCompile(expCommentTimestamps) 126 regexpVotes = regexp.MustCompile(expVotes) 127 regexpVoteTimestamps = regexp.MustCompile(expVoteTimestamps) 128 ) 129 130 // verifyFile verifies a data file downloaded from politeiagui. This can be 131 // one of the data bundles or one of the timestamp files. The file name MUST 132 // be the same file name that was downloaded from politeiagui. 133 // 134 // Files that this function accepts: 135 // Record bundle : [token]-[version].json 136 // Record timestamps : [token]-[version]-timestamps.json 137 // Comments bundle : [token]-comments.json 138 // Comment timestamps: [token]-comments-timestamps.json 139 // Votes bundle : [token]-votes.json 140 // Vote timestamps : [token]-votes-timestamps.json 141 func verifyFile(fp string) error { 142 fp = util.CleanAndExpandPath(fp) 143 filename := filepath.Base(fp) 144 145 // Match file type 146 switch { 147 case regexpRecord.FindString(filename) != "": 148 return verifyRecordBundle(fp) 149 case regexpRecordTimestamps.FindString(filename) != "": 150 return verifyRecordTimestamps(fp) 151 case regexpComments.FindString(filename) != "": 152 return verifyCommentsBundle(fp) 153 case regexpCommentTimestamps.FindString(filename) != "": 154 return verifyCommentTimestamps(fp) 155 case regexpVotes.FindString(filename) != "": 156 return verifyVotesBundle(fp) 157 case regexpVoteTimestamps.FindString(filename) != "": 158 return verifyVoteTimestamps(fp) 159 } 160 161 return fmt.Errorf("file not recognized") 162 } 163 164 func _main() error { 165 // Parse CLI arguments 166 flag.Parse() 167 args := flag.Args() 168 if len(args) == 0 { 169 return fmt.Errorf("no arguments provided") 170 } 171 172 // Check if the user is trying to verify a record submission 173 // manually. This requires passing in the server public key, the 174 // censorship token, the censorship record signature, and all of 175 // the record filepaths. 176 manual := (*publicKey != "") || (*token != "") || (*signature != "") 177 if manual { 178 // The user is trying to verify manually 179 return verifyCensorshipRecord(*publicKey, *token, *signature, args) 180 } 181 182 // The user is trying to verify a bundle file that was downloaded 183 // from politeiagui. 184 fp := args[0] 185 if regexpJSONFile.FindString(fp) == "" { 186 return fmt.Errorf("'%v' is not a json file", fp) 187 } 188 err := verifyFile(fp) 189 if err != nil { 190 return fmt.Errorf("ERR COULD NOT VERIFY: %v", err) 191 } 192 193 return nil 194 } 195 196 func main() { 197 err := _main() 198 if err != nil { 199 fmt.Fprintf(os.Stderr, "%v\n", err) 200 os.Exit(1) 201 } 202 }