self_crypto 0.0.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.
- 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
|