File Coverage

blib/lib/Crypt/RSA/Key/Private/SSH.pm
Criterion Covered Total %
statement 144 147 97.9
branch 19 42 45.2
condition 3 5 60.0
subroutine 19 20 95.0
pod 3 3 100.0
total 188 217 86.6


line stmt bran cond sub pod time code
1             package Crypt::RSA::Key::Private::SSH::Buffer;
2 2     2   1198 use strict;
  2         4  
  2         47  
3 2     2   9 use warnings;
  2         4  
  2         49  
4              
5             ## Crypt::RSA::Key::Private::SSH
6             ##
7             ## Copyright (c) 2001, Vipul Ved Prakash. All rights reserved.
8             ## This code is free software; you can redistribute it and/or modify
9             ## it under the same terms as Perl itself.
10              
11 2     2   9 use Crypt::RSA::DataFormat qw( os2ip bitsize i2osp );
  2         4  
  2         79  
12 2     2   843 use Data::Buffer;
  2         2134  
  2         50  
13 2     2   11 use base qw( Data::Buffer );
  2         5  
  2         296  
14              
15             sub get_mp_int {
16 8     8   15 my $buf = shift;
17 8         14 my $off = $buf->{offset};
18 8         23 my $bits = unpack "n", $buf->bytes($off, 2);
19 8         87 my $bytes = int(($bits+7)/8);
20 8         21 my $p = os2ip( $buf->bytes($off+2, $bytes) );
21 8         11389 $buf->{offset} += 2 + $bytes;
22 8         50 $p;
23             }
24              
25             sub put_mp_int {
26 6     6   11 my $buf = shift;
27 6         10 my $int = shift;
28 6         19 my $bits = bitsize($int);
29 6         24 $buf->put_int16($bits);
30 6         45 $buf->put_chars( i2osp($int) );
31             }
32              
33              
34             package Crypt::RSA::Key::Private::SSH;
35 2     2   11 use strict;
  2         6  
  2         30  
36 2     2   8 use warnings;
  2         7  
  2         67  
37 2     2   12 use constant PRIVKEY_ID => "SSH PRIVATE KEY FILE FORMAT 1.1\n";
  2         4  
  2         146  
38 2     2   10 use vars qw( %CIPHERS %CIPHERS_TEXT );
  2         4  
  2         190  
39              
40             # Having to name all the ciphers here is not extensible, but we're stuck
41             # with it given the RSA1 format. I don't think any of this is standardized.
42             # OpenSSH supports only: none, des, 3des, and blowfish here. This set of
43             # numbers below 10 match. Values above 10 are well supported by Perl modules.
44             BEGIN {
45             # CIPHERS : Used by deserialize to map numbers to modules.
46 2     2   35 %CIPHERS = (
47             # 0 = none
48             1 => [ 'IDEA' ],
49             2 => [ 'DES', 'DES_PP' ],
50             3 => [ 'DES_EDE3' ],
51             # From what I can see, none of the 3+ RC4 modules are CBC compatible
52             # 5 => [ 'RC4' ],
53             6 => [ 'Blowfish', 'Blowfish_PP' ],
54             10 => [ 'Twofish2' ],
55             11 => [ 'CAST5', 'CAST5_PP' ],
56             12 => [ 'Rijndael', 'OpenSSL::AES' ],
57             13 => [ 'RC6' ],
58             14 => [ 'Camellia', 'Camellia_PP' ],
59             # Crypt::Serpent is broken and abandonded.
60             );
61             # CIPHERS_TEXT : Used by serialize to map names to modules to numbers
62 2         66 %CIPHERS_TEXT = (
63             'NONE' => 0,
64             'IDEA' => 1,
65             'DES' => 2,
66             'DES_EDE3' => 3,
67             'DES3' => 3,
68             '3DES' => 3,
69             'TRIPLEDES' => 3,
70             # 'RC4' => 5,
71             # 'ARC4' => 5,
72             # 'ARCFOUR' => 5,
73             'BLOWFISH' => 6,
74             'TWOFISH' => 10,
75             'TWOFISH2' => 10,
76             'CAST5' => 11,
77             'CAST5_PP' => 11,
78             'CAST5PP' => 11,
79             'CAST-5' => 11,
80             'CAST-128' => 11,
81             'CAST128' => 11,
82             'RIJNDAEL' => 12,
83             'AES' => 12,
84             'OPENSSL::AES'=>12,
85             'RC6' => 13,
86             'CAMELLIA' => 14,
87             );
88             }
89              
90 2     2   15 use Carp qw( croak );
  2         4  
  2         77  
