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