aboutsummaryrefslogtreecommitdiff
path: root/src/gnunet/cmd/revoke-zonekey/main.go
diff options
context:
space:
mode:
Diffstat (limited to 'src/gnunet/cmd/revoke-zonekey/main.go')
-rw-r--r--src/gnunet/cmd/revoke-zonekey/main.go336
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
19package main
20
21import (
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
45const (
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
54type 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.
64func 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
106func (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.
132func (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.
153func 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}