File Coverage

blib/lib/Convert/SSH2.pm
Criterion Covered Total %
statement 67 77 87.0
branch 7 18 38.8
condition 1 3 33.3
subroutine 14 16 87.5
pod 3 4 75.0
total 92 118 77.9


line stmt bran cond sub pod time code
1             package Convert::SSH2;
2              
3 3     3   68360 use 5.010;
  3         10  
  3         105  
4 3     3   17 use strict;
  3         5  
  3         96  
5 3     3   14 use warnings;
  3         9  
  3         75  
6              
7 3     3   2569 use Moo;
  3         52504  
  3         21  
8 3     3   8902 use MIME::Base64 qw(decode_base64);
  3         2473  
  3         248  
9 3     3   3064 use File::Slurp qw(read_file write_file);
  3         58697  
  3         307  
10 3     3   34 use Carp qw(confess);
  3         6  
  3         409  
11 3     3   3253 use Try::Tiny;
  3         5300  
  3         197  
12 3     3   2671 use Class::Load qw(load_class);
  3         91172  
  3         207  
13 3     3   4953 use Math::BigInt try => 'GMP';
  3         70963  
  3         24  
14              
15             =head1 NAME
16              
17             Convert::SSH2 - Convert SSH2 RSA keys to other formats
18              
19             =head1 VERSION
20              
21             Version 0.01
22              
23             =cut
24              
25             our $VERSION = '0.01';
26              
27             =head1 SYNOPSIS
28              
29             use 5.010;
30             use Convert::SSH2;
31              
32             my $converter = Convert::SSH2->new('~/.ssh/id_rsa.pub');
33             # Automatically calls parse()
34              
35             # Use default PKCS#1 format
36             say $converter->format_output();
37              
38             $converter->write('/my/pub/key.pem');
39              
40              
41             =head1 PURPOSE
42              
43             This library converts SSH2 style RSA public keys to other representations like PKCS#1.
44             This is useful if you want to use these public keys with other Perl cryptography
45             libraries like L or L.
46              
47             =head1 ATTRIBUTES
48              
49             =over
50              
51             =item key
52              
53             Required. Read-only. The key material. Attempts to be DWIMish. If this is a file path,
54             it will be used to load the file contents into memory. If it's a buffer, it will use
55             the buffer contents.
56              
57             =back
58              
59             =cut
60              
61             has 'key' => (
62             is => 'ro',
63             required => 1,
64             );
65              
66             =over
67              
68             =item format
69              
70             Read-only. The output format. Current supports:
71              
72             =over
73              
74             =item * pkcs1
75              
76             This format looks like
77              
78             -----BEGIN RSA PUBLIC KEY-----
79             ...
80             -----END RSA PUBLIC KEY-----
81              
82             =item * pkcs8
83              
84             This format looks like
85              
86             -----BEGIN PUBLIC KEY-----
87             ...
88             -----END PUBLIC KEY-----
89              
90             =back
91              
92             You can add your own format by implementing a L module.
93              
94             =back
95              
96             =cut
97              
98             has 'format' => (
99             is => 'ro',
100             isa => sub {
101             my $n = shift;
102             confess "$n is not a supported format." unless
103             grep { $n eq $_ } qw(
104             pkcs1
105             pkcs8
106             );
107             },
108             default => sub { 'pkcs1' },
109             );
110              
111             has '_buffer' => (
112             is => 'rw',
113             );
114              
115             has '_output' => (
116             is => 'rw',
117             predicate => '_has_output',
118             );
119              
120             has '_e' => (
121             is => 'rw',
122             );
123              
124             has '_n' => (
125             is => 'rw',
126             );
127              
128             =head1 METHODS
129              
130             Generally, errors are fatal. Use L if you want more graceful error handling.
131              
132             =over
133              
134             =item new()
135              
136             Constructor. Takes any of the attributes as arguments. You may optionally call new
137             with either a buffer or a path, and the class will assume that it is the C
138             material.
139              
140             The object automatically attempts to parse C data after instantiation.
141              
142             =back
143              
144             =cut
145              
146             # Support single caller argument
147             around BUILDARGS => sub {
148             my $orig = shift;
149             my $class = shift;
150              
151             if ( @_ == 1 ) {
152             unshift @_, "key";
153             }
154              
155             $class->$orig(@_);
156             };
157              
158             sub BUILD {
159 4     4 0 72 my $self = shift;
160              
161 4         8 my $buf;
162 4 100       38 unless ( $self->key =~ /\n/ ) {
163 2 50       50 if ( -e $self->key ) {
164 2         25 $buf = read_file($self->key, { binmode => ':raw' });
165             }
166             else {
167 0         0 $buf = $self->key;
168             }
169             }
170             else {
171 2         8 $buf = $self->key;
172             }
173              
174 4         363 $buf =~ s/\n//g;
175 4         53 $self->_buffer( (split / /, $buf)[1] );
176              
177 4         19 $self->parse();
178             }
179              
180             =over
181              
182             =item parse()
183              
184             This method takes the Base64 encoded portion of the SSH key, decodes it, and then converts the
185             data inside of it into three components: the id string ('ssh-rsa'), the public exponent ('e'),
186             and the modulus ('n'). By default it looks for the Base64 data inside the instantiated object,
187             but you can optionally pass in a Base64 string.
188              
189             It uses L to hold large integers such as 'n' or 'e'. If you don't have
190             C installed, it will fall back to pure perl automatically, but there will be a speed
191             penalty.
192              
193             Returns a true value on success.
194              
195             =back
196              
197             =cut
198              
199             sub parse {
200 4     4 1 9 my $self = shift;
201 4   33     54 my $b64 = shift || $self->_buffer;
202              
203 4 50       14 confess "I don't have a buffer!" unless $b64;
204              
205 4 50       63 my $blob = decode_base64($b64) or confess "Couldn't Base64 decode buffer";
206              
207 4         9 my @parts;
208 4         9 my $len = length($blob);
209 4         7 my $pos = 0;
210              
211 4         12 while ( $pos < $len ) {
212             # There's probably a clever way to do this, but this works ok.
213 12         129 my $dlen = hex( unpack "H*", substr($blob, $pos, 4) );
214 12         18 $pos += 4;
215 12         23 push @parts, substr($blob, $pos, $dlen);
216 12         27 $pos += $dlen;
217             }
218              
219             # ok $parts[0] should be a string, $parts[1] the exponent 'e', and $parts[2] the modulus 'n'
220              
221 4 50       24 confess "Invalid key type" unless unpack "A*", $parts[0] eq 'ssh-rsa';
222              
223 4         6 my $e;
224 4 50       23 if ( length($parts[1]) <= 4 ) {
225 4         11 $e = hex( unpack "H*", $parts[1] );
226             }
227             else {
228 0         0 $e = Math::BigInt->new( ("0x" . unpack "H*", $parts[1]) );
229             }
230              
231 4         62 my $n = Math::BigInt->new( ("0x" . unpack "H*", $parts[2]) );
232              
233 4         189458 $self->_e( $e );
234 4         37 $self->_n( $n );
235              
236 4         217 return 1;
237             }
238              
239             =over
240              
241             =item format_output()
242              
243             Using a subclass of L, generate a representation of the SSH2 key.
244              
245             Returns a formatted string.
246              
247             =back
248              
249             =cut
250              
251             sub format_output {
252 4     4 1 3350 my $self = shift;
253 4         28 my $format = "Convert::SSH2::Format::" . uc($self->format);
254              
255             try {
256 4     4   169 load_class $format;
257             }
258             catch {
259 0     0   0 confess "Couldn't load formatter $format: $_";
260 4         48 };
261              
262 4         398 my $fmt = $format->new(
263             e => $self->_e,
264             n => $self->_n,
265             );
266              
267 4         44 my $str = $fmt->generate();
268              
269 4         27 $self->_output( $str );
270              
271 4         87 return $str;
272             }
273              
274             =over
275              
276             =item write()
277              
278             Convenience method to write a formatted key representation to a file.
279              
280             Expects a pathname. Automatically calls C if necessary.
281             If the output format has been generated already, it uses a cached version.
282              
283             Returns a true value on success.
284              
285             =back
286              
287             =cut
288              
289             sub write {
290 0     0 1   my $self = shift;
291 0           my $path = shift;
292              
293 0 0         confess "I don't have a path" unless $path;
294              
295 0 0         if ( -e $path ) {
296 0           confess "$path seems to exist already?";
297             }
298              
299 0 0         $self->format_output() unless $self->_has_output;
300              
301 0           write_file( $path, { noclobber => 1, binmode => ":raw" }, $self->_output );
302             }
303              
304             =head1 AUTHOR
305              
306             Mark Allen, C<< >>
307              
308             =head1 BUGS
309              
310             Please report any bugs or feature requests to C, or through
311             the web interface at L. I will be notified, and then you'll
312             automatically be notified of progress on your bug as I make changes.
313              
314             =head1 SUPPORT
315              
316             You can find documentation for this module with the perldoc command.
317              
318             perldoc Convert::SSH2
319              
320             You can also look for information at:
321              
322             =over 4
323              
324             =item * RT: CPAN's request tracker (report bugs here)
325              
326             L
327              
328             =item * AnnoCPAN: Annotated CPAN documentation
329              
330             L
331              
332             =item * CPAN Ratings
333              
334             L
335              
336             =item * MetaCPAN
337              
338             L
339              
340             =item * Git Hub
341              
342             L
343              
344             =back
345              
346             =head1 SEE ALSO
347              
348             L, L
349              
350             L
351              
352             =head1 ACKNOWLEDGEMENTS
353              
354             Mark Cavage
355              
356             =head1 LICENSE AND COPYRIGHT
357              
358             Copyright 2012 Mark Allen.
359              
360             This program is free software; you can redistribute it and/or modify it
361             under the terms of either: the GNU General Public License as published
362             by the Free Software Foundation; or the Artistic License.
363              
364             See http://dev.perl.org/licenses/ for more information.
365              
366             =cut
367              
368             1; # End of Convert::SSH2