various: add read-only mode support
[girocco.git] / Girocco / SSHUtil.pm
blob8c468d369e52412d9306055a0ee9e59b48915e12
1 # Girocco::SSHUtil.pm -- OpenSSH public key utility
2 # Copyright (c) 2013 Kyle J. McKay. All rights reserved.
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 package Girocco::SSHUtil;
20 use strict;
21 use warnings;
23 use base qw(Exporter);
24 our @EXPORT;
25 our $VERSION;
27 BEGIN {
28 @EXPORT = qw(sshpub_validate);
29 *VERSION = \'1.0';
32 use MIME::Base64;
33 use Digest::MD5 qw(md5_hex);
35 # Input: binary sequence of one or more 4-byte big-endian length + contents
36 # sequences
37 # Output: array of contents or () if invalid
38 sub _splitparts($)
40 my $data = shift;
41 my @parts = ();
42 while (length($data) >= 4) {
43 my $len = unpack('N',substr($data,0,4));
44 my $value = '';
45 if ($len > 0) {
46 return () if $len + 4 > length($data);
47 $value = substr($data,4,$len);
49 push(@parts, $value);
50 substr($data, 0, 4+$len) = '';
52 return () unless length($data) == 0;
53 return @parts;
56 # Input: binary key data
57 # Output: how many key bits are present
58 sub _countbits($)
60 my $data = shift;
61 return undef if length($data) < 1;
62 my $bits = 8 * length($data);
63 # but leading zero bits must be subtracted
64 my $byte = unpack('C',substr($data,0,1));
65 if (!$byte) {
66 $bits -= 8;
67 } else {
68 return undef if $byte & 0x80; # negative is not valid
69 while (!($byte & 0x80)) {
70 --$bits;
71 $byte <<= 1;
74 return $bits;
77 # sshpub_validate
78 # Input: A single-line ssh public key such as the contents of id_rsa.pub
79 # Output:
80 # () (if invalid key)
81 # -OR-
82 # array of 4 elements:
83 # key type (either 'ssh-dss' or 'ssh-rsa')
84 # key size (integer such as 1024, 2048, 3072, 4096 etc.)
85 # key fingerprint (string as shown by ssh-keygen -l -E md5)
86 # key comment (may be '' if none)
87 sub sshpub_validate($)
89 my $raw = shift;
90 return () if !$raw;
91 my @fields = split(/[ ]/, $raw, 3);
92 return () if @fields < 2;
93 return () if $fields[0] ne 'ssh-dss' && $fields[0] ne 'ssh-rsa';
94 return () if $fields[1] !~ m,^[0-9A-Za-z+/=]+$,;
95 $fields[2] = '' if !$fields[2];
96 $fields[2] = join(' ', split(' ', $fields[2]));
97 my $data = decode_base64($fields[1]);
98 return () if !$data || length($data) < 21;
99 my @parts = _splitparts($data);
100 return () if @parts < 3 || $parts[0] ne $fields[0];
101 # ssh-rsa is 3 fields: type, e, n
102 # ssh-dss is 5 fields: type, p, q, g, y
103 my $bitlength;
104 if ($parts[0] eq 'ssh-dss') {
105 return () if @parts != 5;
106 $bitlength = _countbits($parts[1]);
107 } elsif ($parts[0] eq 'ssh-rsa') {
108 return () if @parts != 3;
109 $bitlength = _countbits($parts[2]);
110 } else {
111 return ();
113 return () if !$bitlength || $bitlength < 512; # probably should be 2048
114 my $fingerprint = md5_hex($data);
115 $fingerprint =~ s/(..)(?=.)/$1:/g;
116 return ($fields[0], $bitlength, $fingerprint, $fields[2]);