90 lines
		
	
	
		
			2.2 KiB
		
	
	
	
		
			Ruby
		
	
	
	
			
		
		
	
	
			90 lines
		
	
	
		
			2.2 KiB
		
	
	
	
		
			Ruby
		
	
	
	
# frozen_string_literal: true
 | 
						|
 | 
						|
module Gitlab
 | 
						|
  class SSHPublicKey
 | 
						|
    Technology = Struct.new(:name, :key_class, :supported_sizes)
 | 
						|
 | 
						|
    TECHNOLOGIES = [
 | 
						|
      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])
 | 
						|
    ].freeze
 | 
						|
 | 
						|
    def self.technology(name)
 | 
						|
      TECHNOLOGIES.find { |tech| tech.name.to_s == name.to_s }
 | 
						|
    end
 | 
						|
 | 
						|
    def self.technology_for_key(key)
 | 
						|
      TECHNOLOGIES.find { |tech| key.is_a?(tech.key_class) }
 | 
						|
    end
 | 
						|
 | 
						|
    def self.supported_sizes(name)
 | 
						|
      technology(name)&.supported_sizes
 | 
						|
    end
 | 
						|
 | 
						|
    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
 | 
						|
 | 
						|
    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?
 | 
						|
      SSHKey.valid_ssh_public_key?(key_text)
 | 
						|
    end
 | 
						|
 | 
						|
    def type
 | 
						|
      technology.name if key.present?
 | 
						|
    end
 | 
						|
 | 
						|
    def bits
 | 
						|
      return if key.blank?
 | 
						|
 | 
						|
      case type
 | 
						|
      when :rsa
 | 
						|
        key.n&.num_bits
 | 
						|
      when :dsa
 | 
						|
        key.p&.num_bits
 | 
						|
      when :ecdsa
 | 
						|
        key.group.order&.num_bits
 | 
						|
      when :ed25519
 | 
						|
        256
 | 
						|
      else
 | 
						|
        raise "Unsupported key type: #{type}"
 | 
						|
      end
 | 
						|
    end
 | 
						|
 | 
						|
    private
 | 
						|
 | 
						|
    def technology
 | 
						|
      @technology ||=
 | 
						|
        self.class.technology_for_key(key) || raise("Unsupported key type: #{key.class}")
 | 
						|
    end
 | 
						|
  end
 | 
						|
end
 |