ssh_public_key.rb 2.15 KB
Newer Older
1 2
module Gitlab
  class SSHPublicKey
3
    Technology = Struct.new(:name, :key_class, :supported_sizes)
4 5

    Technologies = [
6 7 8 9
      Technology.new(:rsa, OpenSSL::PKey::RSA, [1024, 2048, 3072, 4096]),
      Technology.new(:dsa, OpenSSL::PKey::DSA, [1024, 2048, 3072]),
      Technology.new(:ecdsa, OpenSSL::PKey::EC, [256, 384, 521]),
      Technology.new(:ed25519, Net::SSH::Authentication::ED25519::PubKey, [256])
10 11 12
    ].freeze

    def self.technology(name)
13
      Technologies.find { |tech| tech.name.to_s == name.to_s }
14 15
    end

Nick Thomas's avatar
Nick Thomas committed
16 17 18 19
    def self.technology_for_key(key)
      Technologies.find { |tech| key.is_a?(tech.key_class) }
    end

20 21
    def self.supported_sizes(name)
      technology(name)&.supported_sizes
22 23
    end

Rubén Dávila's avatar
Rubén Dávila committed
24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
    def self.sanitize(key_content)
      ssh_type, *parts = key_content.strip.split

      return key_content if parts.empty?

      parts.each_with_object("#{ssh_type} ").with_index do |(part, content), index|
        content << part

        if Gitlab::SSHPublicKey.new(content).valid?
          break [content, parts[index + 1]].compact.join(' ') # Add the comment part if present
        elsif parts.size == index + 1 # return original content if we've reached the last element
          break key_content
        end
      end
    end

40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
    attr_reader :key_text, :key

    # Unqualified MD5 fingerprint for compatibility
    delegate :fingerprint, to: :key, allow_nil: true

    def initialize(key_text)
      @key_text = key_text

      @key =
        begin
          Net::SSH::KeyFactory.load_data_public_key(key_text)
        rescue StandardError, NotImplementedError
        end
    end

    def valid?
56
      SSHKey.valid_ssh_public_key?(key_text)
57 58 59
    end

    def type
Rubén Dávila's avatar
Rubén Dávila committed
60
      technology.name if key.present?
61 62 63
    end

    def bits
Rubén Dávila's avatar
Rubén Dávila committed
64
      return if key.blank?
65 66 67

      case type
      when :rsa
Rubén Dávila's avatar
Rubén Dávila committed
68
        key.n&.num_bits
69
      when :dsa
Rubén Dávila's avatar
Rubén Dávila committed
70
        key.p&.num_bits
71
      when :ecdsa
Rubén Dávila's avatar
Rubén Dávila committed
72
        key.group.order&.num_bits
73 74 75 76 77 78
      when :ed25519
        256
      else
        raise "Unsupported key type: #{type}"
      end
    end
79 80 81 82 83

    private

    def technology
      @technology ||=
Nick Thomas's avatar
Nick Thomas committed
84
        self.class.technology_for_key(key) || raise("Unsupported key type: #{key.class}")
85
    end
86 87
  end
end