zwiebel 0.0.3 → 0.0.4
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 +4 -4
- data/lib/zwiebel/control.rb +2 -2
- data/lib/zwiebel/ed25519_certificate.rb +83 -0
- data/lib/zwiebel/errors.rb +28 -0
- data/lib/zwiebel/hidden_service/descriptor.rb +83 -0
- data/lib/zwiebel/hidden_service/inner_layer.rb +81 -0
- data/lib/zwiebel/hidden_service/introduction_point.rb +83 -0
- data/lib/zwiebel/hidden_service/outer_layer.rb +85 -0
- data/lib/zwiebel/hidden_service/v3.rb +49 -0
- data/lib/zwiebel/shake256.rb +360 -0
- data/lib/zwiebel/utilities.rb +68 -0
- data/lib/zwiebel/version.rb +1 -1
- data/lib/zwiebel.rb +58 -7
- metadata +12 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a8bf194bb0d78b479d0b88fe66545f06108cd9180b9d7be9644b5c517f8935cb
|
4
|
+
data.tar.gz: fcbe181944b14678dccae2b87eda1539d637980c39bf6157e7b51ce774e35666
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 56158c5fe35b4f6fdba0c69c9ee4f897d5cb37d942fc64d4f4b3c9676ddd22cb3b7cd6c5ad0f7914656f55274531e99e3ed6361dfe792fe2fe5dd824140a40ce
|
7
|
+
data.tar.gz: 8fa7d94d7c4d4f81454a5c1802ff7a87b056a65e7f74e9afbde49c9477e880b527dc830061525f7c63d0eb3943ea6d27d821bf9fde63ddef08e38d9642685584
|
data/lib/zwiebel/control.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# Copyright 2023, Kurt Meyerhofer
|
1
|
+
# Copyright 2023-2024, Kurt Meyerhofer
|
2
2
|
# This file is part of zwiebel.
|
3
3
|
|
4
4
|
# zwiebel is free software: you can redistribute it and/or modify it under the terms of
|
@@ -15,7 +15,7 @@
|
|
15
15
|
|
16
16
|
module Zwiebel
|
17
17
|
class Control
|
18
|
-
attr_accessor :cookie, :host, :port
|
18
|
+
attr_accessor :cookie, :host, :port, :socket
|
19
19
|
|
20
20
|
def initialize(host: "127.0.0.1", port: 9051, cookie: nil)
|
21
21
|
@host = host
|
@@ -0,0 +1,83 @@
|
|
1
|
+
# Copyright 2023-2024, Kurt Meyerhofer
|
2
|
+
# This file is part of zwiebel.
|
3
|
+
|
4
|
+
# zwiebel is free software: you can redistribute it and/or modify it under the terms of
|
5
|
+
# the GNU Lesser General Public License as published by the Free Software Foundation,
|
6
|
+
# either version 3 of the License, or (at your option) any later version.
|
7
|
+
|
8
|
+
# zwiebel is distributed in the hope that it will be useful, but WITHOUT ANY
|
9
|
+
# WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
10
|
+
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
|
11
|
+
# more details.
|
12
|
+
|
13
|
+
# You should have received a copy of the GNU Lesser General Public License along with zwiebel.
|
14
|
+
# If not, see <https://www.gnu.org/licenses/>.
|
15
|
+
|
16
|
+
module Zwiebel
|
17
|
+
class Ed25519Certificate
|
18
|
+
Extension = Struct.new(:data, :extension_type, :flags)
|
19
|
+
KEY_LENGTH = 32
|
20
|
+
HEADER_LENGTH = 40
|
21
|
+
SIGNATURE_LENGTH = 64
|
22
|
+
attr_accessor :cert_type, :descriptor_data, :extension_data_with_header, :extensions, :expiration_hours, :expires, :key, :signature, :version
|
23
|
+
|
24
|
+
def initialize(descriptor_data:)
|
25
|
+
@descriptor_data = descriptor_data
|
26
|
+
@extensions = []
|
27
|
+
unpack
|
28
|
+
end
|
29
|
+
|
30
|
+
def unpack
|
31
|
+
content = descriptor_data.gsub("-----BEGIN ED25519 CERT-----\n", "").gsub("\n-----END ED25519 CERT-----", "")
|
32
|
+
base64_decoded = Base64.decode64(content)
|
33
|
+
|
34
|
+
if base64_decoded.length < HEADER_LENGTH + SIGNATURE_LENGTH
|
35
|
+
raise ContentLengthError, "certificate should be at least #{HEADER_LENGTH + SIGNATURE_LENGTH} bytes"
|
36
|
+
end
|
37
|
+
@signature = base64_decoded.byteslice((base64_decoded.length - SIGNATURE_LENGTH)..-1)
|
38
|
+
index = 0
|
39
|
+
@version = base64_decoded.byteslice(index, 1).unpack1("C")
|
40
|
+
index += 1
|
41
|
+
@cert_type = base64_decoded.byteslice(index, 1).unpack1("C")
|
42
|
+
index += 1
|
43
|
+
@expiration_hours = base64_decoded.byteslice(index, 4).unpack1("L>") * 3600
|
44
|
+
index += 4
|
45
|
+
key_type = base64_decoded.byteslice(index, 1)
|
46
|
+
index += 1
|
47
|
+
@key = base64_decoded.byteslice(index, KEY_LENGTH)
|
48
|
+
index += KEY_LENGTH
|
49
|
+
extension_count = base64_decoded.byteslice(index, 1)
|
50
|
+
index += 1
|
51
|
+
@extension_data_with_header = base64_decoded.byteslice(index..-(SIGNATURE_LENGTH + 1))
|
52
|
+
@expires = Time.at(@expiration_hours).getutc
|
53
|
+
|
54
|
+
index = 0
|
55
|
+
extension_count.unpack1("C").times do
|
56
|
+
data_size = extension_data_with_header.byteslice(index, 2).unpack1("S>*")
|
57
|
+
index += 2
|
58
|
+
extension_type = extension_data_with_header.byteslice(index, 1).unpack1("C")
|
59
|
+
index += 1
|
60
|
+
flags = extension_data_with_header.byteslice(index, 1).unpack1("C")
|
61
|
+
index += 1
|
62
|
+
data = extension_data_with_header.byteslice(index, data_size)
|
63
|
+
index += data_size
|
64
|
+
extensions.push(Extension.new(data, extension_type, flags))
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def extension_data
|
69
|
+
extension_data_with_header.byteslice(4..-1)
|
70
|
+
end
|
71
|
+
|
72
|
+
def signing_key
|
73
|
+
k = extensions.select do |extension|
|
74
|
+
extension.extension_type == 4
|
75
|
+
end.first
|
76
|
+
k&.data
|
77
|
+
end
|
78
|
+
|
79
|
+
def expired?
|
80
|
+
expiration_hours < Time.now.to_i
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# Copyright 2023-2024, Kurt Meyerhofer
|
2
|
+
# This file is part of zwiebel.
|
3
|
+
|
4
|
+
# zwiebel is free software: you can redistribute it and/or modify it under the terms of
|
5
|
+
# the GNU Lesser General Public License as published by the Free Software Foundation,
|
6
|
+
# either version 3 of the License, or (at your option) any later version.
|
7
|
+
|
8
|
+
# zwiebel is distributed in the hope that it will be useful, but WITHOUT ANY
|
9
|
+
# WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
10
|
+
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
|
11
|
+
# more details.
|
12
|
+
|
13
|
+
# You should have received a copy of the GNU Lesser General Public License along with zwiebel.
|
14
|
+
# If not, see <https://www.gnu.org/licenses/>.
|
15
|
+
|
16
|
+
module Zwiebel
|
17
|
+
class ContentLengthError < StandardError
|
18
|
+
end
|
19
|
+
|
20
|
+
class DataError < StandardError
|
21
|
+
end
|
22
|
+
|
23
|
+
class FileReadError < StandardError
|
24
|
+
end
|
25
|
+
|
26
|
+
class InvalidAddressError < StandardError
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
# Copyright 2023 - 2024, Kurt Meyerhofer
|
2
|
+
# This file is part of zwiebel.
|
3
|
+
|
4
|
+
# zwiebel is free software: you can redistribute it and/or modify it under the terms of
|
5
|
+
# the GNU Lesser General Public License as published by the Free Software Foundation,
|
6
|
+
# either version 3 of the License, or (at your option) any later version.
|
7
|
+
|
8
|
+
# zwiebel is distributed in the hope that it will be useful, but WITHOUT ANY
|
9
|
+
# WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
10
|
+
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
|
11
|
+
# more details.
|
12
|
+
|
13
|
+
# You should have received a copy of the GNU Lesser General Public License along with zwiebel.
|
14
|
+
# If not, see <https://www.gnu.org/licenses/>.
|
15
|
+
|
16
|
+
module Zwiebel
|
17
|
+
module HiddenService
|
18
|
+
class Descriptor
|
19
|
+
FIELDS = %w(hs-descriptor descriptor-lifetime descriptor-signing-key-cert revision-counter superencrypted signature).freeze
|
20
|
+
attr_accessor :certificate, :string
|
21
|
+
|
22
|
+
def initialize(string:)
|
23
|
+
@string = string
|
24
|
+
parse
|
25
|
+
@certificate = Ed25519Certificate.new(descriptor_data: descriptor_signing_key_cert)
|
26
|
+
end
|
27
|
+
|
28
|
+
def parse
|
29
|
+
@hs_descriptor = {}
|
30
|
+
descriptor_current_field = nil
|
31
|
+
string.each_line do |line|
|
32
|
+
line_field = line.split(" ")[0]
|
33
|
+
if FIELDS.include?(line_field)
|
34
|
+
descriptor_current_field = line_field
|
35
|
+
|
36
|
+
if @hs_descriptor[descriptor_current_field].nil? && !line.split(" ")[1..-1].nil?
|
37
|
+
@hs_descriptor[descriptor_current_field] = line.split(" ")[1..-1].join(" ")
|
38
|
+
else
|
39
|
+
@hs_descriptor[descriptor_current_field] = ""
|
40
|
+
end
|
41
|
+
else
|
42
|
+
hs_descriptor_value = @hs_descriptor[descriptor_current_field]
|
43
|
+
|
44
|
+
if hs_descriptor_value.nil?
|
45
|
+
@hs_descriptor[descriptor_current_field] = line
|
46
|
+
else
|
47
|
+
@hs_descriptor[descriptor_current_field] = hs_descriptor_value + line
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def hs_descriptor
|
54
|
+
@hs_descriptor["hs-descriptor"]&.to_i
|
55
|
+
end
|
56
|
+
|
57
|
+
def descriptor_lifetime
|
58
|
+
@hs_descriptor["descriptor-lifetime"]&.to_i
|
59
|
+
end
|
60
|
+
|
61
|
+
def descriptor_signing_key_cert
|
62
|
+
@hs_descriptor["descriptor-signing-key-cert"]
|
63
|
+
end
|
64
|
+
|
65
|
+
def revision_counter
|
66
|
+
@hs_descriptor["revision-counter"]&.to_i
|
67
|
+
end
|
68
|
+
|
69
|
+
def superencrypted
|
70
|
+
@hs_descriptor["superencrypted"]
|
71
|
+
end
|
72
|
+
|
73
|
+
def signature
|
74
|
+
@hs_descriptor["signature"]
|
75
|
+
end
|
76
|
+
|
77
|
+
def subcredential(identity_public_key)
|
78
|
+
credential = OpenSSL::Digest.digest("SHA3-256", "credential" + identity_public_key)
|
79
|
+
OpenSSL::Digest.digest("SHA3-256", "subcredential" + credential + certificate.signing_key)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
# Copyright 2023 - 2024, Kurt Meyerhofer
|
2
|
+
# This file is part of zwiebel.
|
3
|
+
|
4
|
+
# zwiebel is free software: you can redistribute it and/or modify it under the terms of
|
5
|
+
# the GNU Lesser General Public License as published by the Free Software Foundation,
|
6
|
+
# either version 3 of the License, or (at your option) any later version.
|
7
|
+
|
8
|
+
# zwiebel is distributed in the hope that it will be useful, but WITHOUT ANY
|
9
|
+
# WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
10
|
+
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
|
11
|
+
# more details.
|
12
|
+
|
13
|
+
# You should have received a copy of the GNU Lesser General Public License along with zwiebel.
|
14
|
+
# If not, see <https://www.gnu.org/licenses/>.
|
15
|
+
|
16
|
+
module Zwiebel
|
17
|
+
module HiddenService
|
18
|
+
class InnerLayer
|
19
|
+
FIELDS = %w(create2-formats intro-auth-required single-onion-service introduction-point).freeze
|
20
|
+
INTRODUCTION_POINT_FIELDS = %w(onion-key auth-key enc-key enc-key-cert legacy-key legacy-key-cert).freeze
|
21
|
+
attr_accessor :decrypted_data, :introduction_points
|
22
|
+
|
23
|
+
def initialize(decrypted_data:)
|
24
|
+
@decrypted_data = decrypted_data
|
25
|
+
@introduction_points = []
|
26
|
+
parse
|
27
|
+
end
|
28
|
+
|
29
|
+
def parse
|
30
|
+
@inner_layer = {}
|
31
|
+
layer_current_field = nil
|
32
|
+
current_introduction_point_data = nil
|
33
|
+
decrypted_data.each_line do |line|
|
34
|
+
line_field = line.split(" ")[0]
|
35
|
+
|
36
|
+
if FIELDS.include?(line_field)
|
37
|
+
layer_current_field = line_field
|
38
|
+
if layer_current_field == "introduction-point"
|
39
|
+
introduction_points.push(IntroductionPoint.new(data: current_introduction_point_data)) unless current_introduction_point_data.nil?
|
40
|
+
current_introduction_point_data = { layer_current_field => line.split(" ")[1] }
|
41
|
+
else
|
42
|
+
if @inner_layer[layer_current_field].nil? && !line.split(" ")[1..-1].nil?
|
43
|
+
@inner_layer[layer_current_field] = line.split(" ")[1..-1].join(" ")
|
44
|
+
elsif !@inner_layer[layer_current_field].nil? && !line.split(" ")[1..-1].nil?
|
45
|
+
@inner_layer[layer_current_field] += line.split(" ")[1..-1].join(" ")
|
46
|
+
elsif layer_current_field == "single-onion-service"
|
47
|
+
@inner_layer[layer_current_field] = true
|
48
|
+
else
|
49
|
+
@inner_layer[layer_current_field] = ""
|
50
|
+
end
|
51
|
+
end
|
52
|
+
elsif INTRODUCTION_POINT_FIELDS.include?(line_field) || INTRODUCTION_POINT_FIELDS.include?(layer_current_field)
|
53
|
+
layer_current_field = line_field unless !INTRODUCTION_POINT_FIELDS.include?(line_field)
|
54
|
+
if current_introduction_point_data[layer_current_field].nil?
|
55
|
+
current_introduction_point_data[layer_current_field] = line.split(" ")[1..-1].join(" ")
|
56
|
+
else
|
57
|
+
current_introduction_point_data[layer_current_field] += line
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
introduction_points.push(IntroductionPoint.new(data: current_introduction_point_data)) unless current_introduction_point_data.nil?
|
62
|
+
end
|
63
|
+
|
64
|
+
def create2_formats
|
65
|
+
@inner_layer["create2-formats"].to_i
|
66
|
+
end
|
67
|
+
|
68
|
+
def intro_auth_required
|
69
|
+
@inner_layer["intro-auth-required"]
|
70
|
+
end
|
71
|
+
|
72
|
+
def single_onion_service?
|
73
|
+
!!@inner_layer["single-onion-service"]
|
74
|
+
end
|
75
|
+
|
76
|
+
def encrypted
|
77
|
+
@inner_layer["encrypted"]
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
# Copyright 2023 - 2024, Kurt Meyerhofer
|
2
|
+
# This file is part of zwiebel.
|
3
|
+
|
4
|
+
# zwiebel is free software: you can redistribute it and/or modify it under the terms of
|
5
|
+
# the GNU Lesser General Public License as published by the Free Software Foundation,
|
6
|
+
# either version 3 of the License, or (at your option) any later version.
|
7
|
+
|
8
|
+
# zwiebel is distributed in the hope that it will be useful, but WITHOUT ANY
|
9
|
+
# WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
10
|
+
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
|
11
|
+
# more details.
|
12
|
+
|
13
|
+
# You should have received a copy of the GNU Lesser General Public License along with zwiebel.
|
14
|
+
# If not, see <https://www.gnu.org/licenses/>.
|
15
|
+
|
16
|
+
module Zwiebel
|
17
|
+
module HiddenService
|
18
|
+
class IntroductionPoint
|
19
|
+
LinkSpecifier = Struct.new(:address, :fingerprint, :port)
|
20
|
+
attr_accessor :auth_key_ed25519, :link_specifiers, :enc_key_cert_ed25519
|
21
|
+
|
22
|
+
def initialize(data:)
|
23
|
+
@link_specifiers = []
|
24
|
+
@link_specifier = data["introduction-point"]
|
25
|
+
@onion_key = data["onion-key"]
|
26
|
+
@auth_key = data["auth-key"]
|
27
|
+
@enc_key = data["enc-key"]
|
28
|
+
@enc_key_cert = data["enc-key-cert"]
|
29
|
+
@legacy_key = data["legacy-key"]
|
30
|
+
@legacy_key_cert = data["legacy-key-cert"]
|
31
|
+
store_keys
|
32
|
+
decode_link_specifier
|
33
|
+
end
|
34
|
+
|
35
|
+
def onion_key
|
36
|
+
@onion_key&.gsub("ntor ", "")
|
37
|
+
end
|
38
|
+
|
39
|
+
def enc_key
|
40
|
+
@enc_key&.gsub("ntor ", "")
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def store_keys
|
46
|
+
@auth_key_ed25519 = Ed25519Certificate.new(descriptor_data: @auth_key) unless @auth_key.nil?
|
47
|
+
@enc_key_cert_ed25519 = Ed25519Certificate.new(descriptor_data: @enc_key_cert) unless @enc_key_cert.nil?
|
48
|
+
end
|
49
|
+
|
50
|
+
def decode_link_specifier
|
51
|
+
content = Base64.decode64(@link_specifier)
|
52
|
+
|
53
|
+
index = 0
|
54
|
+
count = content.byteslice(index, 1)
|
55
|
+
index += 1
|
56
|
+
|
57
|
+
count.unpack1("C").times do |x|
|
58
|
+
type = content.byteslice(index, 1).unpack1("C")
|
59
|
+
index += 1
|
60
|
+
value_size = content.byteslice(index, 1).unpack1("C")
|
61
|
+
index += 1
|
62
|
+
value = content.byteslice(index, value_size)
|
63
|
+
index += value_size
|
64
|
+
|
65
|
+
if type == 0
|
66
|
+
address = value.byteslice(0, 4).bytes.join(".")
|
67
|
+
port = value.byteslice(4, 2).unpack1("S>*")
|
68
|
+
@link_specifiers.push(LinkSpecifier.new(address, nil, port))
|
69
|
+
elsif type == 1
|
70
|
+
address = (0..14).step(2).map do |x|
|
71
|
+
sprintf("%04x", value.byteslice(x, x + 2).unpack1("S>*"))
|
72
|
+
end.join(":")
|
73
|
+
port = value.byteslice(16, 2).unpack1("S>*")
|
74
|
+
@link_specifiers.push(LinkSpecifier.new(address, nil, port))
|
75
|
+
else
|
76
|
+
# Type 2, 3 and above
|
77
|
+
@link_specifiers.push(LinkSpecifier.new(nil, value, nil))
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
# Copyright 2023 - 2024, Kurt Meyerhofer
|
2
|
+
# This file is part of zwiebel.
|
3
|
+
|
4
|
+
# zwiebel is free software: you can redistribute it and/or modify it under the terms of
|
5
|
+
# the GNU Lesser General Public License as published by the Free Software Foundation,
|
6
|
+
# either version 3 of the License, or (at your option) any later version.
|
7
|
+
|
8
|
+
# zwiebel is distributed in the hope that it will be useful, but WITHOUT ANY
|
9
|
+
# WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
10
|
+
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
|
11
|
+
# more details.
|
12
|
+
|
13
|
+
# You should have received a copy of the GNU Lesser General Public License along with zwiebel.
|
14
|
+
# If not, see <https://www.gnu.org/licenses/>.
|
15
|
+
|
16
|
+
module Zwiebel
|
17
|
+
module HiddenService
|
18
|
+
class OuterLayer
|
19
|
+
FIELDS = %w(desc-auth-type desc-auth-ephemeral-key auth-client encrypted).freeze
|
20
|
+
attr_accessor :decrypted_data
|
21
|
+
|
22
|
+
def initialize(decrypted_data:)
|
23
|
+
@decrypted_data = decrypted_data
|
24
|
+
parse
|
25
|
+
end
|
26
|
+
|
27
|
+
def parse
|
28
|
+
@outer_layer = {}
|
29
|
+
layer_current_field = nil
|
30
|
+
decrypted_data.gsub("\x00", "").each_line do |line|
|
31
|
+
line_field = line.split(" ")[0]
|
32
|
+
if FIELDS.include?(line_field)
|
33
|
+
layer_current_field = line_field
|
34
|
+
if layer_current_field == "auth-client"
|
35
|
+
if !@outer_layer[layer_current_field].nil?
|
36
|
+
_, client_id, iv, cookie = line.split(" ")
|
37
|
+
@outer_layer[layer_current_field].push({
|
38
|
+
client_id: client_id,
|
39
|
+
iv: iv,
|
40
|
+
cookie: cookie
|
41
|
+
})
|
42
|
+
else
|
43
|
+
_, client_id, iv, cookie = line.split(" ")
|
44
|
+
@outer_layer[layer_current_field] = [{
|
45
|
+
client_id: client_id,
|
46
|
+
iv: iv,
|
47
|
+
cookie: cookie
|
48
|
+
}]
|
49
|
+
end
|
50
|
+
elsif @outer_layer[layer_current_field].nil? && !line.split(" ")[1..-1].nil?
|
51
|
+
@outer_layer[layer_current_field] = line.split(" ")[1..-1].join(" ")
|
52
|
+
elsif !@outer_layer[layer_current_field].nil? && !line.split(" ")[1..-1].nil?
|
53
|
+
@outer_layer[layer_current_field] += line.split(" ")[1..-1].join(" ")
|
54
|
+
else
|
55
|
+
@outer_layer[layer_current_field] = ""
|
56
|
+
end
|
57
|
+
else
|
58
|
+
outer_layer_value = @outer_layer[layer_current_field]
|
59
|
+
if outer_layer_value.nil?
|
60
|
+
@outer_layer[layer_current_field] = line
|
61
|
+
else
|
62
|
+
@outer_layer[layer_current_field] = outer_layer_value + line
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def desc_auth_type
|
69
|
+
@outer_layer["desc-auth-type"]
|
70
|
+
end
|
71
|
+
|
72
|
+
def desc_auth_ephemeral_key
|
73
|
+
@outer_layer["desc-auth-ephemeral-key"]
|
74
|
+
end
|
75
|
+
|
76
|
+
def auth_clients
|
77
|
+
@outer_layer["auth-client"]
|
78
|
+
end
|
79
|
+
|
80
|
+
def encrypted
|
81
|
+
@outer_layer["encrypted"]
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# Copyright 2023 - 2024, Kurt Meyerhofer
|
2
|
+
# This file is part of zwiebel.
|
3
|
+
|
4
|
+
# zwiebel is free software: you can redistribute it and/or modify it under the terms of
|
5
|
+
# the GNU Lesser General Public License as published by the Free Software Foundation,
|
6
|
+
# either version 3 of the License, or (at your option) any later version.
|
7
|
+
|
8
|
+
# zwiebel is distributed in the hope that it will be useful, but WITHOUT ANY
|
9
|
+
# WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
10
|
+
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
|
11
|
+
# more details.
|
12
|
+
|
13
|
+
# You should have received a copy of the GNU Lesser General Public License along with zwiebel.
|
14
|
+
# If not, see <https://www.gnu.org/licenses/>.
|
15
|
+
|
16
|
+
module Zwiebel
|
17
|
+
module HiddenService
|
18
|
+
class V3
|
19
|
+
attr_accessor :descriptor, :inner_layer, :onion_address, :outer_layer
|
20
|
+
|
21
|
+
def initialize(descriptor_string:, onion_address:)
|
22
|
+
@descriptor = Descriptor.new(string: descriptor_string)
|
23
|
+
@onion_address = onion_address
|
24
|
+
end
|
25
|
+
|
26
|
+
def decrypt
|
27
|
+
blinded_key = descriptor.certificate.signing_key
|
28
|
+
identity_public_key = Zwiebel.v3_address_pubkey(onion_address)
|
29
|
+
subcredential = descriptor.subcredential(identity_public_key)
|
30
|
+
decrypted_outer_layer = Utilities.decrypt_layer(
|
31
|
+
encrypted_data: descriptor.superencrypted,
|
32
|
+
constant: "hsdir-superencrypted-data",
|
33
|
+
revision_counter: descriptor.revision_counter,
|
34
|
+
subcredential: subcredential,
|
35
|
+
blinded_key: blinded_key
|
36
|
+
)
|
37
|
+
@outer_layer = OuterLayer.new(decrypted_data: decrypted_outer_layer)
|
38
|
+
decrypted_inner_layer = Utilities.decrypt_layer(
|
39
|
+
encrypted_data: outer_layer.encrypted,
|
40
|
+
constant: "hsdir-encrypted-data",
|
41
|
+
revision_counter: descriptor.revision_counter,
|
42
|
+
subcredential: subcredential,
|
43
|
+
blinded_key: blinded_key
|
44
|
+
)
|
45
|
+
@inner_layer = InnerLayer.new(decrypted_data: decrypted_inner_layer)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,360 @@
|
|
1
|
+
# Copyright 2023-2024, Kurt Meyerhofer
|
2
|
+
# This file is part of zwiebel.
|
3
|
+
|
4
|
+
# zwiebel is free software: you can redistribute it and/or modify it under the terms of
|
5
|
+
# the GNU Lesser General Public License as published by the Free Software Foundation,
|
6
|
+
# either version 3 of the License, or (at your option) any later version.
|
7
|
+
|
8
|
+
# zwiebel is distributed in the hope that it will be useful, but WITHOUT ANY
|
9
|
+
# WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
10
|
+
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
|
11
|
+
# more details.
|
12
|
+
|
13
|
+
# You should have received a copy of the GNU Lesser General Public License along with zwiebel.
|
14
|
+
# If not, see <https://www.gnu.org/licenses/>.
|
15
|
+
|
16
|
+
module Zwiebel
|
17
|
+
class Shake256
|
18
|
+
HEX_CHARS = '0123456789abcdef'.freeze
|
19
|
+
PADDING = [31, 7936, 2031616, 520093696].freeze
|
20
|
+
SHIFT = [0, 8, 16, 24].freeze
|
21
|
+
RC = [
|
22
|
+
1, 0, 32898, 0, 32906, 2147483648, 2147516416, 2147483648, 32907, 0, 2147483649,
|
23
|
+
0, 2147516545, 2147483648, 32777, 2147483648, 138, 0, 136, 0, 2147516425, 0,
|
24
|
+
2147483658, 0, 2147516555, 0, 139, 2147483648, 32905, 2147483648, 32771,
|
25
|
+
2147483648, 32770, 2147483648, 128, 2147483648, 32778, 0, 2147483658, 2147483648,
|
26
|
+
2147516545, 2147483648, 32896, 2147483648, 2147483649, 0, 2147516424, 2147483648
|
27
|
+
].freeze
|
28
|
+
|
29
|
+
def initialize(bit_length:, message_type: "string")
|
30
|
+
@bit_length = bit_length
|
31
|
+
@message_type = message_type
|
32
|
+
@finalized = false
|
33
|
+
@reset = true
|
34
|
+
@block = 0
|
35
|
+
@start = 0
|
36
|
+
@block_count = (1600 - (256 << 1)) >> 5
|
37
|
+
@byte_count = @block_count << 2
|
38
|
+
@output_blocks = @bit_length >> 5
|
39
|
+
@extra_bytes = (@bit_length & 31) >> 3
|
40
|
+
@s = Array.new(50, 0)
|
41
|
+
@blocks = Array.new(@block_count, 0)
|
42
|
+
end
|
43
|
+
|
44
|
+
def hexdigest(data)
|
45
|
+
return if data.nil?
|
46
|
+
if @message_type == "string"
|
47
|
+
data_codes = data.bytes
|
48
|
+
length = data.length
|
49
|
+
else
|
50
|
+
data_codes = [data].pack("H*").bytes
|
51
|
+
length = data_codes.length
|
52
|
+
end
|
53
|
+
|
54
|
+
index = 0
|
55
|
+
i = nil
|
56
|
+
while index < length
|
57
|
+
if @reset
|
58
|
+
@reset = false
|
59
|
+
@blocks[0] = @block
|
60
|
+
1.upto(@block_count + 1) do |x|
|
61
|
+
@blocks[x] = 0
|
62
|
+
end
|
63
|
+
end
|
64
|
+
i = @start
|
65
|
+
while index < length && i < @byte_count
|
66
|
+
code = data_codes[index]
|
67
|
+
if @message_type == "string"
|
68
|
+
if code < 0x80
|
69
|
+
@blocks[i >> 2] |= code << SHIFT[i & 3]
|
70
|
+
i += 1
|
71
|
+
elsif code < 0x800
|
72
|
+
@blocks[i >> 2] |= (0xc0 | (code >> 6)) << SHIFT[i & 3]
|
73
|
+
i += 1
|
74
|
+
@blocks[i >> 2] |= (0x80 | (code & 0x3f)) << SHIFT[i & 3]
|
75
|
+
i += 1
|
76
|
+
elsif code < 0xd800 || code >= 0xe000
|
77
|
+
@blocks[i >> 2] |= (0xe0 | (code >> 12)) << SHIFT[i & 3]
|
78
|
+
i += 1
|
79
|
+
@blocks[i >> 2] |= (0x80 | ((code >> 6) & 0x3f)) << SHIFT[i & 3]
|
80
|
+
i += 1
|
81
|
+
@blocks[i >> 2] |= (0x80 | (code & 0x3f)) << SHIFT[i & 3]
|
82
|
+
i += 1
|
83
|
+
else
|
84
|
+
code = 0x10000 + (((code & 0x3ff) << 10) | (data_codes[index += 1] & 0x3ff))
|
85
|
+
@blocks[i >> 2] |= (0xf0 | (code >> 18)) << SHIFT[i & 3]
|
86
|
+
i += 1
|
87
|
+
@blocks[i >> 2] |= (0x80 | ((code >> 12) & 0x3f)) << SHIFT[i & 3]
|
88
|
+
i += 1
|
89
|
+
@blocks[i >> 2] |= (0x80 | ((code >> 6) & 0x3f)) << SHIFT[i & 3]
|
90
|
+
i += 1
|
91
|
+
@blocks[i >> 2] |= (0x80 | (code & 0x3f)) << SHIFT[i & 3]
|
92
|
+
i += 1
|
93
|
+
end
|
94
|
+
else
|
95
|
+
@blocks[i >> 2] |= code << SHIFT[i & 3]
|
96
|
+
i += 1
|
97
|
+
end
|
98
|
+
index += 1
|
99
|
+
end
|
100
|
+
|
101
|
+
@last_byte_index = i
|
102
|
+
|
103
|
+
if i >= @byte_count
|
104
|
+
@start = i - @byte_count
|
105
|
+
@block = @blocks[@block_count]
|
106
|
+
0.upto(@block_count - 1) do |x|
|
107
|
+
@s[x] ^= @blocks[x]
|
108
|
+
end
|
109
|
+
keccak(@s)
|
110
|
+
@reset = true
|
111
|
+
else
|
112
|
+
@start = i
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
finalize
|
117
|
+
|
118
|
+
hex = ""
|
119
|
+
i = 0
|
120
|
+
j = 0
|
121
|
+
s = @s
|
122
|
+
block = nil
|
123
|
+
while j < @output_blocks
|
124
|
+
while i < @block_count && j < @output_blocks
|
125
|
+
block = s[i]
|
126
|
+
hex += HEX_CHARS[(block >> 4) & 0x0F] + HEX_CHARS[block & 0x0F] +
|
127
|
+
HEX_CHARS[(block >> 12) & 0x0F] + HEX_CHARS[(block >> 8) & 0x0F] +
|
128
|
+
HEX_CHARS[(block >> 20) & 0x0F] + HEX_CHARS[(block >> 16) & 0x0F] +
|
129
|
+
HEX_CHARS[(block >> 28) & 0x0F] + HEX_CHARS[(block >> 24) & 0x0F]
|
130
|
+
j += 1
|
131
|
+
i += 1
|
132
|
+
end
|
133
|
+
|
134
|
+
if j % @block_count == 0
|
135
|
+
s = s.dup
|
136
|
+
keccak(s)
|
137
|
+
i = 0
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
if @extra_bytes > 0
|
142
|
+
block = s[i]
|
143
|
+
hex += HEX_CHARS[(block >> 4) & 0x0F] + HEX_CHARS[block & 0x0F]
|
144
|
+
if @extra_bytes > 1
|
145
|
+
hex += HEX_CHARS[(block >> 12) & 0x0F] + HEX_CHARS[(block >> 8) & 0x0F]
|
146
|
+
end
|
147
|
+
if @extra_bytes > 2
|
148
|
+
hex += HEX_CHARS[(block >> 20) & 0x0F] + HEX_CHARS[(block >> 16) & 0x0F]
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
hex
|
153
|
+
end
|
154
|
+
|
155
|
+
private
|
156
|
+
|
157
|
+
def finalize
|
158
|
+
return if @finalized
|
159
|
+
|
160
|
+
@finalized = true
|
161
|
+
i = @last_byte_index.nil? ? 0 : @last_byte_index
|
162
|
+
@blocks[i >> 2] |= PADDING[i & 3]
|
163
|
+
if @last_byte_index == @byte_count
|
164
|
+
@blocks[0] = @blocks[@block_count]
|
165
|
+
1.upto(@block_count) do |x|
|
166
|
+
@blocks[x] = 0
|
167
|
+
end
|
168
|
+
end
|
169
|
+
@blocks[@block_count - 1] |= 0x80000000
|
170
|
+
0.upto(@block_count - 1) do |x|
|
171
|
+
@s[x] ^= @blocks[x]
|
172
|
+
end
|
173
|
+
|
174
|
+
keccak(@s)
|
175
|
+
end
|
176
|
+
|
177
|
+
def keccak(s)
|
178
|
+
(0..47).step(2) do |n|
|
179
|
+
c0 = s[0] ^ s[10] ^ s[20] ^ s[30] ^ s[40]
|
180
|
+
c1 = s[1] ^ s[11] ^ s[21] ^ s[31] ^ s[41]
|
181
|
+
c2 = s[2] ^ s[12] ^ s[22] ^ s[32] ^ s[42]
|
182
|
+
c3 = s[3] ^ s[13] ^ s[23] ^ s[33] ^ s[43]
|
183
|
+
c4 = s[4] ^ s[14] ^ s[24] ^ s[34] ^ s[44]
|
184
|
+
c5 = s[5] ^ s[15] ^ s[25] ^ s[35] ^ s[45]
|
185
|
+
c6 = s[6] ^ s[16] ^ s[26] ^ s[36] ^ s[46]
|
186
|
+
c7 = s[7] ^ s[17] ^ s[27] ^ s[37] ^ s[47]
|
187
|
+
c8 = s[8] ^ s[18] ^ s[28] ^ s[38] ^ s[48]
|
188
|
+
c9 = s[9] ^ s[19] ^ s[29] ^ s[39] ^ s[49]
|
189
|
+
h = c8 ^ ((c2 << 1) | (unsigned_shift_right(c3, 31)))
|
190
|
+
l = c9 ^ ((c3 << 1) | (unsigned_shift_right(c2, 31)))
|
191
|
+
s[0] ^= h
|
192
|
+
s[1] ^= l
|
193
|
+
s[10] ^= h
|
194
|
+
s[11] ^= l
|
195
|
+
s[20] ^= h
|
196
|
+
s[21] ^= l
|
197
|
+
s[30] ^= h
|
198
|
+
s[31] ^= l
|
199
|
+
s[40] ^= h
|
200
|
+
s[41] ^= l
|
201
|
+
h = c0 ^ ((c4 << 1) | (unsigned_shift_right(c5, 31)))
|
202
|
+
l = c1 ^ ((c5 << 1) | (unsigned_shift_right(c4, 31)))
|
203
|
+
s[2] ^= h
|
204
|
+
s[3] ^= l
|
205
|
+
s[12] ^= h
|
206
|
+
s[13] ^= l
|
207
|
+
s[22] ^= h
|
208
|
+
s[23] ^= l
|
209
|
+
s[32] ^= h
|
210
|
+
s[33] ^= l
|
211
|
+
s[42] ^= h
|
212
|
+
s[43] ^= l
|
213
|
+
h = c2 ^ ((c6 << 1) | (unsigned_shift_right(c7, 31)))
|
214
|
+
l = c3 ^ ((c7 << 1) | (unsigned_shift_right(c6, 31)))
|
215
|
+
s[4] ^= h
|
216
|
+
s[5] ^= l
|
217
|
+
s[14] ^= h
|
218
|
+
s[15] ^= l
|
219
|
+
s[24] ^= h
|
220
|
+
s[25] ^= l
|
221
|
+
s[34] ^= h
|
222
|
+
s[35] ^= l
|
223
|
+
s[44] ^= h
|
224
|
+
s[45] ^= l
|
225
|
+
h = c4 ^ ((c8 << 1) | (unsigned_shift_right(c9, 31)))
|
226
|
+
l = c5 ^ ((c9 << 1) | (unsigned_shift_right(c8, 31)))
|
227
|
+
s[6] ^= h
|
228
|
+
s[7] ^= l
|
229
|
+
s[16] ^= h
|
230
|
+
s[17] ^= l
|
231
|
+
s[26] ^= h
|
232
|
+
s[27] ^= l
|
233
|
+
s[36] ^= h
|
234
|
+
s[37] ^= l
|
235
|
+
s[46] ^= h
|
236
|
+
s[47] ^= l
|
237
|
+
h = c6 ^ ((c0 << 1) | (unsigned_shift_right(c1, 31)))
|
238
|
+
l = c7 ^ ((c1 << 1) | (unsigned_shift_right(c0, 31)))
|
239
|
+
s[8] ^= h
|
240
|
+
s[9] ^= l
|
241
|
+
s[18] ^= h
|
242
|
+
s[19] ^= l
|
243
|
+
s[28] ^= h
|
244
|
+
s[29] ^= l
|
245
|
+
s[38] ^= h
|
246
|
+
s[39] ^= l
|
247
|
+
s[48] ^= h
|
248
|
+
s[49] ^= l
|
249
|
+
b0 = s[0]
|
250
|
+
b1 = s[1]
|
251
|
+
b32 = (s[11] << 4) | (unsigned_shift_right(s[10], 28))
|
252
|
+
b33 = (s[10] << 4) | (unsigned_shift_right(s[11], 28))
|
253
|
+
b14 = (s[20] << 3) | (unsigned_shift_right(s[21], 29))
|
254
|
+
b15 = (s[21] << 3) | (unsigned_shift_right(s[20], 29))
|
255
|
+
b46 = (s[31] << 9) | (unsigned_shift_right(s[30], 23))
|
256
|
+
b47 = (s[30] << 9) | (unsigned_shift_right(s[31], 23))
|
257
|
+
b28 = (s[40] << 18) | (unsigned_shift_right(s[41], 14))
|
258
|
+
b29 = (s[41] << 18) | (unsigned_shift_right(s[40], 14))
|
259
|
+
b20 = (s[2] << 1) | (unsigned_shift_right(s[3], 31))
|
260
|
+
b21 = (s[3] << 1) | (unsigned_shift_right(s[2], 31))
|
261
|
+
b2 = (s[13] << 12) | (unsigned_shift_right(s[12], 20))
|
262
|
+
b3 = (s[12] << 12) | (unsigned_shift_right(s[13], 20))
|
263
|
+
b34 = (s[22] << 10) | (unsigned_shift_right(s[23], 22))
|
264
|
+
b35 = (s[23] << 10) | (unsigned_shift_right(s[22], 22))
|
265
|
+
b16 = (s[33] << 13) | (unsigned_shift_right(s[32], 19))
|
266
|
+
b17 = (s[32] << 13) | (unsigned_shift_right(s[33], 19))
|
267
|
+
b48 = (s[42] << 2) | (unsigned_shift_right(s[43], 30))
|
268
|
+
b49 = (s[43] << 2) | (unsigned_shift_right(s[42], 30))
|
269
|
+
b40 = (s[5] << 30) | (unsigned_shift_right(s[4], 2))
|
270
|
+
b41 = (s[4] << 30) | (unsigned_shift_right(s[5], 2))
|
271
|
+
b22 = (s[14] << 6) | (unsigned_shift_right(s[15], 26))
|
272
|
+
b23 = (s[15] << 6) | (unsigned_shift_right(s[14], 26))
|
273
|
+
b4 = (s[25] << 11) | (unsigned_shift_right(s[24], 21))
|
274
|
+
b5 = (s[24] << 11) | (unsigned_shift_right(s[25], 21))
|
275
|
+
b36 = (s[34] << 15) | (unsigned_shift_right(s[35], 17))
|
276
|
+
b37 = (s[35] << 15) | (unsigned_shift_right(s[34], 17))
|
277
|
+
b18 = (s[45] << 29) | (unsigned_shift_right(s[44], 3))
|
278
|
+
b19 = (s[44] << 29) | (unsigned_shift_right(s[45], 3))
|
279
|
+
b10 = (s[6] << 28) | (unsigned_shift_right(s[7], 4))
|
280
|
+
b11 = (s[7] << 28) | (unsigned_shift_right(s[6], 4))
|
281
|
+
b42 = (s[17] << 23) | (unsigned_shift_right(s[16], 9))
|
282
|
+
b43 = (s[16] << 23) | (unsigned_shift_right(s[17], 9))
|
283
|
+
b24 = (s[26] << 25) | (unsigned_shift_right(s[27], 7))
|
284
|
+
b25 = (s[27] << 25) | (unsigned_shift_right(s[26], 7))
|
285
|
+
b6 = (s[36] << 21) | (unsigned_shift_right(s[37], 11))
|
286
|
+
b7 = (s[37] << 21) | (unsigned_shift_right(s[36], 11))
|
287
|
+
b38 = (s[47] << 24) | (unsigned_shift_right(s[46], 8))
|
288
|
+
b39 = (s[46] << 24) | (unsigned_shift_right(s[47], 8))
|
289
|
+
b30 = (s[8] << 27) | (unsigned_shift_right(s[9], 5))
|
290
|
+
b31 = (s[9] << 27) | (unsigned_shift_right(s[8], 5))
|
291
|
+
b12 = (s[18] << 20) | (unsigned_shift_right(s[19], 12))
|
292
|
+
b13 = (s[19] << 20) | (unsigned_shift_right(s[18], 12))
|
293
|
+
b44 = (s[29] << 7) | (unsigned_shift_right(s[28], 25))
|
294
|
+
b45 = (s[28] << 7) | (unsigned_shift_right(s[29], 25))
|
295
|
+
b26 = (s[38] << 8) | (unsigned_shift_right(s[39], 24))
|
296
|
+
b27 = (s[39] << 8) | (unsigned_shift_right(s[38], 24))
|
297
|
+
b8 = (s[48] << 14) | (unsigned_shift_right(s[49], 18))
|
298
|
+
b9 = (s[49] << 14) | (unsigned_shift_right(s[48], 18))
|
299
|
+
s[0] = b0 ^ (~b2 & b4)
|
300
|
+
s[1] = b1 ^ (~b3 & b5)
|
301
|
+
s[10] = b10 ^ (~b12 & b14)
|
302
|
+
s[11] = b11 ^ (~b13 & b15)
|
303
|
+
s[20] = b20 ^ (~b22 & b24)
|
304
|
+
s[21] = b21 ^ (~b23 & b25)
|
305
|
+
s[30] = b30 ^ (~b32 & b34)
|
306
|
+
s[31] = b31 ^ (~b33 & b35)
|
307
|
+
s[40] = b40 ^ (~b42 & b44)
|
308
|
+
s[41] = b41 ^ (~b43 & b45)
|
309
|
+
s[2] = b2 ^ (~b4 & b6)
|
310
|
+
s[3] = b3 ^ (~b5 & b7)
|
311
|
+
s[12] = b12 ^ (~b14 & b16)
|
312
|
+
s[13] = b13 ^ (~b15 & b17)
|
313
|
+
s[22] = b22 ^ (~b24 & b26)
|
314
|
+
s[23] = b23 ^ (~b25 & b27)
|
315
|
+
s[32] = b32 ^ (~b34 & b36)
|
316
|
+
s[33] = b33 ^ (~b35 & b37)
|
317
|
+
s[42] = b42 ^ (~b44 & b46)
|
318
|
+
s[43] = b43 ^ (~b45 & b47)
|
319
|
+
s[4] = b4 ^ (~b6 & b8)
|
320
|
+
s[5] = b5 ^ (~b7 & b9)
|
321
|
+
s[14] = b14 ^ (~b16 & b18)
|
322
|
+
s[15] = b15 ^ (~b17 & b19)
|
323
|
+
s[24] = b24 ^ (~b26 & b28)
|
324
|
+
s[25] = b25 ^ (~b27 & b29)
|
325
|
+
s[34] = b34 ^ (~b36 & b38)
|
326
|
+
s[35] = b35 ^ (~b37 & b39)
|
327
|
+
s[44] = b44 ^ (~b46 & b48)
|
328
|
+
s[45] = b45 ^ (~b47 & b49)
|
329
|
+
s[6] = b6 ^ (~b8 & b0)
|
330
|
+
s[7] = b7 ^ (~b9 & b1)
|
331
|
+
s[16] = b16 ^ (~b18 & b10)
|
332
|
+
s[17] = b17 ^ (~b19 & b11)
|
333
|
+
s[26] = b26 ^ (~b28 & b20)
|
334
|
+
s[27] = b27 ^ (~b29 & b21)
|
335
|
+
s[36] = b36 ^ (~b38 & b30)
|
336
|
+
s[37] = b37 ^ (~b39 & b31)
|
337
|
+
s[46] = b46 ^ (~b48 & b40)
|
338
|
+
s[47] = b47 ^ (~b49 & b41)
|
339
|
+
s[8] = b8 ^ (~b0 & b2)
|
340
|
+
s[9] = b9 ^ (~b1 & b3)
|
341
|
+
s[18] = b18 ^ (~b10 & b12)
|
342
|
+
s[19] = b19 ^ (~b11 & b13)
|
343
|
+
s[28] = b28 ^ (~b20 & b22)
|
344
|
+
s[29] = b29 ^ (~b21 & b23)
|
345
|
+
s[38] = b38 ^ (~b30 & b32)
|
346
|
+
s[39] = b39 ^ (~b31 & b33)
|
347
|
+
s[48] = b48 ^ (~b40 & b42)
|
348
|
+
s[49] = b49 ^ (~b41 & b43)
|
349
|
+
|
350
|
+
s[0] ^= RC[n]
|
351
|
+
s[1] ^= RC[n + 1]
|
352
|
+
end
|
353
|
+
end
|
354
|
+
|
355
|
+
def unsigned_shift_right(val, amount)
|
356
|
+
mask = (1 << (32 - amount)) - 1
|
357
|
+
(val >> amount) & mask
|
358
|
+
end
|
359
|
+
end
|
360
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# Copyright 2023-2024, Kurt Meyerhofer
|
2
|
+
# This file is part of zwiebel.
|
3
|
+
|
4
|
+
# zwiebel is free software: you can redistribute it and/or modify it under the terms of
|
5
|
+
# the GNU Lesser General Public License as published by the Free Software Foundation,
|
6
|
+
# either version 3 of the License, or (at your option) any later version.
|
7
|
+
|
8
|
+
# zwiebel is distributed in the hope that it will be useful, but WITHOUT ANY
|
9
|
+
# WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
10
|
+
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
|
11
|
+
# more details.
|
12
|
+
|
13
|
+
# You should have received a copy of the GNU Lesser General Public License along with zwiebel.
|
14
|
+
# If not, see <https://www.gnu.org/licenses/>.
|
15
|
+
|
16
|
+
require "base64"
|
17
|
+
|
18
|
+
module Zwiebel
|
19
|
+
class Utilities
|
20
|
+
SALT_LENGTH = 16
|
21
|
+
MAC_LENGTH = 32
|
22
|
+
S_KEY_LENGTH = 32
|
23
|
+
S_IV_LENGTH = 16
|
24
|
+
|
25
|
+
def self.current_time_period
|
26
|
+
# tor rend-spec-v3
|
27
|
+
# 2.2.1 [TIME-PERIODS]
|
28
|
+
current_time = Time.now.utc.to_i
|
29
|
+
(current_time / 60 - 1440) / 1440
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.decrypt_layer(encrypted_data:, constant:, revision_counter:, subcredential:, blinded_key:)
|
33
|
+
cleaned_data = encrypted_data.gsub("-----BEGIN MESSAGE-----\n", "").gsub("\n-----END MESSAGE-----", "")
|
34
|
+
encrypted = Base64.decode64(cleaned_data)
|
35
|
+
|
36
|
+
if encrypted.length < SALT_LENGTH + MAC_LENGTH
|
37
|
+
raise ContentLengthError, "encrypted data should be at least #{SALT_LENGTH + MAC_LENGTH} bytes"
|
38
|
+
end
|
39
|
+
|
40
|
+
salt = encrypted.byteslice(0, SALT_LENGTH)
|
41
|
+
ciphertext = encrypted.byteslice(SALT_LENGTH..-(MAC_LENGTH + 1))
|
42
|
+
expected_mac = encrypted.byteslice(-MAC_LENGTH..-1)
|
43
|
+
|
44
|
+
key_digest = blinded_key + subcredential + [revision_counter].pack("Q>") + salt + constant
|
45
|
+
bit_length = (S_KEY_LENGTH + S_IV_LENGTH + MAC_LENGTH) * 8
|
46
|
+
|
47
|
+
keys = Shake256.new(bit_length: bit_length, message_type: "hex").hexdigest(key_digest.unpack1("H*"))
|
48
|
+
keys = [keys].pack("H*")
|
49
|
+
|
50
|
+
secret_key = keys.byteslice(0, S_KEY_LENGTH)
|
51
|
+
secret_iv = keys.byteslice(S_KEY_LENGTH, S_IV_LENGTH)
|
52
|
+
mac_key = keys.byteslice(S_KEY_LENGTH + S_IV_LENGTH, MAC_LENGTH)
|
53
|
+
|
54
|
+
mac_prefix = [mac_key.length].pack("Q>") + mac_key + [salt.length].pack("Q>") + salt
|
55
|
+
mac_for = OpenSSL::Digest.digest("SHA3-256", mac_prefix + ciphertext)
|
56
|
+
|
57
|
+
if expected_mac != mac_for
|
58
|
+
raise DataError, "incorrect message authentication code"
|
59
|
+
end
|
60
|
+
|
61
|
+
decipher = OpenSSL::Cipher.new("aes-256-ctr")
|
62
|
+
decipher.decrypt
|
63
|
+
decipher.key = secret_key
|
64
|
+
decipher.iv = secret_iv
|
65
|
+
decipher.update(ciphertext) + decipher.final
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
data/lib/zwiebel/version.rb
CHANGED
data/lib/zwiebel.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# Copyright 2023, Kurt Meyerhofer
|
1
|
+
# Copyright 2023-2024, Kurt Meyerhofer
|
2
2
|
# This file is part of zwiebel.
|
3
3
|
|
4
4
|
# zwiebel is free software: you can redistribute it and/or modify it under the terms of
|
@@ -14,11 +14,58 @@
|
|
14
14
|
# If not, see <https://www.gnu.org/licenses/>.
|
15
15
|
|
16
16
|
require "base32"
|
17
|
+
require_relative "zwiebel/hidden_service/v3"
|
18
|
+
require_relative "zwiebel/hidden_service/descriptor"
|
19
|
+
require_relative "zwiebel/hidden_service/outer_layer"
|
20
|
+
require_relative "zwiebel/hidden_service/inner_layer"
|
21
|
+
require_relative "zwiebel/hidden_service/introduction_point"
|
17
22
|
require_relative "zwiebel/control"
|
23
|
+
require_relative "zwiebel/ed25519_certificate"
|
24
|
+
require_relative "zwiebel/errors"
|
25
|
+
require_relative "zwiebel/shake256"
|
26
|
+
require_relative "zwiebel/utilities"
|
18
27
|
require_relative "zwiebel/version"
|
19
28
|
|
20
29
|
module Zwiebel
|
21
30
|
|
31
|
+
def self.start(address, settings = {}, &block)
|
32
|
+
raise InvalidAddressError, "address invalid" unless v3_address_valid?(address)
|
33
|
+
setting_options = %i(host port cookie)
|
34
|
+
settings.delete_if do |k, _|
|
35
|
+
!setting_options.include?(k)
|
36
|
+
end
|
37
|
+
|
38
|
+
tor = Control.new(**settings)
|
39
|
+
tor.authenticate
|
40
|
+
address_without_suffix = address.gsub(".onion", "")
|
41
|
+
tor.send_command("GETINFO", "hs/client/desc/id/#{address_without_suffix}")
|
42
|
+
|
43
|
+
hs_reply = tor.read_reply
|
44
|
+
if hs_reply.start_with?("551")
|
45
|
+
# Put this into a reusable method # TODO
|
46
|
+
tor.send_command("HSFETCH", address_without_suffix)
|
47
|
+
tor.send_command("GETINFO", "hs/client/desc/id/#{address_without_suffix}")
|
48
|
+
hs_reply = tor.read_reply
|
49
|
+
end
|
50
|
+
|
51
|
+
hs_descriptor = ""
|
52
|
+
descriptor_current_field = nil
|
53
|
+
while hs_reply != "250 OK"
|
54
|
+
hs_reply = tor.read_reply
|
55
|
+
next if hs_reply == "." || hs_reply == "250 OK"
|
56
|
+
hs_descriptor += "#{hs_reply}\n"
|
57
|
+
end
|
58
|
+
|
59
|
+
if hs_descriptor.length < 1
|
60
|
+
raise DataError, "hidden service descriptor not found"
|
61
|
+
else
|
62
|
+
hidden_service_v3 = HiddenService::V3.new(
|
63
|
+
descriptor_string: hs_descriptor,
|
64
|
+
onion_address: address
|
65
|
+
).decrypt
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
22
69
|
def self.v3_address_valid?(address)
|
23
70
|
# tor address-spec
|
24
71
|
# onion_address = base32(PUBKEY | CHECKSUM | VERSION)
|
@@ -37,24 +84,28 @@ module Zwiebel
|
|
37
84
|
pubkey = decoded_address.byteslice(0, 32)
|
38
85
|
checksum = decoded_address.byteslice(32, 2)
|
39
86
|
version = decoded_address.byteslice(34)
|
40
|
-
|
41
87
|
onion_checksum = ".onion checksum"
|
42
88
|
calculated_checksum = onion_checksum + pubkey + version
|
43
|
-
|
44
89
|
digest = OpenSSL::Digest.digest("SHA3-256", calculated_checksum)
|
45
90
|
checksum_truncated = digest.byteslice(0, 2)
|
46
|
-
|
47
91
|
checksum_truncated == checksum
|
48
92
|
end
|
49
93
|
|
94
|
+
def self.v3_address_pubkey(address)
|
95
|
+
raise InvalidAddressError, "address invalid" unless v3_address_valid?(address)
|
96
|
+
|
97
|
+
decoded_address = Base32.decode(address.gsub(".onion", "").upcase)
|
98
|
+
decoded_address.byteslice(0, 32)
|
99
|
+
end
|
100
|
+
|
50
101
|
def self.cookie_file_hash(file_path:)
|
51
102
|
if !File.exist?(file_path)
|
52
|
-
raise
|
103
|
+
raise FileReadError, "cookie file not present"
|
53
104
|
elsif !File.readable?(file_path)
|
54
|
-
raise
|
105
|
+
raise FileReadError, "not permitted to read cookie file"
|
55
106
|
else
|
56
107
|
data = IO.binread(file_path)
|
57
|
-
data.
|
108
|
+
data.unpack1("H*")
|
58
109
|
end
|
59
110
|
end
|
60
111
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: zwiebel
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Kurt Meyerhofer
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2024-01-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: base32
|
@@ -47,6 +47,15 @@ extra_rdoc_files: []
|
|
47
47
|
files:
|
48
48
|
- lib/zwiebel.rb
|
49
49
|
- lib/zwiebel/control.rb
|
50
|
+
- lib/zwiebel/ed25519_certificate.rb
|
51
|
+
- lib/zwiebel/errors.rb
|
52
|
+
- lib/zwiebel/hidden_service/descriptor.rb
|
53
|
+
- lib/zwiebel/hidden_service/inner_layer.rb
|
54
|
+
- lib/zwiebel/hidden_service/introduction_point.rb
|
55
|
+
- lib/zwiebel/hidden_service/outer_layer.rb
|
56
|
+
- lib/zwiebel/hidden_service/v3.rb
|
57
|
+
- lib/zwiebel/shake256.rb
|
58
|
+
- lib/zwiebel/utilities.rb
|
50
59
|
- lib/zwiebel/version.rb
|
51
60
|
homepage: https://github.com/kmeyerhofer/zwiebel
|
52
61
|
licenses:
|
@@ -67,7 +76,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
67
76
|
- !ruby/object:Gem::Version
|
68
77
|
version: '0'
|
69
78
|
requirements:
|
70
|
-
- Tor (>= 0.
|
79
|
+
- Tor (>= 0.4.1.1-alpha)
|
71
80
|
rubygems_version: 3.2.22
|
72
81
|
signing_key:
|
73
82
|
specification_version: 4
|