diff options
author | Bernd Fix <brf@hoi-polloi.org> | 2020-05-24 13:34:05 +0200 |
---|---|---|
committer | Bernd Fix <brf@hoi-polloi.org> | 2020-05-24 13:34:05 +0200 |
commit | fdf23180919ec42422694f1f1801eead1ea576e8 (patch) | |
tree | 7ef36ebfd29816cbdbb1c16cf99c285db8025bef | |
parent | 3d0b7cab3e273dd2f3f3d8990fb868b93b6b3f95 (diff) | |
download | gnunet-go-fdf23180919ec42422694f1f1801eead1ea576e8.tar.gz gnunet-go-fdf23180919ec42422694f1f1801eead1ea576e8.zip |
Milestone #3 (RC1)
-rw-r--r-- | src/cmd/gnunet-service-revocation-go/main.go | 105 | ||||
-rw-r--r-- | src/cmd/pow-test/main.go | 51 | ||||
-rw-r--r-- | src/cmd/revoke-zonekey/main.go | 192 | ||||
-rw-r--r-- | src/gnunet/config/config.go | 18 | ||||
-rw-r--r-- | src/gnunet/message/factory.go | 2 | ||||
-rw-r--r-- | src/gnunet/message/msg_revocation.go | 35 | ||||
-rw-r--r-- | src/gnunet/modules.go | 19 | ||||
-rw-r--r-- | src/gnunet/service/gns/module.go | 21 | ||||
-rw-r--r-- | src/gnunet/service/gns/service.go | 59 | ||||
-rw-r--r-- | src/gnunet/service/revocation/module.go | 124 | ||||
-rw-r--r-- | src/gnunet/service/revocation/pow.go | 346 | ||||
-rw-r--r-- | src/gnunet/service/revocation/service.go | 160 | ||||
-rw-r--r-- | src/gnunet/util/database.go | 72 | ||||
-rw-r--r-- | src/gnunet/util/key_value_store.go | 188 | ||||
-rw-r--r-- | src/gnunet/util/time.go | 12 |
15 files changed, 1172 insertions, 232 deletions
diff --git a/src/cmd/gnunet-service-revocation-go/main.go b/src/cmd/gnunet-service-revocation-go/main.go new file mode 100644 index 0000000..c829aea --- /dev/null +++ b/src/cmd/gnunet-service-revocation-go/main.go | |||
@@ -0,0 +1,105 @@ | |||
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 | "flag" | ||
23 | "os" | ||
24 | "os/signal" | ||
25 | "syscall" | ||
26 | "time" | ||
27 | |||
28 | "github.com/bfix/gospel/logger" | ||
29 | "gnunet/config" | ||
30 | "gnunet/service" | ||
31 | "gnunet/service/revocation" | ||
32 | ) | ||
33 | |||
34 | func main() { | ||
35 | defer func() { | ||
36 | logger.Println(logger.INFO, "[revocation] Bye.") | ||
37 | // flush last messages | ||
38 | logger.Flush() | ||
39 | }() | ||
40 | logger.Println(logger.INFO, "[revocation] Starting service...") | ||
41 | |||
42 | var ( | ||
43 | cfgFile string | ||
44 | srvEndp string | ||
45 | err error | ||
46 | logLevel int | ||
47 | ) | ||
48 | // handle command line arguments | ||
49 | flag.StringVar(&cfgFile, "c", "gnunet-config.json", "GNUnet configuration file") | ||
50 | flag.StringVar(&srvEndp, "s", "", "REVOCATION service end-point") | ||
51 | flag.IntVar(&logLevel, "L", logger.INFO, "REVOCATION log level (default: INFO)") | ||
52 | flag.Parse() | ||
53 | |||
54 | // read configuration file and set missing arguments. | ||
55 | if err = config.ParseConfig(cfgFile); err != nil { | ||
56 | logger.Printf(logger.ERROR, "[revocation] Invalid configuration file: %s\n", err.Error()) | ||
57 | return | ||
58 | } | ||
59 | |||
60 | // apply configuration | ||
61 | logger.SetLogLevel(logLevel) | ||
62 | if len(srvEndp) == 0 { | ||
63 | srvEndp = config.Cfg.GNS.Endpoint | ||
64 | } | ||
65 | |||
66 | // start a new REVOCATION service | ||
67 | rvc := revocation.NewRevocationService() | ||
68 | srv := service.NewServiceImpl("revocation", rvc) | ||
69 | if err = srv.Start(srvEndp); err != nil { | ||
70 | logger.Printf(logger.ERROR, "[revocation] Error: '%s'\n", err.Error()) | ||
71 | return | ||
72 | } | ||
73 | |||
74 | // handle OS signals | ||
75 | sigCh := make(chan os.Signal, 5) | ||
76 | signal.Notify(sigCh) | ||
77 | |||
78 | // heart beat | ||
79 | tick := time.NewTicker(5 * time.Minute) | ||
80 | |||
81 | loop: | ||
82 | for { | ||
83 | select { | ||
84 | // handle OS signals | ||
85 | case sig := <-sigCh: | ||
86 | switch sig { | ||
87 | case syscall.SIGKILL, syscall.SIGINT, syscall.SIGTERM: | ||
88 | logger.Printf(logger.INFO, "[revocation] Terminating service (on signal '%s')\n", sig) | ||
89 | break loop | ||
90 | case syscall.SIGHUP: | ||
91 | logger.Println(logger.INFO, "[revocation] SIGHUP") | ||
92 | case syscall.SIGURG: | ||
93 | // TODO: https://github.com/golang/go/issues/37942 | ||
94 | default: | ||
95 | logger.Println(logger.INFO, "[revocation] Unhandled signal: "+sig.String()) | ||
96 | } | ||
97 | // handle heart beat | ||
98 | case now := <-tick.C: | ||
99 | logger.Println(logger.INFO, "[revocation] Heart beat at "+now.String()) | ||
100 | } | ||
101 | } | ||
102 | |||
103 | // terminating service | ||
104 | srv.Stop() | ||
105 | } | ||
diff --git a/src/cmd/pow-test/main.go b/src/cmd/pow-test/main.go deleted file mode 100644 index 247f442..0000000 --- a/src/cmd/pow-test/main.go +++ /dev/null | |||
@@ -1,51 +0,0 @@ | |||
1 | package main | ||
2 | |||
3 | import ( | ||
4 | "encoding/hex" | ||
5 | "flag" | ||
6 | "fmt" | ||
7 | "log" | ||
8 | |||
9 | "gnunet/service/revocation" | ||
10 | |||
11 | "github.com/bfix/gospel/crypto/ed25519" | ||
12 | "github.com/bfix/gospel/math" | ||
13 | ) | ||
14 | |||
15 | func main() { | ||
16 | var ( | ||
17 | quiet bool | ||
18 | bits int | ||
19 | ) | ||
20 | flag.IntVar(&bits, "b", 25, "Number of leading zero bits") | ||
21 | flag.BoolVar(&quiet, "q", false, "Be quiet") | ||
22 | flag.Parse() | ||
23 | |||
24 | // pre-set difficulty | ||
25 | fmt.Printf("Leading zeros required: %d\n", bits) | ||
26 | difficulty := math.TWO.Pow(512 - bits).Sub(math.ONE) | ||
27 | fmt.Printf("==> Difficulty: %v\n", difficulty) | ||
28 | |||
29 | // generate a random key pair | ||
30 | pkey, _ := ed25519.NewKeypair() | ||
31 | |||
32 | // initialize RevData structure | ||
33 | rd := revocation.NewRevData(0, pkey) | ||
34 | |||
35 | var count uint64 = 0 | ||
36 | for { | ||
37 | result, err := rd.Compute() | ||
38 | if err != nil { | ||
39 | log.Fatal(err) | ||
40 | } | ||
41 | //fmt.Printf("Nonce=%d, Result=(%d) %v\n", rd.GetNonce(), result.BitLen(), result) | ||
42 | if result.Cmp(difficulty) < 0 { | ||
43 | break | ||
44 | } | ||
45 | count++ | ||
46 | rd.Next() | ||
47 | } | ||
48 | fmt.Printf("PoW found after %d iterations:\n", count) | ||
49 | fmt.Printf("--> Nonce=%d\n", rd.GetNonce()) | ||
50 | fmt.Printf(" REV = %s\n", hex.EncodeToString(rd.GetBlob())) | ||
51 | } | ||
diff --git a/src/cmd/revoke-zonekey/main.go b/src/cmd/revoke-zonekey/main.go new file mode 100644 index 0000000..2bbd90f --- /dev/null +++ b/src/cmd/revoke-zonekey/main.go | |||
@@ -0,0 +1,192 @@ | |||
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/hex" | ||
24 | "flag" | ||
25 | "log" | ||
26 | "os" | ||
27 | "os/signal" | ||
28 | "sync" | ||
29 | "syscall" | ||
30 | |||
31 | "gnunet/service/revocation" | ||
32 | "gnunet/util" | ||
33 | |||
34 | "github.com/bfix/gospel/crypto/ed25519" | ||
35 | "github.com/bfix/gospel/data" | ||
36 | ) | ||
37 | |||
38 | func main() { | ||
39 | log.Println("*** Compute revocation data for a zone key") | ||
40 | log.Println("*** Copyright (c) 2020, Bernd Fix >Y<") | ||
41 | log.Println("*** This is free software distributed under the Affero GPL v3.") | ||
42 | |||
43 | // handle command line arguments | ||
44 | var ( | ||
45 | verbose bool // be verbose with messages | ||
46 | bits int // number of leading zero-bit requested | ||
47 | zonekey string // zonekey to be revoked | ||
48 | filename string // name of file for persistance | ||
49 | ) | ||
50 | flag.IntVar(&bits, "b", 25, "Number of leading zero bits") | ||
51 | flag.BoolVar(&verbose, "v", false, "verbose output") | ||
52 | flag.StringVar(&zonekey, "z", "", "Zone key to be revoked") | ||
53 | flag.StringVar(&filename, "f", "", "Name of file to store revocation") | ||
54 | flag.Parse() | ||
55 | |||
56 | // define layout of persistant data | ||
57 | var revData struct { | ||
58 | Rd *revocation.RevData // Revocation data | ||
59 | T util.RelativeTime // time spend in calculations | ||
60 | Last uint64 // last value used for PoW test | ||
61 | Numbits uint8 // number of leading zero-bits | ||
62 | } | ||
63 | dataBuf := make([]byte, 377) | ||
64 | |||
65 | // read revocation object from file | ||
66 | file, err := os.Open(filename) | ||
67 | cont := true | ||
68 | if err != nil { | ||
69 | if len(zonekey) != 52 { | ||
70 | log.Fatal("Missing or invalid zonekey and no file specified -- aborting") | ||
71 | } | ||
72 | keyData, err := util.DecodeStringToBinary(zonekey, 32) | ||
73 | if err != nil { | ||
74 | log.Fatal("Invalid zonekey: " + err.Error()) | ||
75 | } | ||
76 | pkey := ed25519.NewPublicKeyFromBytes(keyData) | ||
77 | revData.Rd = revocation.NewRevData(util.AbsoluteTimeNow(), pkey) | ||
78 | revData.Numbits = uint8(bits) | ||
79 | revData.T = util.NewRelativeTime(0) | ||
80 | cont = false | ||
81 | } else { | ||
82 | n, err := file.Read(dataBuf) | ||
83 | if err != nil { | ||
84 | log.Fatal("Error reading file: " + err.Error()) | ||
85 | } | ||
86 | if n != len(dataBuf) { | ||
87 | log.Fatal("File corrupted -- aborting") | ||
88 | } | ||
89 | if err = data.Unmarshal(&revData, dataBuf); err != nil { | ||
90 | log.Fatal("File corrupted: " + err.Error()) | ||
91 | } | ||
92 | bits = int(revData.Numbits) | ||
93 | if err = file.Close(); err != nil { | ||
94 | log.Fatal("Error closing file: " + err.Error()) | ||
95 | } | ||
96 | } | ||
97 | |||
98 | if cont { | ||
99 | log.Printf("Revocation calculation started at %s\n", revData.Rd.Timestamp.String()) | ||
100 | log.Printf("Time spent on calculation: %s\n", revData.T.String()) | ||
101 | log.Printf("Last tested PoW value: %d\n", revData.Last) | ||
102 | log.Println("Continuing...") | ||
103 | } else { | ||
104 | log.Println("Starting new revocation calculation...") | ||
105 | } | ||
106 | log.Println("Press ^C to abort...") | ||
107 | |||
108 | // pre-set difficulty | ||
109 | log.Printf("Difficulty: %d\n", bits) | ||
110 | if bits < 25 { | ||
111 | log.Println("WARNING: difficulty is less than 25!") | ||
112 | } | ||
113 | |||
114 | // Start or continue calculation | ||
115 | startTime := util.AbsoluteTimeNow() | ||
116 | ctx, cancelFcn := context.WithCancel(context.Background()) | ||
117 | wg := new(sync.WaitGroup) | ||
118 | wg.Add(1) | ||
119 | go func() { | ||
120 | defer wg.Done() | ||
121 | if result, last := revData.Rd.Compute(ctx, bits, revData.Last); result != 32 { | ||
122 | log.Printf("Incomplete revocation: Only %d of 32 PoWs available!\n", result) | ||
123 | revData.Last = last | ||
124 | revData.T = util.AbsoluteTimeNow().Diff(startTime) | ||
125 | log.Println("Writing revocation data to file...") | ||
126 | file, err := os.Create(filename) | ||
127 | if err != nil { | ||
128 | log.Fatal("Can't write to output file: " + err.Error()) | ||
129 | } | ||
130 | buf, err := data.Marshal(&revData) | ||
131 | if err != nil { | ||
132 | log.Fatal("Internal error: " + err.Error()) | ||
133 | } | ||
134 | if len(buf) != len(dataBuf) { | ||
135 | log.Fatal("Internal error: Buffer mismatch") | ||
136 | } | ||
137 | n, err := file.Write(buf) | ||
138 | if err != nil { | ||
139 | log.Fatal("Can't write to output file: " + err.Error()) | ||
140 | } | ||
141 | if n != len(dataBuf) { | ||
142 | log.Fatal("Can't write data to output file!") | ||
143 | } | ||
144 | if err = file.Close(); err != nil { | ||
145 | log.Fatal("Error closing file: " + err.Error()) | ||
146 | } | ||
147 | } else { | ||
148 | log.Println("Revocation data object:") | ||
149 | log.Println(" 0x" + hex.EncodeToString(revData.Rd.Blob())) | ||
150 | log.Println("Status:") | ||
151 | rc := revData.Rd.Verify() | ||
152 | switch { | ||
153 | case rc == -1: | ||
154 | log.Println(" Missing/invalid signature") | ||
155 | case rc == -2: | ||
156 | log.Println(" Expired revocation") | ||
157 | case rc == -3: | ||
158 | log.Println(" Wrong PoW sequence order") | ||
159 | case rc < 25: | ||
160 | log.Println(" Difficulty to small") | ||
161 | default: | ||
162 | log.Printf(" Difficulty: %d\n", rc) | ||
163 | } | ||
164 | } | ||
165 | }() | ||
166 | |||
167 | go func() { | ||
168 | // handle OS signals | ||
169 | sigCh := make(chan os.Signal, 5) | ||
170 | signal.Notify(sigCh) | ||
171 | loop: | ||
172 | for { | ||
173 | select { | ||
174 | // handle OS signals | ||
175 | case sig := <-sigCh: | ||
176 | switch sig { | ||
177 | case syscall.SIGKILL, syscall.SIGINT, syscall.SIGTERM: | ||
178 | log.Printf("Terminating (on signal '%s')\n", sig) | ||
179 | cancelFcn() | ||
180 | break loop | ||
181 | case syscall.SIGHUP: | ||
182 | log.Println("SIGHUP") | ||
183 | case syscall.SIGURG: | ||
184 | // TODO: https://github.com/golang/go/issues/37942 | ||
185 | default: | ||
186 | log.Println("Unhandled signal: " + sig.String()) | ||
187 | } | ||
188 | } | ||
189 | } | ||
190 | }() | ||
191 | wg.Wait() | ||
192 | } | ||
diff --git a/src/gnunet/config/config.go b/src/gnunet/config/config.go index 01dadea..690b186 100644 --- a/src/gnunet/config/config.go +++ b/src/gnunet/config/config.go | |||
@@ -55,16 +55,26 @@ type NamecacheConfig struct { | |||
55 | } | 55 | } |
56 | 56 | ||
57 | /////////////////////////////////////////////////////////////////////// | 57 | /////////////////////////////////////////////////////////////////////// |
58 | // Revocation configuration | ||
59 | |||
60 | // RevocationConfig | ||
61 | type RevocationConfig struct { | ||
62 | Endpoint string `json:"endpoint"` // end-point of Revocation service | ||
63 | Storage string `json:"storage"` // persistance mechanism for revocation data | ||
64 | } | ||
65 | |||
66 | /////////////////////////////////////////////////////////////////////// | ||
58 | 67 | ||
59 | // Environment settings | 68 | // Environment settings |
60 | type Environ map[string]string | 69 | type Environ map[string]string |
61 | 70 | ||
62 | // Config is the aggregated configuration for GNUnet. | 71 | // Config is the aggregated configuration for GNUnet. |
63 | type Config struct { | 72 | type Config struct { |
64 | Env Environ `json:"environ"` | 73 | Env Environ `json:"environ"` |
65 | DHT *DHTConfig `json:"dht"` | 74 | DHT *DHTConfig `json:"dht"` |
66 | GNS *GNSConfig `json:"gns"` | 75 | GNS *GNSConfig `json:"gns"` |
67 | Namecache *NamecacheConfig `json:"namecache"` | 76 | Namecache *NamecacheConfig `json:"namecache"` |
77 | Revocation *RevocationConfig `json:"revocation"` | ||
68 | } | 78 | } |
69 | 79 | ||
70 | var ( | 80 | var ( |
diff --git a/src/gnunet/message/factory.go b/src/gnunet/message/factory.go index 33e806b..68a24d5 100644 --- a/src/gnunet/message/factory.go +++ b/src/gnunet/message/factory.go | |||
@@ -92,7 +92,7 @@ func NewEmptyMessage(msgType uint16) (Message, error) { | |||
92 | case REVOCATION_QUERY_RESPONSE: | 92 | case REVOCATION_QUERY_RESPONSE: |
93 | return NewRevocationQueryResponseMsg(true), nil | 93 | return NewRevocationQueryResponseMsg(true), nil |
94 | case REVOCATION_REVOKE: | 94 | case REVOCATION_REVOKE: |
95 | return NewRevocationRevokeMsg(0, nil, nil), nil | 95 | return NewRevocationRevokeMsg(nil, nil), nil |
96 | case REVOCATION_REVOKE_RESPONSE: | 96 | case REVOCATION_REVOKE_RESPONSE: |
97 | return NewRevocationRevokeResponseMsg(false), nil | 97 | return NewRevocationRevokeResponseMsg(false), nil |
98 | } | 98 | } |
diff --git a/src/gnunet/message/msg_revocation.go b/src/gnunet/message/msg_revocation.go index fea727c..8c7db06 100644 --- a/src/gnunet/message/msg_revocation.go +++ b/src/gnunet/message/msg_revocation.go | |||
@@ -21,8 +21,6 @@ package message | |||
21 | import ( | 21 | import ( |
22 | "fmt" | 22 | "fmt" |
23 | 23 | ||
24 | "gnunet/crypto" | ||
25 | "gnunet/enums" | ||
26 | "gnunet/util" | 24 | "gnunet/util" |
27 | 25 | ||
28 | "github.com/bfix/gospel/crypto/ed25519" | 26 | "github.com/bfix/gospel/crypto/ed25519" |
@@ -104,28 +102,23 @@ func (msg *RevocationQueryResponseMsg) Header() *MessageHeader { | |||
104 | 102 | ||
105 | // RevocationRevokeMsg | 103 | // RevocationRevokeMsg |
106 | type RevocationRevokeMsg struct { | 104 | type RevocationRevokeMsg struct { |
107 | MsgSize uint16 `order:"big"` // total size of message | 105 | MsgSize uint16 `order:"big"` // total size of message |
108 | MsgType uint16 `order:"big"` // REVOCATION_QUERY (636) | 106 | MsgType uint16 `order:"big"` // REVOCATION_REVOKE (638) |
109 | Reserved uint32 `order:"big"` // Reserved for future use | 107 | Timestamp util.AbsoluteTime // Timestamp of revocation creation |
110 | PoW uint64 `order:"big"` // Proof-of-work: nonce that satisfy condition | 108 | PoWs []uint64 `size:"32" order:"big"` // (Sorted) list of PoW values |
111 | Signature []byte `size:"64"` // Signature of the revocation. | 109 | Signature []byte `size:"64"` // Signature (Proof-of-ownership). |
112 | Purpose *crypto.SignaturePurpose // Size and purpose of signature (8 bytes) | 110 | ZoneKey []byte `size:"32"` // public zone key to be revoked |
113 | ZoneKey []byte `size:"32"` // Zone key to be revoked | ||
114 | } | 111 | } |
115 | 112 | ||
116 | // NewRevocationRevokeMsg creates a new message for a given zone. | 113 | // NewRevocationRevokeMsg creates a new message for a given zone. |
117 | func NewRevocationRevokeMsg(pow uint64, zoneKey *ed25519.PublicKey, sig *ed25519.EcSignature) *RevocationRevokeMsg { | 114 | func NewRevocationRevokeMsg(zoneKey *ed25519.PublicKey, sig *ed25519.EcSignature) *RevocationRevokeMsg { |
118 | msg := &RevocationRevokeMsg{ | 115 | msg := &RevocationRevokeMsg{ |
119 | MsgSize: 120, | 116 | MsgSize: 364, |
120 | MsgType: REVOCATION_REVOKE, | 117 | MsgType: REVOCATION_REVOKE, |
121 | Reserved: 0, | 118 | Timestamp: util.AbsoluteTimeNow(), |
122 | PoW: pow, | 119 | PoWs: make([]uint64, 32), |
123 | Signature: make([]byte, 64), | 120 | Signature: make([]byte, 64), |
124 | Purpose: &crypto.SignaturePurpose{ | 121 | ZoneKey: make([]byte, 32), |
125 | Size: 40, | ||
126 | Purpose: enums.SIG_REVOCATION, | ||
127 | }, | ||
128 | ZoneKey: make([]byte, 32), | ||
129 | } | 122 | } |
130 | if zoneKey != nil { | 123 | if zoneKey != nil { |
131 | copy(msg.ZoneKey, zoneKey.Bytes()) | 124 | copy(msg.ZoneKey, zoneKey.Bytes()) |
@@ -138,7 +131,7 @@ func NewRevocationRevokeMsg(pow uint64, zoneKey *ed25519.PublicKey, sig *ed25519 | |||
138 | 131 | ||
139 | // String returns a human-readable representation of the message. | 132 | // String returns a human-readable representation of the message. |
140 | func (m *RevocationRevokeMsg) String() string { | 133 | func (m *RevocationRevokeMsg) String() string { |
141 | return fmt.Sprintf("RevocationRevokeMsg{pow=%d,zone=%s}", m.PoW, util.EncodeBinaryToString(m.ZoneKey)) | 134 | return fmt.Sprintf("RevocationRevokeMsg{zone=%s}", util.EncodeBinaryToString(m.ZoneKey)) |
142 | } | 135 | } |
143 | 136 | ||
144 | // Header returns the message header in a separate instance. | 137 | // Header returns the message header in a separate instance. |
@@ -153,8 +146,8 @@ func (msg *RevocationRevokeMsg) Header() *MessageHeader { | |||
153 | // RevocationRevokeResponseMsg | 146 | // RevocationRevokeResponseMsg |
154 | type RevocationRevokeResponseMsg struct { | 147 | type RevocationRevokeResponseMsg struct { |
155 | MsgSize uint16 `order:"big"` // total size of message | 148 | MsgSize uint16 `order:"big"` // total size of message |
156 | MsgType uint16 `order:"big"` // REVOCATION_QUERY_RESPONSE (637) | 149 | MsgType uint16 `order:"big"` // REVOCATION_REVOKE_RESPONSE (639) |
157 | Success uint32 `order:"big"` // Revoke successful? | 150 | Success uint32 `order:"big"` // Revoke successful? (0=no, 1=yes) |
158 | } | 151 | } |
159 | 152 | ||
160 | // NewRevocationRevokeResponseMsg creates a new response for a query. | 153 | // NewRevocationRevokeResponseMsg creates a new response for a query. |
diff --git a/src/gnunet/modules.go b/src/gnunet/modules.go index 063b914..24dc92b 100644 --- a/src/gnunet/modules.go +++ b/src/gnunet/modules.go | |||
@@ -32,13 +32,15 @@ import ( | |||
32 | "gnunet/service/dht" | 32 | "gnunet/service/dht" |
33 | "gnunet/service/gns" | 33 | "gnunet/service/gns" |
34 | "gnunet/service/namecache" | 34 | "gnunet/service/namecache" |
35 | "gnunet/service/revocation" | ||
35 | ) | 36 | ) |
36 | 37 | ||
37 | // List of all GNUnet service module instances | 38 | // List of all GNUnet service module instances |
38 | type Instances struct { | 39 | type Instances struct { |
39 | GNS *gns.GNSModule | 40 | GNS *gns.GNSModule |
40 | Namecache *namecache.NamecacheModule | 41 | Namecache *namecache.NamecacheModule |
41 | DHT *dht.DHTModule | 42 | DHT *dht.DHTModule |
43 | Revocation *revocation.RevocationModule | ||
42 | } | 44 | } |
43 | 45 | ||
44 | // Local reference to instance list | 46 | // Local reference to instance list |
@@ -55,10 +57,15 @@ func init() { | |||
55 | // DHT (no calls to other modules) | 57 | // DHT (no calls to other modules) |
56 | Modules.DHT = new(dht.DHTModule) | 58 | Modules.DHT = new(dht.DHTModule) |
57 | 59 | ||
60 | // Revocation (no calls to other modules) | ||
61 | Modules.Revocation = revocation.NewRevocationModule() | ||
62 | |||
58 | // GNS (calls Namecache, DHT and Identity) | 63 | // GNS (calls Namecache, DHT and Identity) |
59 | Modules.GNS = &gns.GNSModule{ | 64 | Modules.GNS = &gns.GNSModule{ |
60 | LookupLocal: Modules.Namecache.Get, | 65 | LookupLocal: Modules.Namecache.Get, |
61 | StoreLocal: Modules.Namecache.Put, | 66 | StoreLocal: Modules.Namecache.Put, |
62 | LookupRemote: Modules.DHT.Get, | 67 | LookupRemote: Modules.DHT.Get, |
68 | RevocationQuery: Modules.Revocation.Query, | ||
69 | RevocationRevoke: Modules.Revocation.Revoke, | ||
63 | } | 70 | } |
64 | } | 71 | } |
diff --git a/src/gnunet/service/gns/module.go b/src/gnunet/service/gns/module.go index e885d2d..5e787d5 100644 --- a/src/gnunet/service/gns/module.go +++ b/src/gnunet/service/gns/module.go | |||
@@ -27,6 +27,7 @@ import ( | |||
27 | "gnunet/enums" | 27 | "gnunet/enums" |
28 | "gnunet/message" | 28 | "gnunet/message" |
29 | "gnunet/service" | 29 | "gnunet/service" |
30 | "gnunet/service/revocation" | ||
30 | "gnunet/util" | 31 | "gnunet/util" |
31 | 32 | ||
32 | "github.com/bfix/gospel/crypto/ed25519" | 33 | "github.com/bfix/gospel/crypto/ed25519" |
@@ -111,9 +112,11 @@ func NewQuery(pkey *ed25519.PublicKey, label string) *Query { | |||
111 | // GNSModule handles the resolution of GNS names to RRs bundled in a block. | 112 | // GNSModule handles the resolution of GNS names to RRs bundled in a block. |
112 | type GNSModule struct { | 113 | type GNSModule struct { |
113 | // Use function references for calls to methods in other modules: | 114 | // Use function references for calls to methods in other modules: |
114 | LookupLocal func(ctx *service.SessionContext, query *Query) (*message.GNSBlock, error) | 115 | LookupLocal func(ctx *service.SessionContext, query *Query) (*message.GNSBlock, error) |
115 | StoreLocal func(ctx *service.SessionContext, block *message.GNSBlock) error | 116 | StoreLocal func(ctx *service.SessionContext, block *message.GNSBlock) error |
116 | LookupRemote func(ctx *service.SessionContext, query *Query) (*message.GNSBlock, error) | 117 | LookupRemote func(ctx *service.SessionContext, query *Query) (*message.GNSBlock, error) |
118 | RevocationQuery func(ctx *service.SessionContext, pkey *ed25519.PublicKey) (valid bool, err error) | ||
119 | RevocationRevoke func(ctx *service.SessionContext, rd *revocation.RevData) (success bool, err error) | ||
117 | } | 120 | } |
118 | 121 | ||
119 | // Resolve a GNS name with multiple labels. If pkey is not nil, the name | 122 | // Resolve a GNS name with multiple labels. If pkey is not nil, the name |
@@ -158,6 +161,12 @@ func (gns *GNSModule) ResolveAbsolute( | |||
158 | err = ErrUnknownTLD | 161 | err = ErrUnknownTLD |
159 | return | 162 | return |
160 | } | 163 | } |
164 | // check if zone key has been revoked | ||
165 | var valid bool | ||
166 | set = message.NewGNSRecordSet() | ||
167 | if valid, err = gns.RevocationQuery(ctx, pkey); err != nil || !valid { | ||
168 | return | ||
169 | } | ||
161 | // continue with resolution relative to a zone. | 170 | // continue with resolution relative to a zone. |
162 | return gns.ResolveRelative(ctx, labels[1:], pkey, kind, mode, depth) | 171 | return gns.ResolveRelative(ctx, labels[1:], pkey, kind, mode, depth) |
163 | } | 172 | } |
@@ -229,6 +238,12 @@ func (gns *GNSModule) ResolveRelative( | |||
229 | if len(labels) == 1 && !kind.HasType(enums.GNS_TYPE_PKEY) { | 238 | if len(labels) == 1 && !kind.HasType(enums.GNS_TYPE_PKEY) { |
230 | labels = append(labels, "@") | 239 | labels = append(labels, "@") |
231 | } | 240 | } |
241 | // check if zone key has been revoked | ||
242 | if valid, err := gns.RevocationQuery(ctx, pkey); err != nil || !valid { | ||
243 | // revoked key -> no results! | ||
244 | records = make([]*message.GNSResourceRecord, 0) | ||
245 | break | ||
246 | } | ||
232 | } else if hdlr := hdlrs.GetHandler(enums.GNS_TYPE_GNS2DNS); hdlr != nil { | 247 | } else if hdlr := hdlrs.GetHandler(enums.GNS_TYPE_GNS2DNS); hdlr != nil { |
233 | // (2) GNS2DNS records | 248 | // (2) GNS2DNS records |
234 | inst := hdlr.(*Gns2DnsHandler) | 249 | inst := hdlr.(*Gns2DnsHandler) |
diff --git a/src/gnunet/service/gns/service.go b/src/gnunet/service/gns/service.go index 464e622..f6310de 100644 --- a/src/gnunet/service/gns/service.go +++ b/src/gnunet/service/gns/service.go | |||
@@ -28,6 +28,7 @@ import ( | |||
28 | "gnunet/enums" | 28 | "gnunet/enums" |
29 | "gnunet/message" | 29 | "gnunet/message" |
30 | "gnunet/service" | 30 | "gnunet/service" |
31 | "gnunet/service/revocation" | ||
31 | "gnunet/transport" | 32 | "gnunet/transport" |
32 | "gnunet/util" | 33 | "gnunet/util" |
33 | 34 | ||
@@ -59,6 +60,8 @@ func NewGNSService() service.Service { | |||
59 | inst.LookupLocal = inst.LookupNamecache | 60 | inst.LookupLocal = inst.LookupNamecache |
60 | inst.StoreLocal = inst.StoreNamecache | 61 | inst.StoreLocal = inst.StoreNamecache |
61 | inst.LookupRemote = inst.LookupDHT | 62 | inst.LookupRemote = inst.LookupDHT |
63 | inst.RevocationQuery = inst.QueryKeyRevocation | ||
64 | inst.RevocationRevoke = inst.RevokeKey | ||
62 | return inst | 65 | return inst |
63 | } | 66 | } |
64 | 67 | ||
@@ -167,6 +170,60 @@ loop: | |||
167 | ctx.Cancel() | 170 | ctx.Cancel() |
168 | } | 171 | } |
169 | 172 | ||
173 | //====================================================================== | ||
174 | |||
175 | // | ||
176 | func (s *GNSService) QueryKeyRevocation(ctx *service.SessionContext, pkey *ed25519.PublicKey) (valid bool, err error) { | ||
177 | logger.Printf(logger.DBG, "[gns] QueryKeyRev(%s)...\n", util.EncodeBinaryToString(pkey.Bytes())) | ||
178 | |||
179 | // assemble request | ||
180 | req := message.NewRevocationQueryMsg(pkey) | ||
181 | |||
182 | // get response from Revocation service | ||
183 | var resp message.Message | ||
184 | if resp, err = service.ServiceRequestResponse(ctx, "gns", "Revocation", config.Cfg.Revocation.Endpoint, req); err != nil { | ||
185 | return | ||
186 | } | ||
187 | |||
188 | // handle message depending on its type | ||
189 | logger.Println(logger.DBG, "[gns] Handling response from Revocation service") | ||
190 | valid = false | ||
191 | switch m := resp.(type) { | ||
192 | case *message.RevocationQueryResponseMsg: | ||
193 | valid = (m.Valid == 1) | ||
194 | } | ||
195 | return | ||
196 | } | ||
197 | |||
198 | // | ||
199 | func (s *GNSService) RevokeKey(ctx *service.SessionContext, rd *revocation.RevData) (success bool, err error) { | ||
200 | logger.Printf(logger.DBG, "[gns] RevokeKey(%s)...\n", util.EncodeBinaryToString(rd.ZoneKey)) | ||
201 | |||
202 | // assemble request | ||
203 | req := message.NewRevocationRevokeMsg(nil, nil) | ||
204 | req.Timestamp = rd.Timestamp | ||
205 | copy(req.PoWs, rd.PoWs) | ||
206 | copy(req.Signature, rd.Signature) | ||
207 | copy(req.ZoneKey, rd.ZoneKey) | ||
208 | |||
209 | // get response from Revocation service | ||
210 | var resp message.Message | ||
211 | if resp, err = service.ServiceRequestResponse(ctx, "gns", "Revocation", config.Cfg.Revocation.Endpoint, req); err != nil { | ||
212 | return | ||
213 | } | ||
214 | |||
215 | // handle message depending on its type | ||
216 | logger.Println(logger.DBG, "[gns] Handling response from Revocation service") | ||
217 | success = false | ||
218 | switch m := resp.(type) { | ||
219 | case *message.RevocationRevokeResponseMsg: | ||
220 | success = (m.Success == 1) | ||
221 | } | ||
222 | return | ||
223 | } | ||
224 | |||
225 | //====================================================================== | ||
226 | |||
170 | // LookupNamecache | 227 | // LookupNamecache |
171 | func (s *GNSService) LookupNamecache(ctx *service.SessionContext, query *Query) (block *message.GNSBlock, err error) { | 228 | func (s *GNSService) LookupNamecache(ctx *service.SessionContext, query *Query) (block *message.GNSBlock, err error) { |
172 | logger.Printf(logger.DBG, "[gns] LookupNamecache(%s)...\n", hex.EncodeToString(query.Key.Bits)) | 229 | logger.Printf(logger.DBG, "[gns] LookupNamecache(%s)...\n", hex.EncodeToString(query.Key.Bits)) |
@@ -266,6 +323,8 @@ func (s *GNSService) StoreNamecache(ctx *service.SessionContext, block *message. | |||
266 | return | 323 | return |
267 | } | 324 | } |
268 | 325 | ||
326 | //====================================================================== | ||
327 | |||
269 | // LookupDHT | 328 | // LookupDHT |
270 | func (s *GNSService) LookupDHT(ctx *service.SessionContext, query *Query) (block *message.GNSBlock, err error) { | 329 | func (s *GNSService) LookupDHT(ctx *service.SessionContext, query *Query) (block *message.GNSBlock, err error) { |
271 | logger.Printf(logger.DBG, "[gns] LookupDHT(%s)...\n", hex.EncodeToString(query.Key.Bits)) | 330 | logger.Printf(logger.DBG, "[gns] LookupDHT(%s)...\n", hex.EncodeToString(query.Key.Bits)) |
diff --git a/src/gnunet/service/revocation/module.go b/src/gnunet/service/revocation/module.go new file mode 100644 index 0000000..b5c8a16 --- /dev/null +++ b/src/gnunet/service/revocation/module.go | |||
@@ -0,0 +1,124 @@ | |||
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 revocation | ||
20 | |||
21 | import ( | ||
22 | "gnunet/config" | ||
23 | "gnunet/service" | ||
24 | "gnunet/util" | ||
25 | |||
26 | "github.com/bfix/gospel/crypto/ed25519" | ||
27 | "github.com/bfix/gospel/data" | ||
28 | "github.com/bfix/gospel/logger" | ||
29 | ) | ||
30 | |||
31 | //====================================================================== | ||
32 | // "GNUnet Revocation" implementation | ||
33 | //====================================================================== | ||
34 | |||
35 | // RevocationModule handles the revocation-related calls to other modules. | ||
36 | type RevocationModule struct { | ||
37 | bloomf *data.BloomFilter // bloomfilter for fast revocation check | ||
38 | kvs util.KeyValueStore // storage for known revocations | ||
39 | } | ||
40 | |||
41 | // Init a revocation module | ||
42 | func (m *RevocationModule) Init() error { | ||
43 | // Initialize access to revocation data storage | ||
44 | var err error | ||
45 | if m.kvs, err = util.OpenKVStore(config.Cfg.Revocation.Storage); err != nil { | ||
46 | return err | ||
47 | } | ||
48 | // traverse the storage and build bloomfilter for all keys | ||
49 | m.bloomf = data.NewBloomFilter(1000000, 1e-8) | ||
50 | keys, err := m.kvs.List() | ||
51 | if err != nil { | ||
52 | return err | ||
53 | } | ||
54 | for _, key := range keys { | ||
55 | buf, err := util.DecodeStringToBinary(key, 32) | ||
56 | if err != nil { | ||
57 | return err | ||
58 | } | ||
59 | m.bloomf.Add(buf) | ||
60 | } | ||
61 | return nil | ||
62 | } | ||
63 | |||
64 | // NewRevocationModule returns an initialized revocation module | ||
65 | func NewRevocationModule() *RevocationModule { | ||
66 | m := new(RevocationModule) | ||
67 | if err := m.Init(); err != nil { | ||
68 | logger.Printf(logger.ERROR, "[revocation] Failed to initialize module: %s\n", err.Error()) | ||
69 | return nil | ||
70 | } | ||
71 | return m | ||
72 | } | ||
73 | |||
74 | // Query return true if the pkey is valid (not revoked) and false | ||
75 | // if the pkey has been revoked. | ||
76 | func (s *RevocationModule) Query(ctx *service.SessionContext, pkey *ed25519.PublicKey) (valid bool, err error) { | ||
77 | // fast check first: is the key in the bloomfilter? | ||
78 | data := pkey.Bytes() | ||
79 | if !s.bloomf.Contains(data) { | ||
80 | // no: it is valid (not revoked) | ||
81 | return true, nil | ||
82 | } | ||
83 | // check in store to detect false-positives | ||
84 | key := util.EncodeBinaryToString(data) | ||
85 | if _, err = s.kvs.Get(key); err != nil { | ||
86 | logger.Printf(logger.ERROR, "[revocation] Failed to locate key '%s' in store: %s\n", key, err.Error()) | ||
87 | // assume not revoked... | ||
88 | return true, err | ||
89 | } | ||
90 | // key seems to be revoked | ||
91 | return false, nil | ||
92 | } | ||
93 | |||
94 | // Revoke | ||
95 | func (s *RevocationModule) Revoke(ctx *service.SessionContext, rd *RevData) (success bool, err error) { | ||
96 | // verify the revocation data | ||
97 | rc := rd.Verify() | ||
98 | switch { | ||
99 | case rc == -1: | ||
100 | logger.Println(logger.WARN, "[revocation] Revoke: Missing/invalid signature") | ||
101 | return false, nil | ||
102 | case rc == -2: | ||
103 | logger.Println(logger.WARN, "[revocation] Revoke: Expired revocation") | ||
104 | return false, nil | ||
105 | case rc == -3: | ||
106 | logger.Println(logger.WARN, "[revocation] Revoke: Wrong PoW sequence order") | ||
107 | return false, nil | ||
108 | case rc < 25: | ||
109 | logger.Println(logger.WARN, "[revocation] Revoke: Difficulty to small") | ||
110 | return false, nil | ||
111 | } | ||
112 | // store the revocation data | ||
113 | // (1) add it to the bloomfilter | ||
114 | s.bloomf.Add(rd.ZoneKey) | ||
115 | // (2) add it to the store | ||
116 | var buf []byte | ||
117 | key := util.EncodeBinaryToString(rd.ZoneKey) | ||
118 | if buf, err = data.Marshal(rd); err != nil { | ||
119 | return false, err | ||
120 | } | ||
121 | value := util.EncodeBinaryToString(buf) | ||
122 | err = s.kvs.Put(key, value) | ||
123 | return true, err | ||
124 | } | ||
diff --git a/src/gnunet/service/revocation/pow.go b/src/gnunet/service/revocation/pow.go index 07c6241..f4b6b9d 100644 --- a/src/gnunet/service/revocation/pow.go +++ b/src/gnunet/service/revocation/pow.go | |||
@@ -20,71 +20,74 @@ package revocation | |||
20 | 20 | ||
21 | import ( | 21 | import ( |
22 | "bytes" | 22 | "bytes" |
23 | "crypto/cipher" | 23 | "context" |
24 | "crypto/sha256" | ||
25 | "crypto/sha512" | ||
26 | "encoding/binary" | 24 | "encoding/binary" |
27 | "sync" | 25 | "time" |
28 | 26 | ||
27 | "gnunet/crypto" | ||
28 | "gnunet/enums" | ||
29 | "gnunet/message" | ||
29 | "gnunet/util" | 30 | "gnunet/util" |
30 | 31 | ||
31 | "github.com/bfix/gospel/crypto/ed25519" | 32 | "github.com/bfix/gospel/crypto/ed25519" |
32 | "github.com/bfix/gospel/data" | 33 | "github.com/bfix/gospel/data" |
33 | "github.com/bfix/gospel/math" | 34 | "github.com/bfix/gospel/math" |
34 | "golang.org/x/crypto/hkdf" | 35 | "golang.org/x/crypto/argon2" |
35 | "golang.org/x/crypto/scrypt" | ||
36 | "golang.org/x/crypto/twofish" | ||
37 | ) | 36 | ) |
38 | 37 | ||
39 | //---------------------------------------------------------------------- | 38 | //---------------------------------------------------------------------- |
40 | // Revocation data | 39 | // Proof-of-Work data |
41 | //---------------------------------------------------------------------- | 40 | //---------------------------------------------------------------------- |
42 | 41 | ||
43 | // RevData is the revocation data structure (wire format) | 42 | // PoWData is the proof-of-work data |
44 | type RevData struct { | 43 | type PoWData struct { |
45 | Nonce uint64 `order:"big"` // start with this nonce value | 44 | PoW uint64 `order:"big"` // start with this PoW value |
46 | ZoneKey []byte `size:"32"` // public zone key to be revoked | 45 | Timestamp util.AbsoluteTime // Timestamp of creation |
46 | ZoneKey []byte `size:"32"` // public zone key to be revoked | ||
47 | 47 | ||
48 | // transient attributes (not serialized) | 48 | // transient attributes (not serialized) |
49 | blob []byte // binary representation of serialized data | 49 | blob []byte // binary representation of serialized data |
50 | } | 50 | } |
51 | 51 | ||
52 | // NewRevData creates a RevData instance for the given arguments. | 52 | // NewPoWData creates a PoWData instance for the given arguments. |
53 | func NewRevData(nonce uint64, zoneKey *ed25519.PublicKey) *RevData { | 53 | func NewPoWData(pow uint64, ts util.AbsoluteTime, zoneKey []byte) *PoWData { |
54 | rd := &RevData{ | 54 | rd := &PoWData{ |
55 | Nonce: nonce, | 55 | PoW: 0, |
56 | ZoneKey: make([]byte, 32), | 56 | Timestamp: ts, |
57 | ZoneKey: zoneKey, | ||
57 | } | 58 | } |
58 | copy(rd.ZoneKey, zoneKey.Bytes()) | 59 | if rd.SetPoW(pow) != nil { |
59 | blob, err := data.Marshal(rd) | ||
60 | if err != nil { | ||
61 | return nil | 60 | return nil |
62 | } | 61 | } |
63 | rd.blob = blob | ||
64 | return rd | 62 | return rd |
65 | } | 63 | } |
66 | 64 | ||
67 | // GetNonce returns the last checked nonce value | 65 | func (p *PoWData) SetPoW(pow uint64) error { |
68 | func (r *RevData) GetNonce() uint64 { | 66 | p.PoW = pow |
69 | if r.blob != nil { | 67 | blob, err := data.Marshal(p) |
70 | var val uint64 | 68 | if err != nil { |
71 | binary.Read(bytes.NewReader(r.blob[:8]), binary.BigEndian, &val) | 69 | return err |
72 | r.Nonce = val | ||
73 | } | 70 | } |
74 | return r.Nonce | 71 | p.blob = blob |
72 | return nil | ||
75 | } | 73 | } |
76 | 74 | ||
77 | // GetBlob returns the binary representation of RevData | 75 | // GetPoW returns the last checked PoW value |
78 | func (r *RevData) GetBlob() []byte { | 76 | func (p *PoWData) GetPoW() uint64 { |
79 | return r.blob | 77 | if p.blob != nil { |
78 | var val uint64 | ||
79 | binary.Read(bytes.NewReader(p.blob[:8]), binary.BigEndian, &val) | ||
80 | p.PoW = val | ||
81 | } | ||
82 | return p.PoW | ||
80 | } | 83 | } |
81 | 84 | ||
82 | // Next selects the next nonce to be tested. | 85 | // Next selects the next PoW to be tested. |
83 | func (r *RevData) Next() { | 86 | func (p *PoWData) Next() { |
84 | var incr func(pos int) | 87 | var incr func(pos int) |
85 | incr = func(pos int) { | 88 | incr = func(pos int) { |
86 | r.blob[pos]++ | 89 | p.blob[pos]++ |
87 | if r.blob[pos] != 0 || pos == 0 { | 90 | if p.blob[pos] != 0 || pos == 0 { |
88 | return | 91 | return |
89 | } | 92 | } |
90 | incr(pos - 1) | 93 | incr(pos - 1) |
@@ -92,140 +95,191 @@ func (r *RevData) Next() { | |||
92 | incr(7) | 95 | incr(7) |
93 | } | 96 | } |
94 | 97 | ||
95 | // Compute calculates the current result for a RevData content. | 98 | // Compute calculates the current result for a PoWData content. |
96 | // The result is returned as a big integer value. | 99 | // The result is returned as a big integer value. |
97 | func (r *RevData) Compute() (*math.Int, error) { | 100 | func (p *PoWData) Compute() *math.Int { |
98 | 101 | key := argon2.Key(p.blob, []byte("gnunet-revocation-proof-of-work"), 3, 1024, 1, 64) | |
99 | // generate key material | 102 | return math.NewIntFromBytes(key) |
100 | k, err := scrypt.Key(r.blob, []byte("gnunet-revocation-proof-of-work"), 2, 8, 2, 32) | ||
101 | if err != nil { | ||
102 | return nil, err | ||
103 | } | ||
104 | |||
105 | // generate initialization vector | ||
106 | iv := make([]byte, 16) | ||
107 | prk := hkdf.Extract(sha512.New, k, []byte("gnunet-proof-of-work-iv")) | ||
108 | rdr := hkdf.Expand(sha256.New, prk, []byte("gnunet-revocation-proof-of-work")) | ||
109 | rdr.Read(iv) | ||
110 | |||
111 | // Encrypt with Twofish CFB stream cipher | ||
112 | out := make([]byte, len(r.blob)) | ||
113 | tf, err := twofish.NewCipher(k) | ||
114 | if err != nil { | ||
115 | return nil, err | ||
116 | } | ||
117 | cipher.NewCFBEncrypter(tf, iv).XORKeyStream(out, r.blob) | ||
118 | |||
119 | // compute result | ||
120 | result, err := scrypt.Key(out, []byte("gnunet-revocation-proof-of-work"), 2, 8, 2, 64) | ||
121 | return math.NewIntFromBytes(result), nil | ||
122 | } | 103 | } |
123 | 104 | ||
124 | //---------------------------------------------------------------------- | 105 | //---------------------------------------------------------------------- |
125 | // Command types for Worker | 106 | // Revocation data |
126 | //---------------------------------------------------------------------- | 107 | //---------------------------------------------------------------------- |
127 | 108 | ||
128 | // StartCmd starts the PoW calculation beginng at given nonce. If a | 109 | // RevData is the revocation data (wire format) |
129 | // revocation is initiated the first time, the nonce is 0. If the computation | 110 | type RevData struct { |
130 | // was interrupted (because the revocation service was shutting down), the | 111 | Timestamp util.AbsoluteTime // Timestamp of creation |
131 | // computation can resume for the next unchecked nonce value. | 112 | PoWs []uint64 `size:"32" order:"big"` // (Sorted) list of PoW values |
132 | // see: StartResponse | 113 | Signature []byte `size:"64"` // Signature (Proof-of-ownership). |
133 | type StartCmd struct { | 114 | ZoneKey []byte `size:"32"` // public zone key to be revoked |
134 | ID int // Command identifier (to relate responses) | ||
135 | task *RevData // RevData instance to be started | ||
136 | } | ||
137 | |||
138 | // PauseCmd temporarily pauses the calculation of a PoW. | ||
139 | // see: PauseResponse | ||
140 | type PauseCmd struct { | ||
141 | ID int // Command identifier (to relate responses) | ||
142 | taskID int // identifier for PoW task | ||
143 | } | ||
144 | |||
145 | // ResumeCmd resumes a paused PoW calculation. | ||
146 | // see: ResumeResponse | ||
147 | type ResumeCmd struct { | ||
148 | ID int // Command identifier (to relate responses) | ||
149 | taskID int // identifier for PoW task | ||
150 | } | ||
151 | |||
152 | // BreakCmd interrupts a running PoW calculation | ||
153 | type BreakCmd struct { | ||
154 | ID int // Command identifier (to relate responses) | ||
155 | taskID int // identifier for PoW task | ||
156 | } | 115 | } |
157 | 116 | ||
158 | //---------------------------------------------------------------------- | 117 | // SignedRevData is the block of data signed for a RevData instance. |
159 | // Response types for Worker | 118 | type SignedRevData struct { |
160 | //---------------------------------------------------------------------- | 119 | Purpose *crypto.SignaturePurpose |
161 | 120 | ZoneKey []byte `size:"32"` // public zone key to be revoked | |
162 | // StartResponse is a reply to the StartCmd message | 121 | Timestamp util.AbsoluteTime // Timestamp of creation |
163 | type StartResponse struct { | ||
164 | ID int // Command identifier (to relate responses) | ||
165 | taskID int // identifier for PoW task | ||
166 | err error // error code (nil on success) | ||
167 | } | 122 | } |
168 | 123 | ||
169 | // PauseResponse is a reply to the PauseCmd message | 124 | // NewRevData initializes a new RevData instance |
170 | type PauseResponse struct { | 125 | func NewRevData(ts util.AbsoluteTime, pkey *ed25519.PublicKey) *RevData { |
171 | ID int // Command identifier (to relate responses) | 126 | rd := &RevData{ |
172 | err error // error code (nil on success) | 127 | Timestamp: ts, |
128 | PoWs: make([]uint64, 32), | ||
129 | Signature: make([]byte, 64), | ||
130 | ZoneKey: make([]byte, 32), | ||
131 | } | ||
132 | copy(rd.ZoneKey, pkey.Bytes()) | ||
133 | return rd | ||
173 | } | 134 | } |
174 | 135 | ||
175 | // ResumeResponse is a reply to the ResumeCmd message | 136 | // NewRevDataFromMsg initializes a new RevData instance from a GNUnet message |
176 | type ResumeResponse struct { | 137 | func NewRevDataFromMsg(m *message.RevocationRevokeMsg) *RevData { |
177 | ID int // Command identifier (to relate responses) | 138 | rd := &RevData{ |
178 | err error // error code (nil on success) | 139 | Timestamp: m.Timestamp, |
140 | Signature: util.Clone(m.Signature), | ||
141 | ZoneKey: util.Clone(m.ZoneKey), | ||
142 | } | ||
143 | for i, pow := range m.PoWs { | ||
144 | rd.PoWs[i] = pow | ||
145 | } | ||
146 | return rd | ||
179 | } | 147 | } |
180 | 148 | ||
181 | // BreakResponse is a reply to the BreakCmd message | 149 | // Sign the revocation data |
182 | type BreakResponse struct { | 150 | func (rd *RevData) Sign(skey *ed25519.PrivateKey) error { |
183 | ID int // Command identifier (to relate responses) | 151 | sigBlock := &SignedRevData{ |
184 | Nonce uint64 // last checked nonce value | 152 | Purpose: &crypto.SignaturePurpose{ |
153 | Size: 48, | ||
154 | Purpose: enums.SIG_REVOCATION, | ||
155 | }, | ||
156 | ZoneKey: rd.ZoneKey, | ||
157 | Timestamp: rd.Timestamp, | ||
158 | } | ||
159 | sigData, err := data.Marshal(sigBlock) | ||
160 | if err != nil { | ||
161 | return err | ||
162 | } | ||
163 | sig, err := skey.EcSign(sigData) | ||
164 | if err != nil { | ||
165 | return err | ||
166 | } | ||
167 | copy(rd.Signature, sig.Bytes()) | ||
168 | return nil | ||
185 | } | 169 | } |
186 | 170 | ||
187 | //---------------------------------------------------------------------- | 171 | // Verify a revocation object: returns the (smallest) number of leading |
188 | // Worker instance | 172 | // zero-bits in the PoWs of this revocation; a number > 0, but smaller |
189 | //---------------------------------------------------------------------- | 173 | // than the minimum (25) indicates invalid PoWs; a value of -1 indicates |
190 | 174 | // a failed signature; -2 indicates an expired revocation and -3 for a | |
191 | // Task represents a currently active PoW calculation | 175 | // "out-of-order" PoW sequence. |
192 | type Task struct { | 176 | func (rd *RevData) Verify() int { |
193 | ID int | 177 | |
194 | rev *RevData | 178 | // (1) check signature |
195 | active bool | 179 | sigBlock := &SignedRevData{ |
196 | } | 180 | Purpose: &crypto.SignaturePurpose{ |
181 | Size: 48, | ||
182 | Purpose: enums.SIG_REVOCATION, | ||
183 | }, | ||
184 | ZoneKey: rd.ZoneKey, | ||
185 | Timestamp: rd.Timestamp, | ||
186 | } | ||
187 | sigData, err := data.Marshal(sigBlock) | ||
188 | if err != nil { | ||
189 | return -1 | ||
190 | } | ||
191 | pkey := ed25519.NewPublicKeyFromBytes(rd.ZoneKey) | ||
192 | sig, err := ed25519.NewEcSignatureFromBytes(rd.Signature) | ||
193 | if err != nil { | ||
194 | return -1 | ||
195 | } | ||
196 | valid, err := pkey.EcVerify(sigData, sig) | ||
197 | if err != nil || !valid { | ||
198 | return -1 | ||
199 | } | ||
197 | 200 | ||
198 | // Worker is the revocation worker. It is responsible to manage ad schedule | 201 | // (2) check PoWs |
199 | // the proof-of-work tasks for revocations. | 202 | var ( |
200 | type Worker struct { | 203 | zbits int = 512 |
201 | tasks map[int]*Task | 204 | last uint64 = 0 |
202 | wg *sync.WaitGroup | 205 | ) |
203 | } | 206 | for _, pow := range rd.PoWs { |
207 | // check sequence order | ||
208 | if pow <= last { | ||
209 | return -3 | ||
210 | } | ||
211 | last = pow | ||
212 | // compute number of leading zero-bits | ||
213 | work := NewPoWData(pow, rd.Timestamp, rd.ZoneKey) | ||
214 | lzb := 512 - work.Compute().BitLen() | ||
215 | if lzb < zbits { | ||
216 | zbits = lzb | ||
217 | } | ||
218 | } | ||
204 | 219 | ||
205 | func NewWorker() *Worker { | 220 | // (3) check expiration |
206 | return &Worker{ | 221 | ttl := time.Duration((zbits-24)*365*24) * time.Hour |
207 | tasks: make(map[int]*Task), | 222 | if util.AbsoluteTimeNow().Add(ttl).Expired() { |
208 | wg: new(sync.WaitGroup), | 223 | return -2 |
209 | } | 224 | } |
225 | return zbits | ||
210 | } | 226 | } |
211 | 227 | ||
212 | func (w *Worker) Run(wg *sync.WaitGroup, cmdCh chan interface{}, responseCh chan interface{}) { | 228 | // Compute tries to compute a valid Revocation; it returns the number of |
213 | defer wg.Done() | 229 | // solved PoWs. The computation is complete if 32 PoWs have been found. |
214 | for { | 230 | func (rd *RevData) Compute(ctx context.Context, bits int, last uint64) (int, uint64) { |
215 | select { | 231 | // set difficulty based on requested number of leading zero-bits |
216 | case cmd := <-cmdCh: | 232 | difficulty := math.TWO.Pow(512 - bits).Sub(math.ONE) |
217 | switch x := cmd.(type) { | 233 | |
218 | case *StartCmd: | 234 | // initialize a new work record (single PoW computation) |
219 | task := &Task{ | 235 | work := NewPoWData(0, rd.Timestamp, rd.ZoneKey) |
220 | ID: util.NextID(), | 236 | |
221 | rev: x.task, | 237 | // work on all PoWs in a revocation data structure; make sure all PoWs |
222 | active: true, | 238 | // are set to a valid value (that results in a valid compute() result |
239 | // below a given threshold) | ||
240 | for i, pow := range rd.PoWs { | ||
241 | // handle "new" pow value: set it to last_pow+1 | ||
242 | // this ensures a correctly sorted pow list by design. | ||
243 | if pow == 0 { | ||
244 | pow = last | ||
245 | } | ||
246 | if pow == 0 && i > 0 { | ||
247 | pow = rd.PoWs[i-1] + 1 | ||
248 | } | ||
249 | // prepare for PoW_i | ||
250 | work.SetPoW(pow) | ||
251 | |||
252 | // Find PoW value in an (interruptable) loop | ||
253 | out := make(chan bool) | ||
254 | go func() { | ||
255 | for { | ||
256 | res := work.Compute() | ||
257 | if res.Cmp(difficulty) < 0 { | ||
258 | break | ||
223 | } | 259 | } |
224 | w.tasks[task.ID] = task | 260 | work.Next() |
261 | } | ||
262 | out <- true | ||
263 | }() | ||
264 | loop: | ||
265 | for { | ||
266 | select { | ||
267 | case <-out: | ||
268 | rd.PoWs[i] = work.GetPoW() | ||
269 | break loop | ||
270 | case <-ctx.Done(): | ||
271 | return i, work.GetPoW() + 1 | ||
225 | } | 272 | } |
226 | |||
227 | default: | ||
228 | // compute a single round of currently active tasks | ||
229 | } | 273 | } |
230 | } | 274 | } |
275 | // we have found all valid PoW values. | ||
276 | return 32, 0 | ||
277 | } | ||
278 | |||
279 | func (rd *RevData) Blob() []byte { | ||
280 | blob, err := data.Marshal(rd) | ||
281 | if err != nil { | ||
282 | return nil | ||
283 | } | ||
284 | return blob | ||
231 | } | 285 | } |
diff --git a/src/gnunet/service/revocation/service.go b/src/gnunet/service/revocation/service.go new file mode 100644 index 0000000..f2a6ec3 --- /dev/null +++ b/src/gnunet/service/revocation/service.go | |||
@@ -0,0 +1,160 @@ | |||
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 revocation | ||
20 | |||
21 | import ( | ||
22 | "io" | ||
23 | |||
24 | "gnunet/message" | ||
25 | "gnunet/service" | ||
26 | "gnunet/transport" | ||
27 | |||
28 | "github.com/bfix/gospel/crypto/ed25519" | ||
29 | "github.com/bfix/gospel/logger" | ||
30 | ) | ||
31 | |||
32 | //---------------------------------------------------------------------- | ||
33 | // "GNUnet Revocation" service implementation | ||
34 | //---------------------------------------------------------------------- | ||
35 | |||
36 | // RevocationService | ||
37 | type RevocationService struct { | ||
38 | RevocationModule | ||
39 | } | ||
40 | |||
41 | // NewRevocationService | ||
42 | func NewRevocationService() service.Service { | ||
43 | // instantiate service and assemble a new Revocation handler. | ||
44 | inst := new(RevocationService) | ||
45 | return inst | ||
46 | } | ||
47 | |||
48 | // Start the Revocation service | ||
49 | func (s *RevocationService) Start(spec string) error { | ||
50 | return nil | ||
51 | } | ||
52 | |||
53 | // Stop the Revocation service | ||
54 | func (s *RevocationService) Stop() error { | ||
55 | return nil | ||
56 | } | ||
57 | |||
58 | // Serve a client channel. | ||
59 | func (s *RevocationService) ServeClient(ctx *service.SessionContext, mc *transport.MsgChannel) { | ||
60 | |||
61 | reqId := 0 | ||
62 | loop: | ||
63 | for { | ||
64 | // receive next message from client | ||
65 | reqId++ | ||
66 | logger.Printf(logger.DBG, "[revocation:%d:%d] Waiting for client request...\n", ctx.Id, reqId) | ||
67 | msg, err := mc.Receive(ctx.Signaller()) | ||
68 | if err != nil { | ||
69 | if err == io.EOF { | ||
70 | logger.Printf(logger.INFO, "[revocation:%d:%d] Client channel closed.\n", ctx.Id, reqId) | ||
71 | } else if err == transport.ErrChannelInterrupted { | ||
72 | logger.Printf(logger.INFO, "[revocation:%d:%d] Service operation interrupted.\n", ctx.Id, reqId) | ||
73 | } else { | ||
74 | logger.Printf(logger.ERROR, "[revocation:%d:%d] Message-receive failed: %s\n", ctx.Id, reqId, err.Error()) | ||
75 | } | ||
76 | break loop | ||
77 | } | ||
78 | logger.Printf(logger.INFO, "[revocation:%d:%d] Received request: %v\n", ctx.Id, reqId, msg) | ||
79 | |||
80 | // handle request | ||
81 | switch m := msg.(type) { | ||
82 | case *message.RevocationQueryMsg: | ||
83 | //---------------------------------------------------------- | ||
84 | // REVOCATION_QUERY | ||
85 | //---------------------------------------------------------- | ||
86 | go func(id int, m *message.RevocationQueryMsg) { | ||
87 | logger.Printf(logger.INFO, "[revocation:%d:%d] Query request received.\n", ctx.Id, id) | ||
88 | var resp *message.RevocationQueryResponseMsg | ||
89 | ctx.Add() | ||
90 | defer func() { | ||
91 | // send response | ||
92 | if resp != nil { | ||
93 | if err := mc.Send(resp, ctx.Signaller()); err != nil { | ||
94 | logger.Printf(logger.ERROR, "[revocation:%d:%d] Failed to send response: %s\n", ctx.Id, id, err.Error()) | ||
95 | } | ||
96 | } | ||
97 | // go-routine finished | ||
98 | logger.Printf(logger.DBG, "[revocation:%d:%d] Query request finished.\n", ctx.Id, id) | ||
99 | ctx.Remove() | ||
100 | }() | ||
101 | |||
102 | pkey := ed25519.NewPublicKeyFromBytes(m.Zone) | ||
103 | valid, err := s.Query(ctx, pkey) | ||
104 | if err != nil { | ||
105 | logger.Printf(logger.ERROR, "[revocation:%d:%d] Failed to query revocation status: %s\n", ctx.Id, id, err.Error()) | ||
106 | if err == transport.ErrChannelInterrupted { | ||
107 | resp = nil | ||
108 | } | ||
109 | return | ||
110 | } | ||
111 | resp = message.NewRevocationQueryResponseMsg(valid) | ||
112 | }(reqId, m) | ||
113 | |||
114 | case *message.RevocationRevokeMsg: | ||
115 | //---------------------------------------------------------- | ||
116 | // REVOCATION_REVOKE | ||
117 | //---------------------------------------------------------- | ||
118 | go func(id int, m *message.RevocationRevokeMsg) { | ||
119 | logger.Printf(logger.INFO, "[revocation:%d:%d] Revoke request received.\n", ctx.Id, id) | ||
120 | var resp *message.RevocationRevokeResponseMsg | ||
121 | ctx.Add() | ||
122 | defer func() { | ||
123 | // send response | ||
124 | if resp != nil { | ||
125 | if err := mc.Send(resp, ctx.Signaller()); err != nil { | ||
126 | logger.Printf(logger.ERROR, "[revocation:%d:%d] Failed to send response: %s\n", ctx.Id, id, err.Error()) | ||
127 | } | ||
128 | } | ||
129 | // go-routine finished | ||
130 | logger.Printf(logger.DBG, "[revocation:%d:%d] Revoke request finished.\n", ctx.Id, id) | ||
131 | ctx.Remove() | ||
132 | }() | ||
133 | |||
134 | rd := NewRevDataFromMsg(m) | ||
135 | valid, err := s.Revoke(ctx, rd) | ||
136 | if err != nil { | ||
137 | logger.Printf(logger.ERROR, "[revocation:%d:%d] Failed to revoke key: %s\n", ctx.Id, id, err.Error()) | ||
138 | if err == transport.ErrChannelInterrupted { | ||
139 | resp = nil | ||
140 | } | ||
141 | return | ||
142 | } | ||
143 | resp = message.NewRevocationRevokeResponseMsg(valid) | ||
144 | }(reqId, m) | ||
145 | |||
146 | default: | ||
147 | //---------------------------------------------------------- | ||
148 | // UNKNOWN message type received | ||
149 | //---------------------------------------------------------- | ||
150 | logger.Printf(logger.ERROR, "[revocation:%d:%d] Unhandled message of type (%d)\n", ctx.Id, reqId, msg.Header().MsgType) | ||
151 | break loop | ||
152 | } | ||
153 | } | ||
154 | // close client connection | ||
155 | mc.Close() | ||
156 | |||
157 | // cancel all tasks running for this session/connection | ||
158 | logger.Printf(logger.INFO, "[revocation:%d] Start closing session... [%d]\n", ctx.Id, ctx.Waiting()) | ||
159 | ctx.Cancel() | ||
160 | } | ||
diff --git a/src/gnunet/util/database.go b/src/gnunet/util/database.go new file mode 100644 index 0000000..48da749 --- /dev/null +++ b/src/gnunet/util/database.go | |||
@@ -0,0 +1,72 @@ | |||
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 util | ||
20 | |||
21 | import ( | ||
22 | "database/sql" | ||
23 | "fmt" | ||
24 | "os" | ||
25 | "strings" | ||
26 | |||
27 | _ "github.com/go-sql-driver/mysql" | ||
28 | _ "github.com/mattn/go-sqlite3" | ||
29 | ) | ||
30 | |||
31 | // Error messages related to databases | ||
32 | var ( | ||
33 | ErrSqlInvalidDatabaseSpec = fmt.Errorf("Invalid database specification") | ||
34 | ErrSqlNoDatabase = fmt.Errorf("Database not found") | ||
35 | ) | ||
36 | |||
37 | // ConnectSqlDatabase connects to an SQL database (various types and flavors): | ||
38 | // The 'spec' option defines the arguments required to connect to a database; | ||
39 | // the meaning and format of the arguments depends on the specific SQL database. | ||
40 | // The arguments are seperated by the '+' character; the first (and mandatory) | ||
41 | // argument defines the SQL database type. Other arguments depend on the value | ||
42 | // of this first argument. | ||
43 | // The following SQL types are implemented: | ||
44 | // * 'sqlite3': SQLite3-compatible database; the second argument specifies the | ||
45 | // file that holds the data (e.g. "sqlite3+/home/user/store.db") | ||
46 | // * 'mysql': A MySQL-compatible database; the second argument specifies the | ||
47 | // information required to log into the database (e.g. | ||
48 | // "[user[:passwd]@][proto[(addr)]]/dbname[?param1=value1&...]"). | ||
49 | func ConnectSqlDatabase(spec string) (db *sql.DB, err error) { | ||
50 | // split spec string into segments | ||
51 | specs := strings.Split(spec, ":") | ||
52 | if len(specs) < 2 { | ||
53 | return nil, ErrSqlInvalidDatabaseSpec | ||
54 | } | ||
55 | switch specs[0] { | ||
56 | case "sqlite3": | ||
57 | // check if the database file exists | ||
58 | var fi os.FileInfo | ||
59 | if fi, err = os.Stat(specs[1]); err != nil { | ||
60 | return nil, ErrSqlNoDatabase | ||
61 | } | ||
62 | if fi.IsDir() { | ||
63 | return nil, ErrSqlNoDatabase | ||
64 | } | ||
65 | // open the database file | ||
66 | return sql.Open("sqlite3", specs[1]) | ||
67 | case "mysql": | ||
68 | // just connect to the database | ||
69 | return sql.Open("mysql", specs[1]) | ||
70 | } | ||
71 | return nil, ErrSqlInvalidDatabaseSpec | ||
72 | } | ||
diff --git a/src/gnunet/util/key_value_store.go b/src/gnunet/util/key_value_store.go new file mode 100644 index 0000000..d87b747 --- /dev/null +++ b/src/gnunet/util/key_value_store.go | |||
@@ -0,0 +1,188 @@ | |||
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 util | ||
20 | |||
21 | import ( | ||
22 | "context" | ||
23 | "database/sql" | ||
24 | "fmt" | ||
25 | "strconv" | ||
26 | "strings" | ||
27 | |||
28 | "github.com/go-redis/redis" | ||
29 | ) | ||
30 | |||
31 | // Error messages related to the key/value-store implementations | ||
32 | var ( | ||
33 | ErrKVSInvalidSpec = fmt.Errorf("Invalid KVStore specification") | ||
34 | ErrKVSNotAvailable = fmt.Errorf("KVStore not available") | ||
35 | ) | ||
36 | |||
37 | // KeyValueStore interface for implementations that store and retrieve | ||
38 | // key/value pairs. Keys and values are strings. | ||
39 | type KeyValueStore interface { | ||
40 | Put(key string, value string) error // put a key/value pair into store | ||
41 | Get(key string) (string, error) // retrieve a value for a key from store | ||
42 | List() ([]string, error) // get all keys from the store | ||
43 | } | ||
44 | |||
45 | // OpenKVStore opens a key/value store for further put/get operations. | ||
46 | // The 'spec' option specifies the arguments required to connect to a specific | ||
47 | // persistence mechanism. The arguments in the 'spec' string are separated by | ||
48 | // the '+' character. | ||
49 | // The first argument specifies the type of key/value store to be used; the | ||
50 | // meaning and format of the following arguments depend on this type. | ||
51 | // | ||
52 | // Key/Value Store types defined: | ||
53 | // * 'redis': Use a Redis server for persistance; the specification is | ||
54 | // "redis+addr+[passwd]+db". 'db' must be an integer value. | ||
55 | // * 'mysql': MySQL-compatible database (see 'database.go' for details) | ||
56 | // * 'sqlite3': SQLite3-compatible database (see 'database.go' for details) | ||
57 | func OpenKVStore(spec string) (KeyValueStore, error) { | ||
58 | // check specification string | ||
59 | specs := strings.Split(spec, "+") | ||
60 | if len(specs) < 2 { | ||
61 | return nil, ErrKVSInvalidSpec | ||
62 | } | ||
63 | switch specs[0] { | ||
64 | case "redis": | ||
65 | //-------------------------------------------------------------- | ||
66 | // NoSQL-based persistance | ||
67 | //-------------------------------------------------------------- | ||
68 | if len(specs) < 4 { | ||
69 | return nil, ErrKVSInvalidSpec | ||
70 | } | ||
71 | db, err := strconv.Atoi(specs[3]) | ||
72 | if err != nil { | ||
73 | return nil, ErrKVSInvalidSpec | ||
74 | } | ||
75 | kvs := new(KvsRedis) | ||
76 | kvs.db = db | ||
77 | kvs.client = redis.NewClient(&redis.Options{ | ||
78 | Addr: specs[1], | ||
79 | Password: specs[2], | ||
80 | DB: db, | ||
81 | }) | ||
82 | if kvs.client == nil { | ||
83 | err = ErrKVSNotAvailable | ||
84 | } | ||
85 | return kvs, err | ||
86 | |||
87 | case "sqlite3", "mysql": | ||
88 | //-------------------------------------------------------------- | ||
89 | // SQL-based persistance | ||
90 | //-------------------------------------------------------------- | ||
91 | kvs := new(KvsSql) | ||
92 | var err error | ||
93 | |||
94 | // connect to SQL database | ||
95 | kvs.db, err = ConnectSqlDatabase(spec) | ||
96 | if err != nil { | ||
97 | return nil, err | ||
98 | } | ||
99 | // get number of key/value pairs (as a check for existing table) | ||
100 | row := kvs.db.QueryRow("select count(*) from store") | ||
101 | var num int | ||
102 | if row.Scan(&num) != nil { | ||
103 | return nil, ErrKVSNotAvailable | ||
104 | } | ||
105 | return kvs, nil | ||
106 | } | ||
107 | return nil, ErrKVSInvalidSpec | ||
108 | } | ||
109 | |||
110 | //====================================================================== | ||
111 | // NoSQL-based key-value-stores | ||
112 | //====================================================================== | ||
113 | |||
114 | // Redis-based key/value store | ||
115 | type KvsRedis struct { | ||
116 | client *redis.Client // client connection | ||
117 | db int // index to database | ||
118 | } | ||
119 | |||
120 | // Put a key/value pair into the store | ||
121 | func (kvs *KvsRedis) Put(key string, value string) error { | ||
122 | return kvs.client.Set(context.TODO(), key, value, 0).Err() | ||
123 | } | ||
124 | |||
125 | // Get a value for a given key from store | ||
126 | func (kvs *KvsRedis) Get(key string) (value string, err error) { | ||
127 | return kvs.client.Get(context.TODO(), key).Result() | ||
128 | } | ||
129 | |||
130 | // Get a list of all keys in store | ||
131 | func (kvs *KvsRedis) List() (keys []string, err error) { | ||
132 | var ( | ||
133 | crs uint64 | ||
134 | segm []string | ||
135 | ctx = context.TODO() | ||
136 | ) | ||
137 | for { | ||
138 | segm, crs, err = kvs.client.Scan(ctx, crs, "*", 10).Result() | ||
139 | if err != nil { | ||
140 | return nil, err | ||
141 | } | ||
142 | if crs == 0 { | ||
143 | break | ||
144 | } | ||
145 | keys = append(keys, segm...) | ||
146 | } | ||
147 | return | ||
148 | } | ||
149 | |||
150 | //====================================================================== | ||
151 | // SQL-based key-value-store | ||
152 | //====================================================================== | ||
153 | |||
154 | // SQL-based key/value store | ||
155 | type KvsSql struct { | ||
156 | db *sql.DB | ||
157 | } | ||
158 | |||
159 | // Put a key/value pair into the store | ||
160 | func (kvs *KvsSql) Put(key string, value string) error { | ||
161 | _, err := kvs.db.Exec("insert into store(key,value) values(?,?)", key, value) | ||
162 | return err | ||
163 | } | ||
164 | |||
165 | // Get a value for a given key from store | ||
166 | func (kvs *KvsSql) Get(key string) (value string, err error) { | ||
167 | row := kvs.db.QueryRow("select value from store where key=?", key) | ||
168 | err = row.Scan(&value) | ||
169 | return | ||
170 | } | ||
171 | |||
172 | // Get a list of all keys in store | ||
173 | func (kvs *KvsSql) List() (keys []string, err error) { | ||
174 | var ( | ||
175 | rows *sql.Rows | ||
176 | key string | ||
177 | ) | ||
178 | rows, err = kvs.db.Query("select key from store") | ||
179 | if err == nil { | ||
180 | for rows.Next() { | ||
181 | if err = rows.Scan(&key); err != nil { | ||
182 | break | ||
183 | } | ||
184 | keys = append(keys, key) | ||
185 | } | ||
186 | } | ||
187 | return | ||
188 | } | ||
diff --git a/src/gnunet/util/time.go b/src/gnunet/util/time.go index e1e0e30..bf2a1c2 100644 --- a/src/gnunet/util/time.go +++ b/src/gnunet/util/time.go | |||
@@ -69,6 +69,18 @@ func (t AbsoluteTime) Add(d time.Duration) AbsoluteTime { | |||
69 | } | 69 | } |
70 | } | 70 | } |
71 | 71 | ||
72 | // Diff returns the relative time between two absolute times; | ||
73 | // the ordering of the absolute times doesn't matter. | ||
74 | func (t AbsoluteTime) Diff(t2 AbsoluteTime) RelativeTime { | ||
75 | var d uint64 | ||
76 | if t.Compare(t2) == 1 { | ||
77 | d = t.Val - t2.Val | ||
78 | } else { | ||
79 | d = t2.Val - t.Val | ||
80 | } | ||
81 | return RelativeTime{d} | ||
82 | } | ||
83 | |||
72 | // Expired returns true if the timestamp is in the past. | 84 | // Expired returns true if the timestamp is in the past. |
73 | func (t AbsoluteTime) Expired() bool { | 85 | func (t AbsoluteTime) Expired() bool { |
74 | // check for "never" | 86 | // check for "never" |