91 2     2   9 use Data::Buffer;
  2         4  
  2         34  
92 2     2   11 use Crypt::CBC 2.17; # We want a good version
  2         46  
  2         39  
93 2     2   10 use Crypt::RSA::Key::Private;
  2         5  
  2         36  
94 2     2   8 use base qw( Crypt::RSA::Key::Private );
  2         5  
  2         1470  
95              
96             sub deserialize {
97 2     2 1 1052 my($key, %params) = @_;
98             my $passphrase = defined $params{Password} ? $params{Password}
99 2 0       10 : defined $key->Password ? $key->Password
    50          
100             : '';
101 2         5 my $string = $params{String};
102 2 50       7 croak "Must supply String=>'blob' to deserialize" unless defined $string;
103 2 50       18 $string = join('', @$string) if ref($string) eq 'ARRAY';
104              
105             croak "Cowardly refusing to deserialize on top of a hidden key"
106 2 50       14 if $key->{Hidden};
107              
108 2         15 my $buffer = new Crypt::RSA::Key::Private::SSH::Buffer;
109 2         24 $buffer->append($string);
110              
111 2         17 my $id = $buffer->bytes(0, length(PRIVKEY_ID), '');
112 2 50       29 croak "Bad key file format" unless $id eq PRIVKEY_ID;
113 2         7 $buffer->bytes(0, 1, '');
114              
115 2         24 my $cipher_type = $buffer->get_int8;
116 2         39 $buffer->get_int32; ## Reserved data.
117              
118 2         38 $buffer->get_int32; ## Private key bits.
119 2         29 $key->n( $buffer->get_mp_int );
120 2         6 $key->e( $buffer->get_mp_int );
121              
122 2         11 $key->Identity( $buffer->get_str ); ## Comment.
123              
124 2 50       8 if ($cipher_type != 0) {
125 2 50       10 my $cipher_names = $CIPHERS{$cipher_type} or
126             croak "Unknown cipher '$cipher_type' used in key file";
127 2         3 my $cipher_name;
128 2         6 foreach my $name (@$cipher_names) {
129 2         5 my $class = "Crypt::$name";
130 2         13 (my $file = $class) =~ s=::|'=/=g;
131 2 50       5 if ( eval { require "$file.pm"; 1 } ) {
  2         17  
  2         7  
132 2         5 $cipher_name = $name; last;
  2         5  
133             }
134             }
135 2 50       6 if (!defined $cipher_name) {
136 0         0 croak "Unsupported cipher '$cipher_names->[0]': $@";
137             }
138              
139 2         13 my $cipher = Crypt::CBC->new( -key => $passphrase,
140             -cipher => $cipher_name );
141 2         264 my $decrypted =
142             $cipher->decrypt($buffer->bytes($buffer->offset));
143 2         1073 $buffer->empty;
144 2         17 $buffer->append($decrypted);
145             }
146              
147 2         17 my $check1 = $buffer->get_int8;
148 2         33 my $check2 = $buffer->get_int8;
149 2 100 66     28 unless ($check1 == $buffer->get_int8 &&
150             $check2 == $buffer->get_int8) {
151 1         203 croak "Bad passphrase supplied for key file";
152             }
153              
154 1         33 $key->d( $buffer->get_mp_int );
155 1         4 $key->u( $buffer->get_mp_int );
156 1         44 $key->p( $buffer->get_mp_int );
157 1         4 $key->q( $buffer->get_mp_int );
158              
159             # Restore other variables.
160 1         5 $key->phi( ($key->p - 1) * ($key->q - 1) );
161 1         7 $key->dp( $key->d % ($key->p - 1) );
162 1         6 $key->dq( $key->d % ($key->q - 1) );
163             # Our passphrase may be just temporary for the serialization, and have
164             # nothing to do with the key. So don't store it.
165             #$key->{Password} = $passphrase unless defined $key->{Password};
166              
167 1         6 $key;
168             }
169              
170              
171             sub serialize {
172 1     1 1 14 my($key, %params) = @_;
173              
174             # We could reveal it, but (1) what if it was hidden with a different
175             # password, and (2) they may not want to revealed (even if hidden after).
176             croak "Cowardly refusing to serialize a hidden key"
177 1 50       5 if $key->{Hidden};
178              
179             my $passphrase = defined $params{Password} ? $params{Password}
180 1 0       5 : defined $key->Password ? $key->Password
    50          
181             : '';
182             my $cipher_name = defined $params{Cipher} ? $params{Cipher}
183 1 0       6 : defined $key->Cipher ? $key->Cipher
    50          
184             : 'Blowfish';
185              
186             # If they've given us no passphrase, we will be unencrypted.
187 1         2 my $cipher_type = 0;
188              
189 1 50       5 if ($passphrase ne '') {
190 1         6 $cipher_type = $CIPHERS_TEXT{ uc $cipher_name };
191 1 50       4 croak "Unknown cipher: '$cipher_name'" unless defined $cipher_type;
192             }
193              
194 1         38 my $buffer = new Crypt::RSA::Key::Private::SSH::Buffer;
195 1         15 my($check1, $check2);
196 1         89 $buffer->put_int8($check1 = int rand 255);
197 1         18 $buffer->put_int8($check2 = int rand 255);
198 1         8 $buffer->put_int8($check1);
199 1         22 $buffer->put_int8($check2);
200              
201 1         11 $buffer->put_mp_int($key->d);
202 1         15 $buffer->put_mp_int($key->u);
203 1         14 $buffer->put_mp_int($key->p);
204 1         12 $buffer->put_mp_int($key->q);
205              
206 1         14 $buffer->put_int8(0)
207             while $buffer->length % 8;
208              
209 1         38 my $encrypted = new Crypt::RSA::Key::Private::SSH::Buffer;
210 1         25 $encrypted->put_chars(PRIVKEY_ID);
211 1         10 $encrypted->put_int8(0);
212 1         8 $encrypted->put_int8($cipher_type);
213 1         10 $encrypted->put_int32(0);
214              
215 1         12 $encrypted->put_int32(Crypt::RSA::DataFormat::bitsize($key->n));
216 1         10 $encrypted->put_mp_int($key->n);
217 1         14 $encrypted->put_mp_int($key->e);
218 1   50     12 $encrypted->put_str($key->Identity || '');
219              
220 1 50       17 if ($cipher_type) {
221 1         4 my $cipher_names = $CIPHERS{$cipher_type};
222 1         3 my $cipher_name;
223 1         3 foreach my $name (@$cipher_names) {
224 1         3 my $class = "Crypt::$name";
225 1         9 (my $file = $class) =~ s=::|'=/=g;
226 1 50       5 if ( eval { require "$file.pm"; 1 } ) {
  1         628  
  1         793  
227 1         3 $cipher_name = $name; last;
  1         3  
228             }
229             }
230 1 50       5 if (!defined $cipher_name) {
231 0         0 croak "Unsupported cipher '$cipher_names->[0]': $@";
232             }
233              
234 1         12 my $cipher = Crypt::CBC->new( -key => $passphrase,
235             -cipher => $cipher_name );
236 1         163 $encrypted->append( $cipher->encrypt($buffer->bytes) );
237             }
238             else {
239 0         0 $encrypted->append($buffer->bytes);
240             }
241              
242 1         1333 $encrypted->bytes;
243             }
244              
245              
246       0 1   sub hide {}
247              
248             =head1 NAME
249              
250             Crypt::RSA::Key::Private::SSH - SSH Private Key Import
251              
252             =head1 SYNOPSIS
253              
254             Crypt::RSA::Key::Private::SSH is a class derived from
255             Crypt::RSA::Key::Private that provides serialize() and
256             deserialize() methods for SSH keys in the SSH1 format.
257              
258             Alternative formats (SSH2, PEM) are not implemented.
259              
260             =head1 AUTHOR
261              
262             Vipul Ved Prakash, Email@vipul.netE wrote the original version.
263              
264             Dana Jacobsen Edana@acm.orgE wrote the new version.
265              
266             =cut
267              
268              
269              
270              
271             1;