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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a178968188ad9e5311134ddd77ecba849f4f8708da9580800e55053f7e459355
4
- data.tar.gz: 8bed48748a9f51a1236493e63c4b808d6e9ee55c621db83eb58b2b62cedc87e5
3
+ metadata.gz: a8bf194bb0d78b479d0b88fe66545f06108cd9180b9d7be9644b5c517f8935cb
4
+ data.tar.gz: fcbe181944b14678dccae2b87eda1539d637980c39bf6157e7b51ce774e35666
5
5
  SHA512:
6
- metadata.gz: f98941b8129bc0ad692a8a17e780311accafe7be99e62a056f6c2b831e2633dbf76fa643ddd8f593a4d89fe22fac18e243c8e75ecc5213624d315cbd4c0dde9a
7
- data.tar.gz: 7ed879c1236fb10f54a672041878ad64f63a8175a45dcf9b4556f3203ae84a9e69e73ae1beb5fbda5d1e1075f395c0aaf372f80ef9954c0e016cf5053ec06357
6
+ metadata.gz: 56158c5fe35b4f6fdba0c69c9ee4f897d5cb37d942fc64d4f4b3c9676ddd22cb3b7cd6c5ad0f7914656f55274531e99e3ed6361dfe792fe2fe5dd824140a40ce
7
+ data.tar.gz: 8fa7d94d7c4d4f81454a5c1802ff7a87b056a65e7f74e9afbde49c9477e880b527dc830061525f7c63d0eb3943ea6d27d821bf9fde63ddef08e38d9642685584
@@ -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
@@ -1,3 +1,3 @@
1
1
  module Zwiebel
2
- VERSION = "0.0.3"
2
+ VERSION = "0.0.4"
3
3
  end
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 StandardError "cookie file not present"
103
+ raise FileReadError, "cookie file not present"
53
104
  elsif !File.readable?(file_path)
54
- raise StandardError "not permitted to read cookie file"
105
+ raise FileReadError, "not permitted to read cookie file"
55
106
  else
56
107
  data = IO.binread(file_path)
57
- data.unpack("H*")[0]
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.3
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: 2023-12-03 00:00:00.000000000 Z
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.3.2.1)
79
+ - Tor (>= 0.4.1.1-alpha)
71
80
  rubygems_version: 3.2.22
72
81
  signing_key:
73
82
  specification_version: 4