carps 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/COPYING +674 -0
- data/GEM_DESCRIPTION +10 -0
- data/History.txt +4 -0
- data/Manifest.txt +156 -0
- data/PostInstall.txt +20 -0
- data/README.rdoc +141 -0
- data/Rakefile +28 -0
- data/bin/carps +123 -0
- data/bin/carps_init +29 -0
- data/bin/carps_ipc_test +44 -0
- data/bin/carps_mod_saver_test +71 -0
- data/bin/carps_mod_test +62 -0
- data/config/website.yml +2 -0
- data/features/character_sheet.feature +40 -0
- data/features/crash.feature +15 -0
- data/features/crypt.feature +22 -0
- data/features/dice.feature +173 -0
- data/features/dm.feature +36 -0
- data/features/dmll.feature +51 -0
- data/features/edit.feature +10 -0
- data/features/email.feature +29 -0
- data/features/interface.feature +17 -0
- data/features/ipc.feature +10 -0
- data/features/mailbox.feature +33 -0
- data/features/mod.feature +31 -0
- data/features/parser.feature +9 -0
- data/features/persistent_protocol.feature +14 -0
- data/features/player.feature +22 -0
- data/features/player_turn.feature +29 -0
- data/features/random.feature +15 -0
- data/features/rule.feature +19 -0
- data/features/safety.feature +13 -0
- data/features/sessions.feature +16 -0
- data/features/start_dm.feature +18 -0
- data/features/start_player.feature +8 -0
- data/features/step_definitions/common_steps.rb +170 -0
- data/features/steps/character_sheet.rb +89 -0
- data/features/steps/crash.rb +27 -0
- data/features/steps/crypt.rb +166 -0
- data/features/steps/dice.rb +91 -0
- data/features/steps/dm.rb +147 -0
- data/features/steps/dmll.rb +47 -0
- data/features/steps/edit.rb +7 -0
- data/features/steps/email.rb +108 -0
- data/features/steps/general.rb +5 -0
- data/features/steps/interface.rb +64 -0
- data/features/steps/ipc.rb +22 -0
- data/features/steps/mailbox.rb +126 -0
- data/features/steps/mod.rb +25 -0
- data/features/steps/parser.rb +23 -0
- data/features/steps/persistent_protocol.rb +29 -0
- data/features/steps/player.rb +82 -0
- data/features/steps/player_turn.rb +56 -0
- data/features/steps/random.rb +47 -0
- data/features/steps/rule.rb +53 -0
- data/features/steps/safety.rb +17 -0
- data/features/steps/sessions.rb +37 -0
- data/features/steps/start_dm.rb +46 -0
- data/features/steps/start_player.rb +65 -0
- data/features/steps/timeout.rb +32 -0
- data/features/steps/type_verification.rb +36 -0
- data/features/steps/wizard.rb +123 -0
- data/features/support/common.rb +29 -0
- data/features/support/env.rb +14 -0
- data/features/support/matchers.rb +11 -0
- data/features/timeout.feature +11 -0
- data/features/type_verification.feature +48 -0
- data/features/wizard.feature +50 -0
- data/lib/carps/crypt/accept_handshake.rb +40 -0
- data/lib/carps/crypt/default_messages.rb +29 -0
- data/lib/carps/crypt/handshake.rb +41 -0
- data/lib/carps/crypt/mailbox.rb +265 -0
- data/lib/carps/crypt/mailer.rb +220 -0
- data/lib/carps/crypt/peer.rb +123 -0
- data/lib/carps/crypt/public_key.rb +60 -0
- data/lib/carps/crypt.rb +23 -0
- data/lib/carps/email/config.rb +122 -0
- data/lib/carps/email/imap.rb +156 -0
- data/lib/carps/email/smtp.rb +119 -0
- data/lib/carps/email/string.rb +30 -0
- data/lib/carps/email.rb +21 -0
- data/lib/carps/mod/action.rb +36 -0
- data/lib/carps/mod/answers.rb +76 -0
- data/lib/carps/mod/client_turn.rb +88 -0
- data/lib/carps/mod/dice.rb +360 -0
- data/lib/carps/mod/dm/interface.rb +160 -0
- data/lib/carps/mod/dm/mod.rb +409 -0
- data/lib/carps/mod/dm/reporter.rb +112 -0
- data/lib/carps/mod/dm/resource.rb +88 -0
- data/lib/carps/mod/dm/room.rb +33 -0
- data/lib/carps/mod/interface.rb +73 -0
- data/lib/carps/mod/launch.rb +108 -0
- data/lib/carps/mod/mod.rb +52 -0
- data/lib/carps/mod/player/interface.rb +76 -0
- data/lib/carps/mod/player/mod.rb +109 -0
- data/lib/carps/mod/question.rb +63 -0
- data/lib/carps/mod/rule.rb +112 -0
- data/lib/carps/mod/sheet/character.rb +82 -0
- data/lib/carps/mod/sheet/editor.rb +97 -0
- data/lib/carps/mod/sheet/new_sheet.rb +71 -0
- data/lib/carps/mod/sheet/schema.rb +84 -0
- data/lib/carps/mod/sheet/type.rb +161 -0
- data/lib/carps/mod/sheet/verifier.rb +41 -0
- data/lib/carps/mod/status_report.rb +51 -0
- data/lib/carps/mod.rb +40 -0
- data/lib/carps/protocol/keyword.rb +104 -0
- data/lib/carps/protocol/message.rb +138 -0
- data/lib/carps/protocol.rb +19 -0
- data/lib/carps/service/client_parser.rb +34 -0
- data/lib/carps/service/dm/config.rb +76 -0
- data/lib/carps/service/dm/mailer.rb +70 -0
- data/lib/carps/service/dm/new_game.rb +101 -0
- data/lib/carps/service/dm/start.rb +47 -0
- data/lib/carps/service/game.rb +101 -0
- data/lib/carps/service/interface.rb +174 -0
- data/lib/carps/service/invite.rb +79 -0
- data/lib/carps/service/mod.rb +43 -0
- data/lib/carps/service/player/config.rb +78 -0
- data/lib/carps/service/player/mailer.rb +49 -0
- data/lib/carps/service/player/start.rb +60 -0
- data/lib/carps/service/server_parser.rb +33 -0
- data/lib/carps/service/session.rb +96 -0
- data/lib/carps/service/start/config.rb +71 -0
- data/lib/carps/service/start/interface.rb +101 -0
- data/lib/carps/service/start/mailer.rb +46 -0
- data/lib/carps/service.rb +35 -0
- data/lib/carps/test.rb +34 -0
- data/lib/carps/ui/colour.rb +28 -0
- data/lib/carps/ui/error.rb +39 -0
- data/lib/carps/ui/highlight.rb +32 -0
- data/lib/carps/ui/question.rb +63 -0
- data/lib/carps/ui/warn.rb +37 -0
- data/lib/carps/ui.rb +21 -0
- data/lib/carps/util/config.rb +162 -0
- data/lib/carps/util/editor.rb +147 -0
- data/lib/carps/util/error.rb +75 -0
- data/lib/carps/util/files.rb +48 -0
- data/lib/carps/util/init.rb +93 -0
- data/lib/carps/util/process.rb +150 -0
- data/lib/carps/util/timeout.rb +31 -0
- data/lib/carps/util/windows.rb +29 -0
- data/lib/carps/util.rb +24 -0
- data/lib/carps/wizard/dm.rb +41 -0
- data/lib/carps/wizard/player.rb +40 -0
- data/lib/carps/wizard/steps.rb +494 -0
- data/lib/carps/wizard/wizard.rb +126 -0
- data/lib/carps/wizard.rb +21 -0
- data/lib/carps.rb +45 -0
- data/permission +16 -0
- data/script/console +10 -0
- data/script/destroy +14 -0
- data/script/generate +14 -0
- data/tasks/readme_site.rake +28 -0
- data/test/test_carps.rb +11 -0
- data/test/test_helper.rb +3 -0
- data/website/index.html +271 -0
- metadata +304 -0
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
# Copyright 2010 John Morrice
|
|
2
|
+
|
|
3
|
+
# This file is part of CARPS.
|
|
4
|
+
|
|
5
|
+
# CARPS is free software: you can redistribute it and/or modify
|
|
6
|
+
# it under the terms of the GNU General Public License as published by
|
|
7
|
+
# the Free Software Foundation, either version 3 of the License, or
|
|
8
|
+
# (at your option) any later version.
|
|
9
|
+
|
|
10
|
+
# CARPS is distributed in the hope that it will be useful,
|
|
11
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
# GNU General Public License for more details.
|
|
14
|
+
|
|
15
|
+
# You should have received a copy of the GNU General Public License
|
|
16
|
+
# along with CARPS. If not, see <http://www.gnu.org/licenses/>.
|
|
17
|
+
|
|
18
|
+
# This mailbox receives all messages that come in
|
|
19
|
+
|
|
20
|
+
require "carps/ui/warn"
|
|
21
|
+
|
|
22
|
+
require "carps/util/process"
|
|
23
|
+
require "carps/util/files"
|
|
24
|
+
|
|
25
|
+
require "drb"
|
|
26
|
+
|
|
27
|
+
module CARPS
|
|
28
|
+
|
|
29
|
+
# The mailbox's responsibility is in sending messages and securely and robustly receiving them
|
|
30
|
+
#
|
|
31
|
+
# It has knowledge is of the public keys of the Mailer s of the remote peers
|
|
32
|
+
class Mailbox
|
|
33
|
+
|
|
34
|
+
include DRbUndumped
|
|
35
|
+
|
|
36
|
+
# Create the mailbox from a simple, synchronous mail sender and receiver.
|
|
37
|
+
#
|
|
38
|
+
# The third parameter is a MessageParser.
|
|
39
|
+
#
|
|
40
|
+
# The fourth parameter is a SessionManager.
|
|
41
|
+
def initialize sender, receiver, parser, manager
|
|
42
|
+
@manager = manager
|
|
43
|
+
@receiver = receiver
|
|
44
|
+
@parser = parser
|
|
45
|
+
@sender = sender
|
|
46
|
+
@mail = []
|
|
47
|
+
@peers = {}
|
|
48
|
+
@secure = false
|
|
49
|
+
# Semaphore to make sure only one thread can send mail at any one time
|
|
50
|
+
@ssemaphore = Mutex.new
|
|
51
|
+
# Semaphore to make sure only one thread can receive mail at any one time
|
|
52
|
+
@rsemaphore = Mutex.new
|
|
53
|
+
# Load mails from the last session
|
|
54
|
+
load_old_mails
|
|
55
|
+
# Receive mail
|
|
56
|
+
receive_forever
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Shutdown the mailbox
|
|
60
|
+
def shutdown
|
|
61
|
+
@child.kill
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Add a new peer
|
|
65
|
+
def add_peer peer
|
|
66
|
+
@peers[peer.addr] = peer
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Is this already a peer?
|
|
70
|
+
def peer? peer
|
|
71
|
+
@peers.member? peer
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Send a message
|
|
75
|
+
def send to, message
|
|
76
|
+
@ssemaphore.synchronize do
|
|
77
|
+
@sender.send to, message
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Tag some text
|
|
82
|
+
def tag text
|
|
83
|
+
@manager.tag text
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Securely read a message. Block until one occurs.
|
|
87
|
+
def read type, must_be_from=nil
|
|
88
|
+
msg = nil
|
|
89
|
+
until msg
|
|
90
|
+
msg = search type, must_be_from
|
|
91
|
+
sleep 1
|
|
92
|
+
end
|
|
93
|
+
# Ding!
|
|
94
|
+
puts "\a"
|
|
95
|
+
return msg
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Check for a new message. Don't block
|
|
99
|
+
def check type, must_be_from=nil
|
|
100
|
+
search type, must_be_from
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Insecurely read a message. Block until one comes.
|
|
104
|
+
def insecure_read type, must_be_from=nil
|
|
105
|
+
msg = nil
|
|
106
|
+
until msg
|
|
107
|
+
msg = insecure_search type, must_be_from
|
|
108
|
+
sleep 1
|
|
109
|
+
end
|
|
110
|
+
# Ding!
|
|
111
|
+
puts "\a"
|
|
112
|
+
return msg
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Check for new messages insecurely. Don't block.
|
|
116
|
+
def insecure_check type, must_be_from=nil
|
|
117
|
+
insecure_search type, must_be_from
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
private
|
|
121
|
+
|
|
122
|
+
# See if there is an appropriate message in the mail box
|
|
123
|
+
def search type, must_be_from
|
|
124
|
+
@rsemaphore.synchronize do
|
|
125
|
+
@mail.each_index do |index|
|
|
126
|
+
mail = @mail[index]
|
|
127
|
+
from = mail.from
|
|
128
|
+
if secure from
|
|
129
|
+
unless @peers[from].verify mail
|
|
130
|
+
remove_mail index
|
|
131
|
+
next
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
pass = appropriate?(mail, type, must_be_from)
|
|
135
|
+
if pass
|
|
136
|
+
remove_mail index
|
|
137
|
+
return mail
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
nil
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# Was the mail message appropriate?
|
|
145
|
+
def appropriate? mail, type, must_be_from
|
|
146
|
+
pass = mail.class == type
|
|
147
|
+
if must_be_from
|
|
148
|
+
pass = pass and mail.from == must_be_from
|
|
149
|
+
end
|
|
150
|
+
pass and @manager.belong? mail
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# Remove a mail message
|
|
154
|
+
def remove_mail index
|
|
155
|
+
@mail[index].delete
|
|
156
|
+
@mail.delete_at index
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# Communication with someone is secure if there is a peer for them
|
|
160
|
+
def secure addr
|
|
161
|
+
@peers.member? addr
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
# Insecurely see if there is an appropriate message in the mail box
|
|
165
|
+
def insecure_search type, must_be_from
|
|
166
|
+
@rsemaphore.synchronize do
|
|
167
|
+
@mail.each_index do |index|
|
|
168
|
+
mail = @mail[index]
|
|
169
|
+
pass = appropriate?(mail, type, must_be_from)
|
|
170
|
+
if pass
|
|
171
|
+
remove_mail index
|
|
172
|
+
return mail
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
nil
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
# Receive new mail
|
|
180
|
+
def receive_forever
|
|
181
|
+
@child = Thread.fork do
|
|
182
|
+
loop do
|
|
183
|
+
receive_new
|
|
184
|
+
sleep 1
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
# Read new mail messages into the mail box
|
|
190
|
+
def receive_new
|
|
191
|
+
mail = @receiver.read
|
|
192
|
+
mail.each do |blob|
|
|
193
|
+
decode_mail blob
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
# Read a new mail message from a blob of text
|
|
198
|
+
def decode_mail blob, persistence = {:save_mail => true}
|
|
199
|
+
input = blob
|
|
200
|
+
who = nil
|
|
201
|
+
|
|
202
|
+
begin
|
|
203
|
+
# Find who sent the message
|
|
204
|
+
who, blob = find K.addr, blob
|
|
205
|
+
rescue Expected
|
|
206
|
+
UI::warn "Mail message did not contain sender.", blob
|
|
207
|
+
return
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
# Get the security information from the mail
|
|
211
|
+
delayed_crypt, blob = security_info blob
|
|
212
|
+
|
|
213
|
+
# Find the message's session
|
|
214
|
+
session = nil
|
|
215
|
+
begin
|
|
216
|
+
session, blob = find K.session, blob
|
|
217
|
+
rescue Expected
|
|
218
|
+
UI::warn "Mail message did not contain session.", blob
|
|
219
|
+
return
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
# Parse a message
|
|
223
|
+
msg = @parser.parse blob
|
|
224
|
+
|
|
225
|
+
if msg
|
|
226
|
+
msg.session = session
|
|
227
|
+
msg.crypt = delayed_crypt
|
|
228
|
+
msg.from = who
|
|
229
|
+
puts "Mail from #{who}: #{msg.class.to_s}"
|
|
230
|
+
# Save the text we parsed the message from.
|
|
231
|
+
if persistence[:save_mail]
|
|
232
|
+
msg.save input
|
|
233
|
+
end
|
|
234
|
+
path = persistence[:path]
|
|
235
|
+
if path
|
|
236
|
+
msg.path = path
|
|
237
|
+
end
|
|
238
|
+
@rsemaphore.synchronize do
|
|
239
|
+
@mail.push msg
|
|
240
|
+
end
|
|
241
|
+
else
|
|
242
|
+
UI::warn "Failed to parse message from #{who}"
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
# Load mails from the last session
|
|
247
|
+
def load_old_mails
|
|
248
|
+
old_mails = files $CONFIG + "/.mail"
|
|
249
|
+
old_mails.each do |fn|
|
|
250
|
+
blob = nil
|
|
251
|
+
begin
|
|
252
|
+
blob = File.read fn
|
|
253
|
+
rescue StandardError => e
|
|
254
|
+
UI::put_error "Could not read old message: #{e}"
|
|
255
|
+
end
|
|
256
|
+
if blob
|
|
257
|
+
blob.force_encoding "ASCII-8BIT"
|
|
258
|
+
decode_mail blob, {:save_mail => false, :path => fn}
|
|
259
|
+
end
|
|
260
|
+
end
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
end
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
# Copyright 2010 John Morrice
|
|
2
|
+
|
|
3
|
+
# This file is part of CARPS.
|
|
4
|
+
|
|
5
|
+
# CARPS is free software: you can redistribute it and/or modify
|
|
6
|
+
# it under the terms of the GNU General Public License as published by
|
|
7
|
+
# the Free Software Foundation, either version 3 of the License, or
|
|
8
|
+
# (at your option) any later version.
|
|
9
|
+
|
|
10
|
+
# CARPS is distributed in the hope that it will be useful,
|
|
11
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
# GNU General Public License for more details.
|
|
14
|
+
|
|
15
|
+
# You should have received a copy of the GNU General Public License
|
|
16
|
+
# along with CARPS. If not, see <http://www.gnu.org/licenses/>.
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
require "carps/protocol/keyword"
|
|
20
|
+
|
|
21
|
+
require "carps/ui/warn"
|
|
22
|
+
require "carps/ui/question"
|
|
23
|
+
|
|
24
|
+
require "carps/util/process"
|
|
25
|
+
require "carps/util/files"
|
|
26
|
+
|
|
27
|
+
require "carps/crypt/handshake"
|
|
28
|
+
require "carps/crypt/public_key"
|
|
29
|
+
require "carps/crypt/accept_handshake"
|
|
30
|
+
require "carps/crypt/peer"
|
|
31
|
+
|
|
32
|
+
require "digest/md5"
|
|
33
|
+
|
|
34
|
+
require "openssl"
|
|
35
|
+
|
|
36
|
+
module CARPS
|
|
37
|
+
|
|
38
|
+
# High level CARPS mail client supporting strong cryptographic message signing.
|
|
39
|
+
#
|
|
40
|
+
# It has knowledge of our own public and private key. Its big responsibility is turning Messages into Strings and signing them.
|
|
41
|
+
class Mailer
|
|
42
|
+
|
|
43
|
+
# Extend protocol for sharing our address
|
|
44
|
+
protoval :addr
|
|
45
|
+
|
|
46
|
+
# The first parameter is the email address
|
|
47
|
+
#
|
|
48
|
+
# The second the Mailbox.
|
|
49
|
+
def initialize address, mailbox
|
|
50
|
+
@addr = address
|
|
51
|
+
@mailbox = mailbox
|
|
52
|
+
@private_key = get_keys
|
|
53
|
+
@public_key = @private_key.public_key
|
|
54
|
+
# Load the old peers
|
|
55
|
+
load_peers
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Perform a handshake to authenticate with a peer
|
|
59
|
+
def handshake to
|
|
60
|
+
if @mailbox.peer? to
|
|
61
|
+
puts "No need for handshake: " + to + " is already a known peer."
|
|
62
|
+
else
|
|
63
|
+
puts "Offering cryptographic handshake to #{to}"
|
|
64
|
+
# Create a new peer
|
|
65
|
+
peer = Peer.new to
|
|
66
|
+
@mailbox.add_peer peer
|
|
67
|
+
# Request a handshake
|
|
68
|
+
send to, Handshake.new
|
|
69
|
+
# Get the peer's key
|
|
70
|
+
their_key = @mailbox.insecure_read PublicKey, to
|
|
71
|
+
peer.your_key their_key.key
|
|
72
|
+
peer.save
|
|
73
|
+
# Send our key
|
|
74
|
+
send to, PublicKey.new(@public_key)
|
|
75
|
+
# Receive an okay message
|
|
76
|
+
read AcceptHandshake, to
|
|
77
|
+
puts "Established spoof-proof communications with #{to}"
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Shutdown the mailer
|
|
82
|
+
def shutdown
|
|
83
|
+
@mailbox.shutdown
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Check for handshakes
|
|
87
|
+
def check_handshake
|
|
88
|
+
@mailbox.insecure_check Handshake
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Respond to a handshake request
|
|
92
|
+
def handle_handshake handshake
|
|
93
|
+
# Get the peer's address
|
|
94
|
+
from = handshake.from
|
|
95
|
+
puts "Receiving handshake request from #{from}."
|
|
96
|
+
if @mailbox.peer? from
|
|
97
|
+
UI::warn "Handshake request from #{from} has been dropped because #{from} is already a known peer", "Possible spoofing attack."
|
|
98
|
+
else
|
|
99
|
+
# See if the user accepts the handshake.
|
|
100
|
+
accept = accept_handshake? from
|
|
101
|
+
if accept
|
|
102
|
+
# Send our key to the peer
|
|
103
|
+
send from, PublicKey.new(@public_key)
|
|
104
|
+
# Get their key
|
|
105
|
+
peer_key = @mailbox.insecure_read PublicKey, from
|
|
106
|
+
# Create a new peer
|
|
107
|
+
peer = Peer.new from
|
|
108
|
+
@mailbox.add_peer peer
|
|
109
|
+
peer.your_key peer_key.key
|
|
110
|
+
peer.save
|
|
111
|
+
# Send an okay message
|
|
112
|
+
send from, AcceptHandshake.new
|
|
113
|
+
puts "Established spoof-proof communications with #{from}."
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# Give our address to interested parties
|
|
119
|
+
def address
|
|
120
|
+
@addr
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# Send a message
|
|
124
|
+
def send to, message
|
|
125
|
+
text = message.emit
|
|
126
|
+
# The mailbox tags the message with a session key
|
|
127
|
+
text = @mailbox.tag text
|
|
128
|
+
# Sign the message
|
|
129
|
+
digest = Digest::MD5.digest text
|
|
130
|
+
sig = @private_key.syssign digest
|
|
131
|
+
mail = (V.addr @addr) + (V.sig sig) + text + K.end
|
|
132
|
+
@mailbox.send to, mail
|
|
133
|
+
puts "#{message.class} sent to " + to
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# Receive a message. Block until it is here.
|
|
137
|
+
def read type, must_be_from=nil
|
|
138
|
+
@mailbox.read type, must_be_from
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# Check for a message. Don't block! Return nil if nothing is available.
|
|
142
|
+
def check type, must_be_from=nil
|
|
143
|
+
@mailbox.check type, must_be_from
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
protected
|
|
147
|
+
|
|
148
|
+
# Ask the user if they accept the handshake.
|
|
149
|
+
#
|
|
150
|
+
# Refactored out for tinkering and automated testing.
|
|
151
|
+
def accept_handshake? from
|
|
152
|
+
UI::confirm "Accept handshake from #{from}?"
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
private
|
|
157
|
+
|
|
158
|
+
# Get cryptographic keys
|
|
159
|
+
#
|
|
160
|
+
# If we can't find them, regenerate them
|
|
161
|
+
def get_keys
|
|
162
|
+
pkey = OpenSSL::PKey
|
|
163
|
+
if File.exists? keyfile
|
|
164
|
+
begin
|
|
165
|
+
pem = File.read keyfile
|
|
166
|
+
return pkey::DSA.new pem
|
|
167
|
+
rescue
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
UI::warn "Could not read cryptographic key from #{keyfile}"
|
|
171
|
+
return keygen
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
# The key file
|
|
175
|
+
def keyfile
|
|
176
|
+
keyfile = $CONFIG + ".key"
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
# Generate keys
|
|
180
|
+
def keygen
|
|
181
|
+
puts "Generating cryptographic keys. This may take a minute."
|
|
182
|
+
key = OpenSSL::PKey::DSA.generate 2048
|
|
183
|
+
begin
|
|
184
|
+
pri = File.new keyfile, "w"
|
|
185
|
+
pri.chmod 0600
|
|
186
|
+
pri.write key.to_pem
|
|
187
|
+
pri.close
|
|
188
|
+
rescue
|
|
189
|
+
UI::warn "Could not save cryptographic keys in #{keyfile}", "They will be regenerated next time CARPS is run."
|
|
190
|
+
end
|
|
191
|
+
key
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
# Peer directory
|
|
196
|
+
def peer_dir
|
|
197
|
+
# Yes, this is strange. It's to cope with needed to have two mailers at once for testing, which never would normally happen.
|
|
198
|
+
# In other words, global variables are bad and Haskell is right.
|
|
199
|
+
unless @peer_dir
|
|
200
|
+
@peer_dir = $CONFIG + "/.peers/"
|
|
201
|
+
end
|
|
202
|
+
@peer_dir
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
# Load previous peers
|
|
206
|
+
def load_peers
|
|
207
|
+
peer_file_names = files peer_dir
|
|
208
|
+
peer_file_names.each do |p|
|
|
209
|
+
load_peer p
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
# Load a peer
|
|
214
|
+
def load_peer peer_file_name
|
|
215
|
+
peer = Peer.load ".peers/" + File.basename(peer_file_name)
|
|
216
|
+
@mailbox.add_peer peer
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
end
|
|
220
|
+
end
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# Copyright 2010 John Morrice
|
|
2
|
+
|
|
3
|
+
# This file is part of CARPS.
|
|
4
|
+
|
|
5
|
+
# CARPS is free software: you can redistribute it and/or modify
|
|
6
|
+
# it under the terms of the GNU General Public License as published by
|
|
7
|
+
# the Free Software Foundation, either version 3 of the License, or
|
|
8
|
+
# (at your option) any later version.
|
|
9
|
+
|
|
10
|
+
# CARPS is distributed in the hope that it will be useful,
|
|
11
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
# GNU General Public License for more details.
|
|
14
|
+
|
|
15
|
+
# You should have received a copy of the GNU General Public License
|
|
16
|
+
# along with CARPS. If not, see <http://www.gnu.org/licenses/>.
|
|
17
|
+
|
|
18
|
+
require "carps/util"
|
|
19
|
+
|
|
20
|
+
require "carps/ui"
|
|
21
|
+
|
|
22
|
+
require "carps/protocol/keyword"
|
|
23
|
+
|
|
24
|
+
require "openssl"
|
|
25
|
+
|
|
26
|
+
require "yaml"
|
|
27
|
+
|
|
28
|
+
module CARPS
|
|
29
|
+
|
|
30
|
+
# Clean the end of an email
|
|
31
|
+
#
|
|
32
|
+
# Strip the last end marker and any text after it
|
|
33
|
+
def clean_end blob
|
|
34
|
+
rb = blob.reverse
|
|
35
|
+
before, after = rb.split K.end.reverse, 2
|
|
36
|
+
if after
|
|
37
|
+
return after.reverse
|
|
38
|
+
end
|
|
39
|
+
nil
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Fetch security information from an email
|
|
43
|
+
def security_info blob
|
|
44
|
+
blob = clean_end blob
|
|
45
|
+
sig = nil
|
|
46
|
+
begin
|
|
47
|
+
sig, blob = find K.sig, blob
|
|
48
|
+
rescue
|
|
49
|
+
UI::warn "Message signature was malformed", blob
|
|
50
|
+
return nil
|
|
51
|
+
end
|
|
52
|
+
# If the digest is the hash of the message and the signature matches the digest then all is well
|
|
53
|
+
dig = Digest::MD5.digest blob
|
|
54
|
+
[[sig, dig], blob]
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Peers
|
|
58
|
+
class Peer < UserConfig
|
|
59
|
+
|
|
60
|
+
# Extend protocol for signed data
|
|
61
|
+
protoval :sig
|
|
62
|
+
|
|
63
|
+
# Create a new peer
|
|
64
|
+
def initialize addr
|
|
65
|
+
@addr = addr
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def addr
|
|
69
|
+
@addr
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Tell this peer its key
|
|
73
|
+
def your_key key
|
|
74
|
+
@peer_key = key
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Perform a verification on an email
|
|
78
|
+
def verify mail
|
|
79
|
+
sig, dig = mail.crypt
|
|
80
|
+
begin
|
|
81
|
+
pass = @peer_key.sysverify dig, sig
|
|
82
|
+
rescue OpenSSL::PKey::DSAError => e
|
|
83
|
+
UI::warn "Someone sent you an invalid signature: #{e.message}"
|
|
84
|
+
return false
|
|
85
|
+
end
|
|
86
|
+
if pass
|
|
87
|
+
return true
|
|
88
|
+
else
|
|
89
|
+
UI::warn "Someone has attempted to spoof an email from #{mail.from}", mail.to_s
|
|
90
|
+
return false
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Save as YAML file in .peers
|
|
95
|
+
def save
|
|
96
|
+
y = emit.to_yaml
|
|
97
|
+
begin
|
|
98
|
+
write_file_in ".peers/", y
|
|
99
|
+
rescue StandardError => e
|
|
100
|
+
UI::warn "Could not save Peer in .peers/"
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
protected
|
|
105
|
+
|
|
106
|
+
# Emit this peer as a yaml document
|
|
107
|
+
def emit
|
|
108
|
+
{"addr" => @addr, "key" => @peer_key.to_pem}
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def parse_yaml conf
|
|
112
|
+
key_pem = read_conf conf, "key"
|
|
113
|
+
@addr = read_conf conf, "addr"
|
|
114
|
+
[key_pem]
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def load_resources key_pem
|
|
118
|
+
@peer_key = OpenSSL::PKey::DSA.new key_pem
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
end
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# Copyright 2010 John Morrice
|
|
2
|
+
|
|
3
|
+
# This file is part of CARPS.
|
|
4
|
+
|
|
5
|
+
# CARPS is free software: you can redistribute it and/or modify
|
|
6
|
+
# it under the terms of the GNU General Public License as published by
|
|
7
|
+
# the Free Software Foundation, either version 3 of the License, or
|
|
8
|
+
# (at your option) any later version.
|
|
9
|
+
|
|
10
|
+
# CARPS is distributed in the hope that it will be useful,
|
|
11
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
# GNU General Public License for more details.
|
|
14
|
+
|
|
15
|
+
# You should have received a copy of the GNU General Public License
|
|
16
|
+
# along with CARPS. If not, see <http://www.gnu.org/licenses/>.
|
|
17
|
+
|
|
18
|
+
require "carps/protocol/message"
|
|
19
|
+
require "carps/protocol/keyword"
|
|
20
|
+
|
|
21
|
+
require "openssl"
|
|
22
|
+
|
|
23
|
+
module CARPS
|
|
24
|
+
|
|
25
|
+
# Transmit public keys over email
|
|
26
|
+
class PublicKey < Message
|
|
27
|
+
|
|
28
|
+
# Extend the protocol for public_keys
|
|
29
|
+
protoval "key"
|
|
30
|
+
|
|
31
|
+
# Create a new handshake
|
|
32
|
+
def initialize public_key
|
|
33
|
+
@public_key = public_key
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Parse from text
|
|
37
|
+
def PublicKey.parse blob
|
|
38
|
+
key, blob = find K.key, blob
|
|
39
|
+
pkey = OpenSSL::PKey
|
|
40
|
+
begin
|
|
41
|
+
key = pkey::DSA.new key
|
|
42
|
+
return [PublicKey.new(key), blob]
|
|
43
|
+
rescue pkey::DSAError
|
|
44
|
+
raise Expected, "Public key"
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Emit the handshake as text
|
|
49
|
+
def emit
|
|
50
|
+
V.key @public_key.to_pem
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Share the public key
|
|
54
|
+
def key
|
|
55
|
+
@public_key
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
end
|
data/lib/carps/crypt.rb
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Copyright 2010 John Morrice
|
|
2
|
+
|
|
3
|
+
# This file is part of CARPS.
|
|
4
|
+
|
|
5
|
+
# CARPS is free software: you can redistribute it and/or modify
|
|
6
|
+
# it under the terms of the GNU General Public License as published by
|
|
7
|
+
# the Free Software Foundation, either version 3 of the License, or
|
|
8
|
+
# (at your option) any later version.
|
|
9
|
+
|
|
10
|
+
# CARPS is distributed in the hope that it will be useful,
|
|
11
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
# GNU General Public License for more details.
|
|
14
|
+
|
|
15
|
+
# You should have received a copy of the GNU General Public License
|
|
16
|
+
# along with CARPS. If not, see <http://www.gnu.org/licenses/>.
|
|
17
|
+
|
|
18
|
+
require "carps/crypt/accept_handshake"
|
|
19
|
+
require "carps/crypt/default_messages"
|
|
20
|
+
require "carps/crypt/handshake"
|
|
21
|
+
require "carps/crypt/mailbox"
|
|
22
|
+
require "carps/crypt/mailer"
|
|
23
|
+
require "carps/crypt/public_key"
|