diff options
Diffstat (limited to 'src/gnunet/cmd/revoke-zonekey/main.go')
-rw-r--r-- | src/gnunet/cmd/revoke-zonekey/main.go | 336 |
1 files changed, 336 insertions, 0 deletions
diff --git a/src/gnunet/cmd/revoke-zonekey/main.go b/src/gnunet/cmd/revoke-zonekey/main.go new file mode 100644 index 0000000..298a7e4 --- /dev/null +++ b/src/gnunet/cmd/revoke-zonekey/main.go | |||
@@ -0,0 +1,336 @@ | |||
1 | // This file is part of gnunet-go, a GNUnet-implementation in Golang. | ||
2 | // Copyright (C) 2019, 2020 Bernd Fix >Y< | ||
3 | // | ||
4 | // gnunet-go is free software: you can redistribute it and/or modify it | ||
5 | // under the terms of the GNU Affero General Public License as published | ||
6 | // by the Free Software Foundation, either version 3 of the License, | ||
7 | // or (at your option) any later version. | ||
8 | // | ||
9 | // gnunet-go is distributed in the hope that it will be useful, but | ||
10 | // WITHOUT ANY WARRANTY; without even the implied warranty of | ||
11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
12 | // Affero General Public License for more details. | ||
13 | // | ||
14 | // You should have received a copy of the GNU Affero General Public License | ||
15 | // along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
16 | // | ||
17 | // SPDX-License-Identifier: AGPL3.0-or-later | ||
18 | |||
19 | package main | ||
20 | |||
21 | import ( | ||
22 | "context" | ||
23 | "encoding/base64" | ||
24 | "flag" | ||
25 | "fmt" | ||
26 | "log" | ||
27 | "os" | ||
28 | "os/signal" | ||
29 | "sync" | ||
30 | "syscall" | ||
31 | |||
32 | "gnunet/crypto" | ||
33 | "gnunet/service/revocation" | ||
34 | "gnunet/util" | ||
35 | |||
36 | "github.com/bfix/gospel/data" | ||
37 | ) | ||
38 | |||
39 | //---------------------------------------------------------------------- | ||
40 | // Data structure used to calculate a valid revocation for a given | ||
41 | // zone key. | ||
42 | //---------------------------------------------------------------------- | ||
43 | |||
44 | // State of RevData calculation | ||
45 | const ( | ||
46 | S_NEW = iota // start new PoW calculation | ||
47 | S_CONT // continue PoW calculation | ||
48 | S_DONE // PoW calculation done | ||
49 | S_SIGNED // revocation data signed | ||
50 | ) | ||
51 | |||
52 | // RevData is the storage layout for persistent data used by this program. | ||
53 | // Data is read from and written to a file | ||
54 | type RevData struct { | ||
55 | Rd *revocation.RevDataCalc `` // Revocation data | ||
56 | T util.RelativeTime `` // time spend in calculations | ||
57 | Last uint64 `order:"big"` // last value used for PoW test | ||
58 | Numbits uint8 `` // number of leading zero-bits (difficulty) | ||
59 | State uint8 `` // processing state | ||
60 | } | ||
61 | |||
62 | // ReadRevData restores revocation data from perstistent storage. If no | ||
63 | // stored data is found, a new revocation data structure is returned. | ||
64 | func ReadRevData(filename string, bits int, zk *crypto.ZoneKey) (rd *RevData, err error) { | ||
65 | // create new initialized revocation instance with no PoWs. | ||
66 | rd = &RevData{ | ||
67 | Rd: revocation.NewRevDataCalc(zk), | ||
68 | Numbits: uint8(bits), | ||
69 | T: util.NewRelativeTime(0), | ||
70 | State: S_NEW, | ||
71 | } | ||
72 | |||
73 | // read revocation object from file. If the file does not exist, a new | ||
74 | // calculation is started; otherwise the old calculation will continue. | ||
75 | var file *os.File | ||
76 | if file, err = os.Open(filename); err != nil { | ||
77 | return | ||
78 | } | ||
79 | // read existing file | ||
80 | dataBuf := make([]byte, rd.size()) | ||
81 | var n int | ||
82 | if n, err = file.Read(dataBuf); err != nil { | ||
83 | err = fmt.Errorf("Error reading file: " + err.Error()) | ||
84 | return | ||
85 | } | ||
86 | if n != len(dataBuf) { | ||
87 | err = fmt.Errorf("File size mismatch") | ||
88 | return | ||
89 | } | ||
90 | if err = data.Unmarshal(&rd, dataBuf); err != nil { | ||
91 | err = fmt.Errorf("File corrupted: " + err.Error()) | ||
92 | return | ||
93 | } | ||
94 | if !zk.Equal(&rd.Rd.RevData.ZoneKeySig.ZoneKey) { | ||
95 | err = fmt.Errorf("Zone key mismatch") | ||
96 | return | ||
97 | } | ||
98 | bits = int(rd.Numbits) | ||
99 | if err = file.Close(); err != nil { | ||
100 | err = fmt.Errorf("Error closing file: " + err.Error()) | ||
101 | } | ||
102 | return | ||
103 | } | ||
104 | |||
105 | // Write revocation data to file | ||
106 | func (r *RevData) Write(filename string) (err error) { | ||
107 | var file *os.File | ||
108 | if file, err = os.Create(filename); err != nil { | ||
109 | return fmt.Errorf("Can't write to output file: " + err.Error()) | ||
110 | } | ||
111 | var buf []byte | ||
112 | if buf, err = data.Marshal(r); err != nil { | ||
113 | return fmt.Errorf("Internal error: " + err.Error()) | ||
114 | } | ||
115 | if len(buf) != r.size() { | ||
116 | return fmt.Errorf("Internal error: Buffer mismatch %d != %d", len(buf), r.size()) | ||
117 | } | ||
118 | var n int | ||
119 | if n, err = file.Write(buf); err != nil { | ||
120 | return fmt.Errorf("Can't write to output file: " + err.Error()) | ||
121 | } | ||
122 | if n != len(buf) { | ||
123 | return fmt.Errorf("Can't write data to output file!") | ||
124 | } | ||
125 | if err = file.Close(); err != nil { | ||
126 | return fmt.Errorf("Error closing file: " + err.Error()) | ||
127 | } | ||
128 | return | ||
129 | } | ||
130 | |||
131 | // size of the RevData instance in bytes. | ||
132 | func (r *RevData) size() int { | ||
133 | return 18 + r.Rd.Size() | ||
134 | } | ||
135 | |||
136 | // revoke-zonekey generates a revocation message in a multi-step/multi-state | ||
137 | // process run stand-alone from other GNUnet services: | ||
138 | // | ||
139 | // (1) Generate the desired PoWs for the public zone key: | ||
140 | // This process can be started, stopped and resumed, so the long | ||
141 | // calculation time (usually days or even weeks) can be interruped if | ||
142 | // desired. For security reasons you should only pass the "-z" argument to | ||
143 | // this step but not the "-k" argument (private key) as it is not required | ||
144 | // to calculate the PoWs. | ||
145 | // | ||
146 | // | ||
147 | // (2) A fully generated PoW set can be signed with the private key to create | ||
148 | // the final revocation data to be send out. This requires to pass the "-k" | ||
149 | // and "-z" argument. | ||
150 | // | ||
151 | // The two steps can be run (sequentially) on separate machines; step one requires | ||
152 | // computing power nd memory and step two requires a trusted environment. | ||
153 | func main() { | ||
154 | log.Println("*** Compute revocation data for a zone key") | ||
155 | log.Println("*** Copyright (c) 2020-2022, Bernd Fix >Y<") | ||
156 | log.Println("*** This is free software distributed under the Affero GPL v3.") | ||
157 | |||
158 | //------------------------------------------------------------------ | ||
159 | // handle command line arguments | ||
160 | //------------------------------------------------------------------ | ||
161 | var ( | ||
162 | verbose bool // be verbose with messages | ||
163 | bits int // number of leading zero-bit requested | ||
164 | zonekey string // zonekey to be revoked | ||
165 | prvkey string // private zonekey (base64-encoded key data) | ||
166 | testing bool // test mode (no minimum difficulty) | ||
167 | filename string // name of file for persistance | ||
168 | ) | ||
169 | minDiff := revocation.MinDifficulty | ||
170 | flag.IntVar(&bits, "b", minDiff+1, "Number of leading zero bits") | ||
171 | flag.StringVar(&zonekey, "z", "", "Zone key to be revoked (zone ID)") | ||
172 | flag.StringVar(&prvkey, "k", "", "Private zone key (base54-encoded)") | ||
173 | flag.StringVar(&filename, "f", "", "Name of file to store revocation") | ||
174 | flag.BoolVar(&verbose, "v", false, "verbose output") | ||
175 | flag.BoolVar(&testing, "t", false, "test-mode only") | ||
176 | flag.Parse() | ||
177 | |||
178 | // check arguments (difficulty, zonekey and filename) | ||
179 | if bits < minDiff { | ||
180 | if testing { | ||
181 | log.Printf("WARNING: difficulty is less than %d!", minDiff) | ||
182 | } else { | ||
183 | log.Printf("INFO: difficulty set to %d (required minimum)", minDiff) | ||
184 | bits = minDiff | ||
185 | } | ||
186 | } | ||
187 | if len(filename) == 0 { | ||
188 | log.Fatal("Missing '-f' argument (filename for revocation data)") | ||
189 | } | ||
190 | |||
191 | //------------------------------------------------------------------ | ||
192 | // Handle zone keys. | ||
193 | //------------------------------------------------------------------ | ||
194 | var ( | ||
195 | keyData []byte // binary key data | ||
196 | zk *crypto.ZoneKey // GNUnet zone key | ||
197 | sk *crypto.ZonePrivate // GNUnet private zone key | ||
198 | err error | ||
199 | ) | ||
200 | // reconstruct public key | ||
201 | if keyData, err = util.DecodeStringToBinary(zonekey, 32); err != nil { | ||
202 | log.Fatal("Invalid zonekey encoding: " + err.Error()) | ||
203 | } | ||
204 | if zk, err = crypto.NewZoneKey(keyData); err != nil { | ||
205 | log.Fatal("Invalid zonekey format: " + err.Error()) | ||
206 | } | ||
207 | // reconstruct private key (optional) | ||
208 | if len(prvkey) > 0 { | ||
209 | if keyData, err = base64.StdEncoding.DecodeString(prvkey); err != nil { | ||
210 | log.Fatal("Invalid private zonekey encoding: " + err.Error()) | ||
211 | } | ||
212 | if sk, err = crypto.NewZonePrivate(zk.Type, keyData); err != nil { | ||
213 | log.Fatal("Invalid zonekey format: " + err.Error()) | ||
214 | } | ||
215 | // verify consistency | ||
216 | if !zk.Equal(sk.Public()) { | ||
217 | log.Fatal("Public and private zone keys don't match.") | ||
218 | } | ||
219 | } | ||
220 | |||
221 | //------------------------------------------------------------------ | ||
222 | // Read revocation data from file to continue calculation or to sign | ||
223 | // the revocation. If no file exists, a new (empty) instance is | ||
224 | // returned. | ||
225 | //------------------------------------------------------------------ | ||
226 | rd, err := ReadRevData(filename, bits, zk) | ||
227 | |||
228 | // handle revocation data state | ||
229 | switch rd.State { | ||
230 | case S_NEW: | ||
231 | log.Println("Starting new revocation calculation...") | ||
232 | rd.State = S_CONT | ||
233 | |||
234 | case S_CONT: | ||
235 | log.Printf("Revocation calculation started at %s\n", rd.Rd.Timestamp.String()) | ||
236 | log.Printf("Time spent on calculation: %s\n", rd.T.String()) | ||
237 | log.Printf("Last tested PoW value: %d\n", rd.Last) | ||
238 | log.Println("Continuing...") | ||
239 | |||
240 | case S_DONE: | ||
241 | // calculation complete: sign with private key | ||
242 | if sk == nil { | ||
243 | log.Fatal("Need to sign revocation: private key is missing.") | ||
244 | } | ||
245 | log.Println("Signing revocation with private key") | ||
246 | if err := rd.Rd.Sign(sk); err != nil { | ||
247 | log.Fatal("Failed to sign revocation: " + err.Error()) | ||
248 | } | ||
249 | // write final revocation | ||
250 | rd.State = S_SIGNED | ||
251 | if err = rd.Write(filename); err != nil { | ||
252 | log.Fatal("Failed to write revocation: " + err.Error()) | ||
253 | } | ||
254 | log.Println("Revocation complete and ready for (later) use.") | ||
255 | return | ||
256 | } | ||
257 | // Continue (or start) calculation | ||
258 | log.Println("Press ^C to abort...") | ||
259 | log.Printf("Difficulty: %d\n", bits) | ||
260 | |||
261 | ctx, cancelFcn := context.WithCancel(context.Background()) | ||
262 | wg := new(sync.WaitGroup) | ||
263 | wg.Add(1) | ||
264 | go func() { | ||
265 | defer wg.Done() | ||
266 | // show progress messages | ||
267 | cb := func(average float64, last uint64) { | ||
268 | log.Printf("Improved PoW: %.2f average zero bits, %d steps\n", average, last) | ||
269 | } | ||
270 | |||
271 | // calculate revocation data until the required difficulty is met | ||
272 | // or the process is terminated by the user (by pressing ^C). | ||
273 | startTime := util.AbsoluteTimeNow() | ||
274 | average, last := rd.Rd.Compute(ctx, bits, rd.Last, cb) | ||
275 | |||
276 | // check achieved diffiulty (average) | ||
277 | if average < float64(bits) { | ||
278 | // The calculation was interrupted; we still need to compute | ||
279 | // more and better PoWs... | ||
280 | log.Printf("Incomplete revocation: Only %f zero bits on average!\n", average) | ||
281 | rd.State = S_CONT | ||
282 | } else { | ||
283 | // we have reached the required PoW difficulty | ||
284 | rd.State = S_DONE | ||
285 | // check if we have a valid revocation. | ||
286 | log.Println("Revocation calculation complete:") | ||
287 | diff, rc := rd.Rd.Verify(false) | ||
288 | switch { | ||
289 | case rc == -1: | ||
290 | log.Println(" Missing/invalid signature") | ||
291 | case rc == -2: | ||
292 | log.Println(" Expired revocation") | ||
293 | case rc == -3: | ||
294 | log.Println(" Wrong PoW sequence order") | ||
295 | case diff < float64(revocation.MinAvgDifficulty): | ||
296 | log.Println(" Difficulty to small") | ||
297 | default: | ||
298 | log.Printf(" Difficulty is %.2f\n", diff) | ||
299 | } | ||
300 | } | ||
301 | // update elapsed time | ||
302 | rd.T.Add(util.AbsoluteTimeNow().Diff(startTime)) | ||
303 | rd.Last = last | ||
304 | |||
305 | log.Println("Writing revocation data to file...") | ||
306 | if err = rd.Write(filename); err != nil { | ||
307 | log.Fatal("Can't write to file: " + err.Error()) | ||
308 | } | ||
309 | }() | ||
310 | |||
311 | go func() { | ||
312 | // handle OS signals | ||
313 | sigCh := make(chan os.Signal, 5) | ||
314 | signal.Notify(sigCh) | ||
315 | loop: | ||
316 | for { | ||
317 | select { | ||
318 | // handle OS signals | ||
319 | case sig := <-sigCh: | ||
320 | switch sig { | ||
321 | case syscall.SIGKILL, syscall.SIGINT, syscall.SIGTERM: | ||
322 | log.Printf("Terminating (on signal '%s')\n", sig) | ||
323 | cancelFcn() | ||
324 | break loop | ||
325 | case syscall.SIGHUP: | ||
326 | log.Println("SIGHUP") | ||
327 | case syscall.SIGURG: | ||
328 | // TODO: https://github.com/golang/go/issues/37942 | ||
329 | default: | ||
330 | log.Println("Unhandled signal: " + sig.String()) | ||
331 | } | ||
332 | } | ||
333 | } | ||
334 | }() | ||
335 | wg.Wait() | ||
336 | } | ||