aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBernd Fix <brf@hoi-polloi.org>2020-05-24 13:34:05 +0200
committerBernd Fix <brf@hoi-polloi.org>2020-05-24 13:34:05 +0200
commitfdf23180919ec42422694f1f1801eead1ea576e8 (patch)
tree7ef36ebfd29816cbdbb1c16cf99c285db8025bef
parent3d0b7cab3e273dd2f3f3d8990fb868b93b6b3f95 (diff)
downloadgnunet-go-fdf23180919ec42422694f1f1801eead1ea576e8.tar.gz
gnunet-go-fdf23180919ec42422694f1f1801eead1ea576e8.zip
Milestone #3 (RC1)
-rw-r--r--src/cmd/gnunet-service-revocation-go/main.go105
-rw-r--r--src/cmd/pow-test/main.go51
-rw-r--r--src/cmd/revoke-zonekey/main.go192
-rw-r--r--src/gnunet/config/config.go18
-rw-r--r--src/gnunet/message/factory.go2
-rw-r--r--src/gnunet/message/msg_revocation.go35
-rw-r--r--src/gnunet/modules.go19
-rw-r--r--src/gnunet/service/gns/module.go21
-rw-r--r--src/gnunet/service/gns/service.go59
-rw-r--r--src/gnunet/service/revocation/module.go124
-rw-r--r--src/gnunet/service/revocation/pow.go346
-rw-r--r--src/gnunet/service/revocation/service.go160
-rw-r--r--src/gnunet/util/database.go72
-rw-r--r--src/gnunet/util/key_value_store.go188
-rw-r--r--src/gnunet/util/time.go12
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
19package main
20
21import (
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
34func 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
81loop:
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 @@
1package main
2
3import (
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
15func 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
19package main
20
21import (
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
38func 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
61type 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
60type Environ map[string]string 69type Environ map[string]string
61 70
62// Config is the aggregated configuration for GNUnet. 71// Config is the aggregated configuration for GNUnet.
63type Config struct { 72type 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
70var ( 80var (
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
21import ( 21import (
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
106type RevocationRevokeMsg struct { 104type 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.
117func NewRevocationRevokeMsg(pow uint64, zoneKey *ed25519.PublicKey, sig *ed25519.EcSignature) *RevocationRevokeMsg { 114func 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.
140func (m *RevocationRevokeMsg) String() string { 133func (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
154type RevocationRevokeResponseMsg struct { 147type 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
38type Instances struct { 39type 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.
112type GNSModule struct { 113type 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//
176func (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//
199func (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
171func (s *GNSService) LookupNamecache(ctx *service.SessionContext, query *Query) (block *message.GNSBlock, err error) { 228func (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
270func (s *GNSService) LookupDHT(ctx *service.SessionContext, query *Query) (block *message.GNSBlock, err error) { 329func (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
19package revocation
20
21import (
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.
36type 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
42func (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
65func 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.
76func (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
95func (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
21import ( 21import (
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
44type RevData struct { 43type 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.
53func NewRevData(nonce uint64, zoneKey *ed25519.PublicKey) *RevData { 53func 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 65func (p *PoWData) SetPoW(pow uint64) error {
68func (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
78func (r *RevData) GetBlob() []byte { 76func (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.
83func (r *RevData) Next() { 86func (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.
97func (r *RevData) Compute() (*math.Int, error) { 100func (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 110type 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).
133type 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
140type 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
147type 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
153type 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 118type 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
163type 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
170type PauseResponse struct { 125func 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
176type ResumeResponse struct { 137func 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
182type BreakResponse struct { 150func (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.
192type Task struct { 176func (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 (
200type 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
205func 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
212func (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 { 230func (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
279func (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
19package revocation
20
21import (
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
37type RevocationService struct {
38 RevocationModule
39}
40
41// NewRevocationService
42func 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
49func (s *RevocationService) Start(spec string) error {
50 return nil
51}
52
53// Stop the Revocation service
54func (s *RevocationService) Stop() error {
55 return nil
56}
57
58// Serve a client channel.
59func (s *RevocationService) ServeClient(ctx *service.SessionContext, mc *transport.MsgChannel) {
60
61 reqId := 0
62loop:
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
19package util
20
21import (
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
32var (
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&...]").
49func 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
19package util
20
21import (
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
32var (
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.
39type 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)
57func 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
115type 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
121func (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
126func (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
131func (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
155type KvsSql struct {
156 db *sql.DB
157}
158
159// Put a key/value pair into the store
160func (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
166func (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
173func (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.
74func (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.
73func (t AbsoluteTime) Expired() bool { 85func (t AbsoluteTime) Expired() bool {
74 // check for "never" 86 // check for "never"