b.liu | e958203 | 2025-04-17 19:18:16 +0800 | [diff] [blame^] | 1 | Author: Daniel F. Dickinson <cshored@thecshore.com> |
| 2 | Date: Sun Jan 27 01:04:25 2019 -0500 |
| 3 | |
| 4 | gitolite: Eliminate the need for ssh-keygen dependency |
| 5 | |
| 6 | Previously gitolite used ssh-keygen to generate fingerprints |
| 7 | from OpenSSH keys to ensure non-duplication of keys when |
| 8 | processing them to create / manage user ssh access to the |
| 9 | git repositories. This ends up depending on openssl, |
| 10 | which is large and unnecessary when we are running on an |
| 11 | embedded distro such as OpenWrt. |
| 12 | |
| 13 | Signed-off-by: Daniel F. Dickinson <cshored@thecshore.com> |
| 14 | --- a/src/lib/Gitolite/Common.pm |
| 15 | +++ b/src/lib/Gitolite/Common.pm |
| 16 | @@ -26,6 +26,8 @@ package Gitolite::Common; |
| 17 | use Exporter 'import'; |
| 18 | use File::Path qw(mkpath); |
| 19 | use File::Temp qw(tempfile); |
| 20 | +use MIME::Base64 qw(decode_base64); |
| 21 | +use Digest::SHA qw(sha256_base64); |
| 22 | use Carp qw(carp cluck croak confess); |
| 23 | |
| 24 | use strict; |
| 25 | @@ -352,43 +354,82 @@ sub logger_plus_stderr { |
| 26 | } |
| 27 | |
| 28 | # ---------------------------------------------------------------------- |
| 29 | +# Decode OpenSSH key |
| 30 | +# If the key cannot be parsed it will be undef |
| 31 | +# Returns (algorithm_name, algo_data1, algo_data2, ...) |
| 32 | +sub ssh_decode_key($) { |
| 33 | + my $key = shift; |
| 34 | + my $keydata = decode_base64($key); |
| 35 | + my @keyparts = (); |
| 36 | + my $partlen; |
| 37 | + my $algorithm; |
| 38 | + my $data; |
| 39 | + my $pos = 0; |
| 40 | + $partlen = unpack('N', substr $keydata, $pos, 4) or return undef; |
| 41 | + $algorithm = substr $keydata, $pos + 4, $partlen or return undef; |
| 42 | + $pos = $pos + 4 + $partlen; |
| 43 | + while ( $pos <= length($keydata) ) { |
| 44 | + $partlen = unpack('N', substr $keydata, $pos, 4) or last; |
| 45 | + $data = unpack('s>*', substr $keydata, $pos + 4, 4) or last; |
| 46 | + $pos = $pos + 4 + $partlen; |
| 47 | + push @keyparts, $data; |
| 48 | + } |
| 49 | + return ( $algorithm, @keyparts ); |
| 50 | +} |
| 51 | + |
| 52 | +# ---------------------------------------------------------------------- |
| 53 | +# Parse OpenSSH line |
| 54 | +# If the file cannot be parsed it will be undef |
| 55 | +# Returns (restrictions, algorithm, PEMkey, comment) |
| 56 | +sub ssh_parse_line($) { |
| 57 | + my $ssh_line = shift; |
| 58 | + my @ssh_parts = split / /, $ssh_line, 5; |
| 59 | + if (scalar @ssh_parts < 4) { |
| 60 | + @ssh_parts = ('', @ssh_parts); |
| 61 | + } |
| 62 | + if (scalar @ssh_parts > 4) { |
| 63 | + @ssh_parts = @ssh_parts[0,3] |
| 64 | + } |
| 65 | + if (scalar @ssh_parts < 4) { |
| 66 | + @ssh_parts = undef; |
| 67 | + } |
| 68 | + return ( @ssh_parts ); |
| 69 | +} |
| 70 | + |
| 71 | +# ---------------------------------------------------------------------- |
| 72 | +# Get the SSH fingerprint of a line of text |
| 73 | +# If the fingerprint cannot be parsed, it will be undef |
| 74 | +# In a scalar context, returns the fingerprint |
| 75 | +# In a list context, returns (fingerprint, output) where output |
| 76 | +# is the parsed input line (less algorithm) |
| 77 | +sub ssh_fingerprint_line($) { |
| 78 | + my $ssh_line = shift; |
| 79 | + my @parsed_line = ssh_parse_line($ssh_line) or return undef; |
| 80 | + my @ssh_parts = ssh_decode_key($parsed_line[2]) or return undef; |
| 81 | + ( $parsed_line[1] eq $ssh_parts[0] ) or die "algorithm mismatch: $parsed_line[1] vs. $ssh_parts[0]"; |
| 82 | + my $fp = sha256_base64(join(' ', @ssh_parts[1,-1])); |
| 83 | + return wantarray ? ($fp, join(' ', @ssh_parts[1,-1])) : $fp; |
| 84 | +} |
| 85 | + |
| 86 | +# ---------------------------------------------------------------------- |
| 87 | # Get the SSH fingerprint of a file |
| 88 | # If the fingerprint cannot be parsed, it will be undef |
| 89 | # In a scalar context, returns the fingerprint |
| 90 | # In a list context, returns (fingerprint, output) where output |
| 91 | -# is the raw output of the ssh-keygen command |
| 92 | -sub ssh_fingerprint_file { |
| 93 | +# is the raw input line |
| 94 | +sub ssh_fingerprint_file($) { |
| 95 | my $in = shift; |
| 96 | -f $in or die "file not found: $in\n"; |
| 97 | my $fh; |
| 98 | - open( $fh, "ssh-keygen -l -f $in |" ) or die "could not fork: $!\n"; |
| 99 | + open( $fh, $in ) or die "could not open $in: $!\n"; |
| 100 | my $output = <$fh>; |
| 101 | chomp $output; |
| 102 | - # dbg("fp = $fp"); |
| 103 | close $fh; |
| 104 | # Return a valid fingerprint or undef |
| 105 | - my $fp = undef; |
| 106 | - if($output =~ /((?:MD5:)?(?:[0-9a-f]{2}:){15}[0-9a-f]{2})/i or |
| 107 | - $output =~ m{((?:RIPEMD|SHA)\d+:[A-Za-z0-9+/=]+)}i) { |
| 108 | - $fp = $1; |
| 109 | - } |
| 110 | + my $fp = ssh_fingerprint_line($output); |
| 111 | return wantarray ? ($fp, $output) : $fp; |
| 112 | } |
| 113 | |
| 114 | -# Get the SSH fingerprint of a line of text |
| 115 | -# If the fingerprint cannot be parsed, it will be undef |
| 116 | -# In a scalar context, returns the fingerprint |
| 117 | -# In a list context, returns (fingerprint, output) where output |
| 118 | -# is the raw output of the ssh-keygen command |
| 119 | -sub ssh_fingerprint_line { |
| 120 | - my ( $fh, $fn ) = tempfile(); |
| 121 | - print $fh shift() . "\n"; |
| 122 | - close $fh; |
| 123 | - my ($fp,$output) = ssh_fingerprint_file($fn); |
| 124 | - unlink $fn; |
| 125 | - return wantarray ? ($fp,$output) : $fp; |
| 126 | -} |
| 127 | - |
| 128 | # ---------------------------------------------------------------------- |
| 129 | |
| 130 | # bare-minimum subset of 'Tsh' (see github.com/sitaramc/tsh) |