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
 |