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 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