self_crypto 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/Rakefile +19 -0
- data/ext/self_crypto/account.c +292 -0
- data/ext/self_crypto/extconf.rb +15 -0
- data/ext/self_crypto/omemo.c +170 -0
- data/ext/self_crypto/pk.c +15 -0
- data/ext/self_crypto/pk_decryption.c +129 -0
- data/ext/self_crypto/pk_encryption.c +93 -0
- data/ext/self_crypto/pk_signing.c +102 -0
- data/ext/self_crypto/sas.c +190 -0
- data/ext/self_crypto/self_crypto.c +68 -0
- data/ext/self_crypto/self_crypto.h +15 -0
- data/ext/self_crypto/session.c +363 -0
- data/ext/self_crypto/utility.c +143 -0
- data/lib/self_crypto.rb +14 -0
- data/lib/self_crypto/account.rb +30 -0
- data/lib/self_crypto/group_message.rb +34 -0
- data/lib/self_crypto/group_session.rb +8 -0
- data/lib/self_crypto/message.rb +6 -0
- data/lib/self_crypto/olm_error.rb +70 -0
- data/lib/self_crypto/olm_message.rb +25 -0
- data/lib/self_crypto/pre_key_message.rb +6 -0
- data/lib/self_crypto/sas.rb +28 -0
- data/lib/self_crypto/sas_data.rb +71 -0
- data/lib/self_crypto/session.rb +16 -0
- data/lib/self_crypto/utility.rb +7 -0
- data/lib/self_crypto/version.rb +5 -0
- data/test/examples/test_bob_no_answer.rb +62 -0
- data/test/examples/test_exchange.rb +60 -0
- data/test/spec/test_account.rb +100 -0
- data/test/unit/test_account_methods.rb +64 -0
- metadata +134 -0
data/lib/self_crypto.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'self_crypto/version'
|
2
|
+
require 'self_crypto/self_crypto'
|
3
|
+
require 'self_crypto/account'
|
4
|
+
require 'self_crypto/session'
|
5
|
+
require 'self_crypto/group_session'
|
6
|
+
require 'self_crypto/olm_message'
|
7
|
+
require 'self_crypto/message'
|
8
|
+
require 'self_crypto/group_message'
|
9
|
+
require 'self_crypto/pre_key_message'
|
10
|
+
require 'self_crypto/sas'
|
11
|
+
require 'self_crypto/utility'
|
12
|
+
|
13
|
+
module SelfCrypto
|
14
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'base64'
|
2
|
+
|
3
|
+
module SelfCrypto
|
4
|
+
|
5
|
+
class Account
|
6
|
+
|
7
|
+
# @param pickle [String] pickled state
|
8
|
+
# @param password [String] password used to encrypt pickled state
|
9
|
+
# @return [Account]
|
10
|
+
def self.from_pickle(pickle, password="")
|
11
|
+
Account.new(pickle: pickle, password: password)
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.from_seed(seed)
|
15
|
+
Account.new(seed: Base64.decode64(seed))
|
16
|
+
end
|
17
|
+
|
18
|
+
def gen_otk(number=1)
|
19
|
+
generate_one_time_keys(number)
|
20
|
+
end
|
21
|
+
|
22
|
+
alias_method :ik, :identity_keys
|
23
|
+
alias_method :otk, :one_time_keys
|
24
|
+
alias_method :mark_otk, :mark_keys_as_published
|
25
|
+
alias_method :max_otk, :max_number_of_one_time_keys
|
26
|
+
alias_method :update_otk, :remove_one_time_keys
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'base64'
|
2
|
+
|
3
|
+
module SelfCrypto
|
4
|
+
|
5
|
+
class GroupMessage
|
6
|
+
|
7
|
+
# @param msg [String] base64 or bytes
|
8
|
+
def initialize(msg)
|
9
|
+
@value = msg
|
10
|
+
@data = JSON.parse(msg)
|
11
|
+
end
|
12
|
+
|
13
|
+
# @return [String] bytes
|
14
|
+
def to_bytes
|
15
|
+
Base64.decode64(value)
|
16
|
+
end
|
17
|
+
|
18
|
+
# @return [String] base64
|
19
|
+
def to_s
|
20
|
+
@value.dup
|
21
|
+
end
|
22
|
+
|
23
|
+
def get_message(identity)
|
24
|
+
h = @data['recipients'][identity]
|
25
|
+
if h['mtype'] == 0
|
26
|
+
PreKeyMessage.new(h['ciphertext'])
|
27
|
+
else
|
28
|
+
Message.new(h['ciphertext'])
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
module SelfCrypto
|
2
|
+
|
3
|
+
module OlmError
|
4
|
+
|
5
|
+
class SUCCESS < StandardError
|
6
|
+
end
|
7
|
+
|
8
|
+
class NOT_ENOUGH_RANDOM < StandardError
|
9
|
+
end
|
10
|
+
|
11
|
+
class OUTPUT_BUFFER_TOO_SMALL < StandardError
|
12
|
+
end
|
13
|
+
|
14
|
+
class BAD_MESSAGE_VERSION < StandardError
|
15
|
+
end
|
16
|
+
|
17
|
+
class BAD_MESSAGE_FORMAT < StandardError
|
18
|
+
end
|
19
|
+
|
20
|
+
class BAD_MESSAGE_MAC < StandardError
|
21
|
+
end
|
22
|
+
|
23
|
+
class BAD_MESSAGE_KEY_ID < StandardError
|
24
|
+
end
|
25
|
+
|
26
|
+
class INVALID_BASE64 < StandardError
|
27
|
+
end
|
28
|
+
|
29
|
+
class BAD_ACCOUNT_KEY < StandardError
|
30
|
+
end
|
31
|
+
|
32
|
+
class UNKNOWN_PICKLE_VERSION < StandardError
|
33
|
+
end
|
34
|
+
|
35
|
+
class CORRUPTED_PICKLE < StandardError
|
36
|
+
end
|
37
|
+
|
38
|
+
class BAD_SESSION_KEY < StandardError
|
39
|
+
end
|
40
|
+
|
41
|
+
class UNKNOWN_MESSAGE_INDEX < StandardError
|
42
|
+
end
|
43
|
+
|
44
|
+
class BAD_LEGACY_ACCOUNT_PICKLE < StandardError
|
45
|
+
end
|
46
|
+
|
47
|
+
class BAD_SIGNATURE < StandardError
|
48
|
+
end
|
49
|
+
|
50
|
+
class OLM_INPUT_BUFFER_TOO_SMALL < StandardError
|
51
|
+
end
|
52
|
+
|
53
|
+
class UnknownError < StandardError
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.from_string(str)
|
57
|
+
begin
|
58
|
+
OlmError.const_get(str)
|
59
|
+
rescue NameError
|
60
|
+
UnknownError.new str
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def self.raise_from_string(str)
|
65
|
+
raise from_string(str)
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'base64'
|
2
|
+
|
3
|
+
module SelfCrypto
|
4
|
+
|
5
|
+
class OlmMessage
|
6
|
+
|
7
|
+
# @param msg [String] base64 or bytes
|
8
|
+
def initialize(msg)
|
9
|
+
raise "abstract class" if self.class == OlmMessage
|
10
|
+
@value = msg
|
11
|
+
end
|
12
|
+
|
13
|
+
# @return [String] bytes
|
14
|
+
def to_bytes
|
15
|
+
Base64.decode64(value)
|
16
|
+
end
|
17
|
+
|
18
|
+
# @return [String] base64
|
19
|
+
def to_s
|
20
|
+
@value.dup
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require_relative './sas_data'
|
2
|
+
|
3
|
+
class SelfCrypto::SAS
|
4
|
+
METHODS = %i[decimal emoji]
|
5
|
+
|
6
|
+
def generate(method, info)
|
7
|
+
method = method.to_sym
|
8
|
+
raise ArgumentError, "Unknown SAS method: #{method}" unless METHODS.include? method
|
9
|
+
|
10
|
+
send method, info
|
11
|
+
end
|
12
|
+
|
13
|
+
protected
|
14
|
+
|
15
|
+
def decimal(info)
|
16
|
+
bytes = generate_bytes(5, info)
|
17
|
+
bits = bytes.unpack1('B39')
|
18
|
+
grouped = bits.chars.each_slice(13).map &:join
|
19
|
+
grouped.map {|s| s.to_i(2) + 1000}
|
20
|
+
end
|
21
|
+
|
22
|
+
def emoji(info)
|
23
|
+
bytes = generate_bytes(6, info)
|
24
|
+
bits = bytes.unpack1('B42')
|
25
|
+
grouped = bits.chars.each_slice(6).map &:join
|
26
|
+
grouped.map {|s| EMOJI_TABLE[s.to_i(2)]}.join
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
class SelfCrypto::SAS
|
5
|
+
EMOJI_TABLE = {
|
6
|
+
0 => '🐶',
|
7
|
+
1 => '🐱',
|
8
|
+
2 => '🦁',
|
9
|
+
3 => '🐎',
|
10
|
+
4 => '🦄',
|
11
|
+
5 => '🐷',
|
12
|
+
6 => '🐘',
|
13
|
+
7 => '🐰',
|
14
|
+
8 => '🐼',
|
15
|
+
9 => '🐓',
|
16
|
+
10 => '🐧',
|
17
|
+
11 => '🐢',
|
18
|
+
12 => '🐟',
|
19
|
+
13 => '🐙',
|
20
|
+
14 => '🦋',
|
21
|
+
15 => '🌷',
|
22
|
+
16 => '🌳',
|
23
|
+
17 => '🌵',
|
24
|
+
18 => '🍄',
|
25
|
+
19 => '🌏',
|
26
|
+
20 => '🌙',
|
27
|
+
21 => '☁',
|
28
|
+
22 => '🔥',
|
29
|
+
23 => '🍌',
|
30
|
+
24 => '🍎',
|
31
|
+
25 => '🍓',
|
32
|
+
26 => '🌽',
|
33
|
+
27 => '🍕',
|
34
|
+
28 => '🎂',
|
35
|
+
29 => '❤',
|
36
|
+
30 => '😀',
|
37
|
+
31 => '🤖',
|
38
|
+
32 => '🎩',
|
39
|
+
33 => '👓',
|
40
|
+
34 => '🔧',
|
41
|
+
35 => '🎅',
|
42
|
+
36 => '👍',
|
43
|
+
37 => '☂',
|
44
|
+
38 => '⌛',
|
45
|
+
39 => '⏰',
|
46
|
+
40 => '🎁',
|
47
|
+
41 => '💡',
|
48
|
+
42 => '📕',
|
49
|
+
43 => '✏',
|
50
|
+
44 => '📎',
|
51
|
+
45 => '✂',
|
52
|
+
46 => '🔒',
|
53
|
+
47 => '🔑',
|
54
|
+
48 => '🔨',
|
55
|
+
49 => '☎',
|
56
|
+
50 => '🏁',
|
57
|
+
51 => '🚂',
|
58
|
+
52 => '🚲',
|
59
|
+
53 => '✈',
|
60
|
+
54 => '🚀',
|
61
|
+
55 => '🏆',
|
62
|
+
56 => '⚽',
|
63
|
+
57 => '🎸',
|
64
|
+
58 => '🎺',
|
65
|
+
59 => '🔔',
|
66
|
+
60 => '⚓',
|
67
|
+
61 => '🎧',
|
68
|
+
62 => '📁',
|
69
|
+
63 => '📌'
|
70
|
+
}
|
71
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module SelfCrypto
|
2
|
+
|
3
|
+
class Session
|
4
|
+
|
5
|
+
# @param pickle [String] pickled state
|
6
|
+
# @param password [String] password used to encrypt pickled state
|
7
|
+
# @return [Session]
|
8
|
+
def self.from_pickle(pickle, password="")
|
9
|
+
Session.new(pickle, password)
|
10
|
+
end
|
11
|
+
|
12
|
+
alias_method :has_received?, :has_received_message
|
13
|
+
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'minitest/autorun'
|
2
|
+
require 'self_crypto'
|
3
|
+
|
4
|
+
class TestExchange < Minitest::Test
|
5
|
+
|
6
|
+
include SelfCrypto
|
7
|
+
|
8
|
+
# Alice -> Bob
|
9
|
+
# Alice -> Bob
|
10
|
+
#
|
11
|
+
def test_bob_no_answer
|
12
|
+
|
13
|
+
alice = Account.new
|
14
|
+
bob = Account.new
|
15
|
+
|
16
|
+
# Alice wants to send a message to Bob
|
17
|
+
alice_msg = "hi bob"
|
18
|
+
|
19
|
+
# Bob generates a one-time-key
|
20
|
+
bob.gen_otk
|
21
|
+
|
22
|
+
# Alice must have Bob's identity and one-time-key to make a session
|
23
|
+
alice_session = alice.outbound_session(bob.ik['curve25519'], bob.otk['curve25519'].values.first)
|
24
|
+
|
25
|
+
# Bob marks all one-time-keys as published
|
26
|
+
bob.mark_otk
|
27
|
+
|
28
|
+
# Alice can encrypt
|
29
|
+
encrypted = alice_session.encrypt(alice_msg)
|
30
|
+
assert_instance_of PreKeyMessage, encrypted
|
31
|
+
|
32
|
+
# Bob can create a session from this first message
|
33
|
+
bob_session = bob.inbound_session(encrypted)
|
34
|
+
|
35
|
+
# Bob can now update his list of marked otk (since he knows one has been used)
|
36
|
+
bob.update_otk(bob_session)
|
37
|
+
|
38
|
+
# Bob can decrypt Alice's message
|
39
|
+
bob_msg = bob_session.decrypt(encrypted)
|
40
|
+
|
41
|
+
assert_equal alice_msg, bob_msg
|
42
|
+
|
43
|
+
# At this point Bob has received but Alice hasn't
|
44
|
+
assert bob_session.has_received?
|
45
|
+
refute alice_session.has_received?
|
46
|
+
|
47
|
+
###
|
48
|
+
|
49
|
+
# Alice sends another message before reply from Bob
|
50
|
+
alice_msg = "BOB!"
|
51
|
+
|
52
|
+
encrypted = alice_session.encrypt(alice_msg)
|
53
|
+
assert_instance_of PreKeyMessage, encrypted
|
54
|
+
|
55
|
+
# Bob needs to check if this is the same session or a new one
|
56
|
+
same_session = bob_session.will_receive? encrypted
|
57
|
+
|
58
|
+
assert same_session
|
59
|
+
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'minitest/autorun'
|
2
|
+
require 'self_crypto'
|
3
|
+
|
4
|
+
class TestExchange < Minitest::Test
|
5
|
+
|
6
|
+
include SelfCrypto
|
7
|
+
|
8
|
+
# Alice -> Bob
|
9
|
+
# Alice <- Bob
|
10
|
+
def test_exchange
|
11
|
+
|
12
|
+
alice = Account.new
|
13
|
+
bob = Account.new
|
14
|
+
|
15
|
+
# Alice wants to send a message to Bob
|
16
|
+
alice_msg = "hi bob"
|
17
|
+
|
18
|
+
# Bob generates a one-time-key
|
19
|
+
bob.gen_otk
|
20
|
+
|
21
|
+
# Alice must have Bob's identity and one-time-key to make a session
|
22
|
+
alice_session = alice.outbound_session(bob.ik['curve25519'], bob.otk['curve25519'].values.first)
|
23
|
+
|
24
|
+
# Bob marks all one-time-keys as published
|
25
|
+
bob.mark_otk
|
26
|
+
|
27
|
+
# Alice can encrypt
|
28
|
+
encrypted = alice_session.encrypt(alice_msg)
|
29
|
+
assert_instance_of PreKeyMessage, encrypted
|
30
|
+
|
31
|
+
# Bob can create a session from this first message
|
32
|
+
bob_session = bob.inbound_session(encrypted)
|
33
|
+
|
34
|
+
# Bob can now update his list of marked otk (since he knows one has been used)
|
35
|
+
bob.update_otk(bob_session)
|
36
|
+
|
37
|
+
# Bob can decrypt Alice's message
|
38
|
+
bob_msg = bob_session.decrypt(encrypted)
|
39
|
+
|
40
|
+
assert_equal alice_msg, bob_msg
|
41
|
+
|
42
|
+
# At this point Bob has received but Alice hasn't
|
43
|
+
assert bob_session.has_received?
|
44
|
+
refute alice_session.has_received?
|
45
|
+
|
46
|
+
####
|
47
|
+
|
48
|
+
# Bob can send messages back to Alice
|
49
|
+
bob_msg = "hi alice"
|
50
|
+
|
51
|
+
encrypted = bob_session.encrypt(bob_msg)
|
52
|
+
assert_instance_of Message, encrypted
|
53
|
+
|
54
|
+
alice_msg = alice_session.decrypt(encrypted)
|
55
|
+
|
56
|
+
assert_equal alice_msg, bob_msg
|
57
|
+
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|