zwiebel 0.0.3 → 0.0.4
Sign up to get free protection for your applications and to get access to all the features.
- 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
|