Codebase list libcryptx-perl / 227045f
new module Crypt::Misc Karel Miko 8 years ago
12 changed file(s) with 319 addition(s) and 172 deletion(s). Raw diff Collapse all Expand all
1313 - maybe: add enc_b64/dec_b64 + enc_b64u/dec_b64u + enc_b32/dec_b32
1414 - maybe: x509_rsa_pubkey + x509_rsa_pubkey_alg
1515
16 0.028_03 2016/03/29
16 0.028_04 2016/04/04
1717 - NEW module: Math::BigInt::LTM
18 - NEW module: Crypt::Misc
19 - TODO: pod for Crypt::Misc
20 - TODO: fix - Please specify prototyping behavior for CryptX.xs (see perlxs manual)
1821
1922 0.028 2016/03/23
2023 - IMPORTANT: switch from Module::Build to ExtUtils::MakeMaker
0 package Crypt::Misc;
1
2 use strict;
3 use warnings;
4
5 require Exporter; our @ISA = qw(Exporter); ### use Exporter 5.57 'import';
6 use Carp 'croak';
7 our %EXPORT_TAGS = ( all => [qw(encode_b64 decode_b64 encode_b64u decode_b64u pem_to_der der_to_pem read_rawfile write_rawfile slow_eq)] );
8 our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
9 our @EXPORT = qw();
10
11 use Carp 'carp';
12 use CryptX;
13 use Crypt::Digest 'digest_data';
14 use Crypt::Mode::CBC;
15 use Crypt::Mode::CFB;
16 use Crypt::Mode::ECB;
17 use Crypt::Mode::OFB;
18 use Crypt::Cipher;
19 use Crypt::PRNG 'random_bytes';
20
21 sub encode_b64 {
22 CryptX::_encode_base64(@_);
23 }
24
25 sub decode_b64 {
26 CryptX::_decode_base64(@_);
27 }
28
29 sub encode_b64u {
30 CryptX::_encode_base64url(@_);
31 }
32
33 sub decode_b64u {
34 CryptX::_decode_base64url(@_);
35 }
36
37 sub pem_to_der {
38 my ($data, $password) = @_;
39
40 my ($begin, $obj1, $content, $end, $obj2) = $data =~ m/(----[- ]BEGIN ([^\r\n\-]+KEY)[ -]----)(.*?)(----[- ]END ([^\r\n\-]+)[ -]----)/s;
41 return undef unless $content;
42
43 $content =~ s/^\s+//sg;
44 $content =~ s/\s+$//sg;
45 $content =~ s/\r\n/\n/sg; # CR-LF >> LF
46 $content =~ s/\r/\n/sg; # CR >> LF
47 $content =~ s/\\\n//sg; # \ + LF
48
49 my ($headers, undef, $b64) = $content =~ /^(([^:]+:.*?\n)*)(.*)$/s;
50 return undef unless $b64;
51
52 my $binary = decode_b64($b64);
53 return undef unless $binary;
54
55 my ($ptype, $cipher_name, $iv_hex);
56 for my $h (split /\n/, ($headers||'')) {
57 my ($k, $v) = split /:\s*/, $h, 2;
58 $ptype = $v if $k eq 'Proc-Type';
59 ($cipher_name, $iv_hex) = $v =~ /^\s*(.*?)\s*,\s*([0-9a-fA-F]+)\s*$/ if $k eq 'DEK-Info';
60 }
61 if ($cipher_name && $iv_hex && $ptype && $ptype eq '4,ENCRYPTED') {
62 croak "FATAL: encrypted PEM but no password provided" unless defined $password;
63 my $iv = pack("H*", $iv_hex);
64 my ($mode, $klen) = _name2mode($cipher_name);
65 my $key = _password2key($password, $klen, $iv, 'MD5');
66 return $mode->decrypt($binary, $key, $iv);
67 }
68 return $binary;
69 }
70
71 sub der_to_pem {
72 my ($data, $header_name, $password, $cipher_name) = @_;
73 my $content = $data;
74 my @headers;
75
76 if ($password) {
77 $cipher_name ||= 'AES-256-CBC';
78 my ($mode, $klen, $ilen) = _name2mode($cipher_name);
79 my $iv = random_bytes($ilen);
80 my $key = _password2key($password, $klen, $iv, 'MD5');
81 $content = $mode->encrypt($data, $key, $iv);
82 push @headers, 'Proc-Type: 4,ENCRYPTED', "DEK-Info: ".uc($cipher_name).",".unpack("H*", $iv);
83 }
84
85 my $pem = "-----BEGIN $header_name-----\n";
86 if (@headers) {
87 $pem .= "$_\n" for @headers;
88 $pem .= "\n";
89 }
90 my @l = encode_b64($content) =~ /.{1,64}/g;
91 $pem .= join("\n", @l) . "\n";
92 $pem .= "-----END $header_name-----\n";
93 return $pem;
94 }
95
96 sub read_rawfile {
97 my $f = shift;
98 croak "FATAL: read_rawfile() non-existing file '$f'" unless -f $f;
99 open my $fh, "<", $f or croak "FATAL: read_rawfile() cannot open file '$f': $!";
100 binmode $fh;
101 return do { local $/; <$fh> };
102 }
103
104 sub write_rawfile {
105 # write_rawfile($filename, $data);
106 croak "FATAL: write_rawfile() no data" unless defined $_[1];
107 open my $fh, ">", $_[0] or croak "FATAL: write_rawfile() cannot open file '$_[0]': $!";
108 binmode $fh;
109 print $fh $_[1] or croak "FATAL: write_rawfile() cannot write to '$_[0]': $!";
110 close $fh or croak "FATAL: write_rawfile() cannot close '$_[0]': $!";
111 return;
112 }
113
114 sub slow_eq {
115 my ($a, $b) = @_;
116 return unless defined $a && defined $b;
117 my $diff = length $a ^ length $b;
118 for(my $i = 0; $i < length $a && $i < length $b; $i++) {
119 $diff |= ord(substr $a, $i) ^ ord(substr $b, $i);
120 }
121 return $diff == 0;
122 }
123
124 ### private functions
125
126 sub _name2mode {
127 my $cipher_name = uc(shift);
128 my %trans = ( 'DES-EDE3' => 'DES_EDE' );
129
130 my ($cipher, undef, $klen, $mode) = $cipher_name =~ /^(AES|CAMELLIA|DES|DES-EDE3|SEED)(-(\d+))?-(CBC|CFB|ECB|OFB)$/i;
131 croak "FATAL: unsupported cipher '$cipher_name'" unless $cipher && $mode;
132 $cipher = $trans{$cipher} || $cipher;
133 $klen = $klen ? int($klen/8) : Crypt::Cipher::min_keysize($cipher);
134 my $ilen = Crypt::Cipher::blocksize($cipher);
135 croak "FATAL: unsupported cipher '$cipher_name'" unless $klen && $ilen;
136
137 return (Crypt::Mode::CBC->new($cipher), $klen, $ilen) if $mode eq 'CBC';
138 return (Crypt::Mode::CFB->new($cipher), $klen, $ilen) if $mode eq 'CFB';
139 return (Crypt::Mode::ECB->new($cipher), $klen, $ilen) if $mode eq 'ECB';
140 return (Crypt::Mode::OFB->new($cipher), $klen, $ilen) if $mode eq 'OFB';
141 }
142
143 sub _password2key {
144 my ($password, $klen, $iv, $hash) = @_;
145 my $salt = substr($iv, 0, 8);
146 my $key = '';
147 while (length($key) < $klen) {
148 $key .= digest_data($hash, $key . $password . $salt);
149 }
150 return substr($key, 0, $klen);
151 }
152
153 1;
154
155 =pod
156
157 =head1 NAME
158
159 Crypt::Misc - miscellaneous functions related to (or used by) CryptX
160
161 =head1 SYNOPSIS
162
163 use Crypt::Misc ':all';
164
165
166 =head1 DESCRIPTION
167
168 xxx
169
170 =head1 METHODS
171
172 =head2 encode_b64
173
174 xxx
175
176 =head2 decode_b64
177
178 xxx
179
180 =head2 read_rawfile
181
182 xxx
183
184 =head2 write_rawfile
185
186 xxx
187
188 =head2 slow_eq
189
190 xxx
191
192 =head2 encode_b64u
193
194 xxx
195
196 =head2 decode_b64u
197
198 xxx
199
200 =head2 pem_to_der
201
202 xxx
203
204 =head2 der_to_pem
205
206 xxx
207
208 =head1 SEE ALSO
209
210 =over
211
212 =item * L<CryptX|CryptX>
213
214 =back
215
216 =cut
217
218 __END__
77 our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
88 our @EXPORT = qw();
99
10 use Carp;
1011 use CryptX;
11 use Crypt::PK;
1212 use Crypt::Digest 'digest_data';
13 use Carp;
13 use Crypt::Misc qw(read_rawfile encode_b64u decode_b64u encode_b64 decode_b64);
1414
1515 sub new {
1616 my ($class, $f) = @_;
2727 $data = $$key;
2828 }
2929 elsif (-f $key) {
30 $data = Crypt::PK::_slurp_file($key);
30 $data = read_rawfile($key);
3131 }
3232 else {
3333 croak "FATAL: non-existing file '$key'";
77 our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
88 our @EXPORT = qw();
99
10 use CryptX qw( _encode_base64 _decode_base64 );
10 use Carp;
11 use CryptX qw(_encode_json _decode_json);
12 use Crypt::Digest 'digest_data';
13 use Crypt::Misc qw(read_rawfile encode_b64u decode_b64u encode_b64 decode_b64 pem_to_der der_to_pem);
1114 use Crypt::PK;
12 use Crypt::Digest 'digest_data';
13 use Carp;
1415
1516 sub new {
1617 my ($class, $f, $p) = @_;
2324 my ($self, $type, $password, $cipher) = @_;
2425 my $key = $self->export_key_der($type||'');
2526 return unless $key;
26 return Crypt::PK::_asn1_to_pem($key, "DSA PRIVATE KEY", $password, $cipher) if $type eq 'private';
27 return Crypt::PK::_asn1_to_pem($key, "DSA PUBLIC KEY") if $type eq 'public';
28 return Crypt::PK::_asn1_to_pem($key, "PUBLIC KEY") if $type eq 'public_x509';
27 return der_to_pem($key, "DSA PRIVATE KEY", $password, $cipher) if $type eq 'private';
28 return der_to_pem($key, "DSA PUBLIC KEY") if $type eq 'public';
29 return der_to_pem($key, "PUBLIC KEY") if $type eq 'public_x509';
2930 }
3031
3132 sub import_key {
4546 $data = $$key;
4647 }
4748 elsif (-f $key) {
48 $data = Crypt::PK::_slurp_file($key);
49 $data = read_rawfile($key);
4950 }
5051 else {
5152 croak "FATAL: non-existing file '$key'";
5354 croak "FATAL: invalid key data" unless $data;
5455
5556 if ($data =~ /-----BEGIN (DSA PRIVATE|DSA PUBLIC|PRIVATE|PUBLIC) KEY-----(.*?)-----END/sg) {
56 $data = Crypt::PK::_pem_to_binary($data, $password);
57 $data = pem_to_der($data, $password);
5758 return $self->_import($data);
5859 }
5960 elsif ($data =~ /---- BEGIN SSH2 PUBLIC KEY ----(.*?)---- END SSH2 PUBLIC KEY ----/sg) {
60 $data = Crypt::PK::_pem_to_binary($data);
61 $data = pem_to_der($data);
6162 my ($typ, $p, $q, $g, $y) = Crypt::PK::_ssh_parse($data);
6263 return $self->_import_hex(unpack('H*',$p), unpack('H*',$q), unpack('H*',$g), undef, unpack('H*',$y)) if $typ && $p && $q && $g && $y && $typ eq 'ssh-dss';
6364 }
6465 elsif ($data =~ /ssh-dss\s+(\S+)/) {
65 $data = _decode_base64("$1");
66 $data = decode_b64("$1");
6667 my ($typ, $p, $q, $g, $y) = Crypt::PK::_ssh_parse($data);
6768 return $self->_import_hex(unpack('H*',$p), unpack('H*',$q), unpack('H*',$g), undef, unpack('H*',$y)) if $typ && $p && $q && $g && $y && $typ eq 'ssh-dss';
6869 }
77 our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
88 our @EXPORT = qw();
99
10 use CryptX qw( _encode_base64url _decode_base64url _encode_base64 _decode_base64 _decode_json _encode_json );
10 use Carp;
11 use CryptX qw(_encode_json _decode_json);
12 use Crypt::Digest 'digest_data';
13 use Crypt::Misc qw(read_rawfile encode_b64u decode_b64u encode_b64 decode_b64 pem_to_der der_to_pem);
1114 use Crypt::PK;
12 use Crypt::Digest 'digest_data';
13 use Carp;
1415
1516 our %curve = (
1617 ### http://www.ecc-brainpool.org/download/Domain-parameters.pdf (v1.0 19.10.2005)
432433 my ($self, $type, $password, $cipher) = @_;
433434 my $key = $self->export_key_der($type||'');
434435 return unless $key;
435 return Crypt::PK::_asn1_to_pem($key, "EC PRIVATE KEY", $password, $cipher) if $type eq 'private';
436 return Crypt::PK::_asn1_to_pem($key, "PUBLIC KEY") if $type eq 'public' || $type eq 'public_compressed';
436 return der_to_pem($key, "EC PRIVATE KEY", $password, $cipher) if $type eq 'private';
437 return der_to_pem($key, "PUBLIC KEY") if $type eq 'public' || $type eq 'public_compressed';
437438 }
438439
439440 sub export_key_jwk {
454455 # but they are used in https://tools.ietf.org/html/rfc7517#appendix-A.2
455456 my $hash = {
456457 kty => "EC", crv=>$curve,
457 x => _encode_base64url(pack("H*", $kh->{pub_x})),
458 y => _encode_base64url(pack("H*", $kh->{pub_y})),
459 d => _encode_base64url(pack("H*", $kh->{k})),
458 x => encode_b64u(pack("H*", $kh->{pub_x})),
459 y => encode_b64u(pack("H*", $kh->{pub_y})),
460 d => encode_b64u(pack("H*", $kh->{k})),
460461 };
461462 return $wanthash ? $hash : _encode_json($hash);
462463 }
467468 }
468469 my $hash = {
469470 kty => "EC", crv=>$curve,
470 x => _encode_base64url(pack("H*", $kh->{pub_x})),
471 y => _encode_base64url(pack("H*", $kh->{pub_y})),
471 x => encode_b64u(pack("H*", $kh->{pub_x})),
472 y => encode_b64u(pack("H*", $kh->{pub_y})),
472473 };
473474 return $wanthash ? $hash : _encode_json($hash);
474475 }
490491 # hash with items corresponding to JSON Web Key (JWK)
491492 $key = {%$key}; # make a copy as we will modify it
492493 for (qw/x y d/) {
493 $key->{$_} = eval { unpack("H*", _decode_base64url($key->{$_})) } if exists $key->{$_};
494 $key->{$_} = eval { unpack("H*", decode_b64u($key->{$_})) } if exists $key->{$_};
494495 }
495496 if (my $curve = $jwkcrv{$key->{crv}}) {
496497 return $self->_import_hex($key->{x}, $key->{y}, $key->{d}, $curve);
504505 $data = $$key;
505506 }
506507 elsif (-f $key) {
507 $data = Crypt::PK::_slurp_file($key);
508 $data = read_rawfile($key);
508509 }
509510 else {
510511 croak "FATAL: non-existing file '$key'";
512513 croak "FATAL: invalid key data" unless $data;
513514
514515 if ($data =~ /-----BEGIN (EC PRIVATE|EC PUBLIC|PUBLIC) KEY-----(.*?)-----END/sg) {
515 $data = Crypt::PK::_pem_to_binary($data, $password);
516 $data = pem_to_der($data, $password);
516517 return $self->_import($data);
517518 }
518519 elsif ($data =~ /-----BEGIN PRIVATE KEY-----(.*?)-----END/sg) {
519 $data = Crypt::PK::_pem_to_binary($data, $password);
520 $data = pem_to_der($data, $password);
520521 return $self->_import_pkcs8($data);
521522 }
522523 elsif ($data =~ /-----BEGIN ENCRYPTED PRIVATE KEY-----(.*?)-----END/sg) {
529530 my $h = _decode_json($json);
530531 if ($h && $h->{kty} eq "EC") {
531532 for (qw/x y d/) {
532 $h->{$_} = eval { unpack("H*", _decode_base64url($h->{$_})) } if exists $h->{$_};
533 $h->{$_} = eval { unpack("H*", decode_b64u($h->{$_})) } if exists $h->{$_};
533534 }
534535 if (my $curve = $jwkcrv{$h->{crv}}) {
535536 return $self->_import_hex($h->{x}, $h->{y}, $h->{d}, $curve);
537538 }
538539 }
539540 elsif ($data =~ /---- BEGIN SSH2 PUBLIC KEY ----(.*?)---- END SSH2 PUBLIC KEY ----/sg) {
540 $data = Crypt::PK::_pem_to_binary($data);
541 $data = pem_to_der($data);
541542 my ($typ, $skip, $pubkey) = Crypt::PK::_ssh_parse($data);
542543 return $self->import_key_raw($pubkey, "$2") if $pubkey && $typ =~ /^ecdsa-(.+?)-(.*)$/;
543544 }
544545 elsif ($data =~ /(ecdsa-\S+)\s+(\S+)/) {
545 $data = _decode_base64("$2");
546 $data = decode_b64("$2");
546547 my ($typ, $skip, $pubkey) = Crypt::PK::_ssh_parse($data);
547548 return $self->import_key_raw($pubkey, "$2") if $pubkey && $typ =~ /^ecdsa-(.+?)-(.*)$/;
548549 }
77 our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
88 our @EXPORT = qw();
99
10 use CryptX qw( _encode_base64url _decode_base64url _encode_base64 _decode_base64 _encode_json _decode_json);
10 use Carp;
11 use CryptX qw(_encode_json _decode_json);
12 use Crypt::Digest 'digest_data';
13 use Crypt::Misc qw(read_rawfile encode_b64u decode_b64u encode_b64 decode_b64 pem_to_der der_to_pem);
1114 use Crypt::PK;
12 use Crypt::Digest 'digest_data';
13 use Carp;
1415
1516 sub new {
1617 my ($class, $f, $p) = @_;
2728 # PKCS#1 RSAPrivateKey** (PEM header: BEGIN RSA PRIVATE KEY)
2829 # PKCS#8 PrivateKeyInfo* (PEM header: BEGIN PRIVATE KEY)
2930 # PKCS#8 EncryptedPrivateKeyInfo** (PEM header: BEGIN ENCRYPTED PRIVATE KEY)
30 return Crypt::PK::_asn1_to_pem($key, "RSA PRIVATE KEY", $password, $cipher) if $type eq 'private';
31 return der_to_pem($key, "RSA PRIVATE KEY", $password, $cipher) if $type eq 'private';
3132
3233 # PKCS#1 RSAPublicKey* (PEM header: BEGIN RSA PUBLIC KEY)
33 return Crypt::PK::_asn1_to_pem($key, "RSA PUBLIC KEY") if $type eq 'public';
34 return der_to_pem($key, "RSA PUBLIC KEY") if $type eq 'public';
3435 # X.509 SubjectPublicKeyInfo** (PEM header: BEGIN PUBLIC KEY)
35 return Crypt::PK::_asn1_to_pem($key, "PUBLIC KEY") if $type eq 'public_x509';
36 return der_to_pem($key, "PUBLIC KEY") if $type eq 'public_x509';
3637 }
3738
3839 sub export_key_jwk {
4546 }
4647 my $hash = {
4748 kty => "RSA",
48 n => _encode_base64url(pack("H*", $kh->{N})),
49 e => _encode_base64url(pack("H*", $kh->{e})),
50 d => _encode_base64url(pack("H*", $kh->{d})),
51 p => _encode_base64url(pack("H*", $kh->{p})),
52 q => _encode_base64url(pack("H*", $kh->{q})),
53 dp => _encode_base64url(pack("H*", $kh->{dP})),
54 dq => _encode_base64url(pack("H*", $kh->{dQ})),
55 qi => _encode_base64url(pack("H*", $kh->{qP})),
49 n => encode_b64u(pack("H*", $kh->{N})),
50 e => encode_b64u(pack("H*", $kh->{e})),
51 d => encode_b64u(pack("H*", $kh->{d})),
52 p => encode_b64u(pack("H*", $kh->{p})),
53 q => encode_b64u(pack("H*", $kh->{q})),
54 dp => encode_b64u(pack("H*", $kh->{dP})),
55 dq => encode_b64u(pack("H*", $kh->{dQ})),
56 qi => encode_b64u(pack("H*", $kh->{qP})),
5657 };
5758 return $wanthash ? $hash : _encode_json($hash);
5859 }
6364 }
6465 my $hash = {
6566 kty => "RSA",
66 n => _encode_base64url(pack("H*", $kh->{N})),
67 e => _encode_base64url(pack("H*", $kh->{e})),
67 n => encode_b64u(pack("H*", $kh->{N})),
68 e => encode_b64u(pack("H*", $kh->{e})),
6869 };
6970 return $wanthash ? $hash : _encode_json($hash);
7071 }
8384 if ($key->{n} && $key->{e} && $key->{kty} && $key->{kty} eq "RSA") {
8485 # hash with items corresponding to JSON Web Key (JWK)
8586 for (qw/n e d p q dp dq qi/) {
86 $key->{$_} = eval { unpack("H*", _decode_base64url($key->{$_})) } if exists $key->{$_};
87 $key->{$_} = eval { unpack("H*", decode_b64u($key->{$_})) } if exists $key->{$_};
8788 }
8889 return $self->_import_hex($key->{n}, $key->{e}, $key->{d}, $key->{p}, $key->{q}, $key->{dp}, $key->{dq}, $key->{qi});
8990 }
9596 $data = $$key;
9697 }
9798 elsif (-f $key) {
98 $data = Crypt::PK::_slurp_file($key);
99 $data = read_rawfile($key);
99100 }
100101 else {
101102 croak "FATAL: non-existing file '$key'";
106107 # PKCS#1 RSAPublicKey (PEM header: BEGIN RSA PUBLIC KEY)
107108 # PKCS#1 RSAPrivateKey (PEM header: BEGIN RSA PRIVATE KEY)
108109 # X.509 SubjectPublicKeyInfo (PEM header: BEGIN PUBLIC KEY)
109 $data = Crypt::PK::_pem_to_binary($data, $password);
110 $data = pem_to_der($data, $password);
110111 return $self->_import($data) if $data;
111112 }
112113 elsif ($data =~ /-----BEGIN PRIVATE KEY-----(.*?)-----END/sg) {
113114 # PKCS#8 PrivateKeyInfo (PEM header: BEGIN PRIVATE KEY)
114 $data = Crypt::PK::_pem_to_binary($data, $password);
115 $data = pem_to_der($data, $password);
115116 return $self->_import_pkcs8($data) if $data;
116117 }
117118 elsif ($data =~ /-----BEGIN ENCRYPTED PRIVATE KEY-----(.*?)-----END/sg) {
124125 my $h = _decode_json($json);
125126 if ($h && $h->{kty} eq "RSA") {
126127 for (qw/n e d p q dp dq qi/) {
127 $h->{$_} = eval { unpack("H*", _decode_base64url($h->{$_})) } if exists $h->{$_};
128 $h->{$_} = eval { unpack("H*", decode_b64u($h->{$_})) } if exists $h->{$_};
128129 }
129130 return $self->_import_hex($h->{n}, $h->{e}, $h->{d}, $h->{p}, $h->{q}, $h->{dp}, $h->{dq}, $h->{qi}) if $h->{n} && $h->{e};
130131 }
131132 }
132133 elsif ($data =~ /---- BEGIN SSH2 PUBLIC KEY ----(.*?)---- END SSH2 PUBLIC KEY ----/sg) {
133 $data = Crypt::PK::_pem_to_binary($data);
134 $data = pem_to_der($data);
134135 my ($typ, $N, $e) = Crypt::PK::_ssh_parse($data);
135136 return $self->_import_hex(unpack("H*", $e), unpack("H*", $N)) if $typ && $e && $N && $typ eq 'ssh-rsa';
136137 }
137138 elsif ($data =~ /ssh-rsa\s+(\S+)/) {
138 $data = _decode_base64("$1");
139 $data = decode_b64("$1");
139140 my ($typ, $N, $e) = Crypt::PK::_ssh_parse($data);
140141 return $self->_import_hex(unpack("H*", $e), unpack("H*", $N)) if $typ && $e && $N && $typ eq 'ssh-rsa';
141142 }
33 use warnings;
44
55 use Carp;
6 use CryptX qw( _encode_base64 _decode_base64 );
7 use Crypt::Digest qw(digest_data);
8 use Crypt::Mode::CBC;
9 use Crypt::Mode::CFB;
10 use Crypt::Mode::ECB;
11 use Crypt::Mode::OFB;
12 use Crypt::Cipher;
13 use Crypt::PRNG 'random_bytes';
14
15 sub _slurp_file {
16 my $f = shift;
17 croak "FATAL: non-existing file '$f'" unless -f $f;
18 local $/ = undef;
19 open my $fh, "<", $f or croak "FATAL: couldn't open file: $!";
20 binmode $fh;
21 my $string = readline($fh);
22 close $fh;
23 return $string;
24 }
25
26 sub _name2mode {
27 my $cipher_name = uc(shift);
28 my %trans = ( 'DES-EDE3' => 'DES_EDE' );
29
30 my ($cipher, undef, $klen, $mode) = $cipher_name =~ /^(AES|CAMELLIA|DES|DES-EDE3|SEED)(-(\d+))?-(CBC|CFB|ECB|OFB)$/i;
31 croak "FATAL: unsupported cipher '$cipher_name'" unless $cipher && $mode;
32 $cipher = $trans{$cipher} || $cipher;
33 $klen = $klen ? int($klen/8) : Crypt::Cipher::min_keysize($cipher);
34 my $ilen = Crypt::Cipher::blocksize($cipher);
35 croak "FATAL: unsupported cipher '$cipher_name'" unless $klen && $ilen;
36
37 return (Crypt::Mode::CBC->new($cipher), $klen, $ilen) if $mode eq 'CBC';
38 return (Crypt::Mode::CFB->new($cipher), $klen, $ilen) if $mode eq 'CFB';
39 return (Crypt::Mode::ECB->new($cipher), $klen, $ilen) if $mode eq 'ECB';
40 return (Crypt::Mode::OFB->new($cipher), $klen, $ilen) if $mode eq 'OFB';
41 }
42
43 sub _password2key {
44 my ($password, $klen, $iv, $hash) = @_;
45 my $salt = substr($iv, 0, 8);
46 my $key = '';
47 while (length($key) < $klen) {
48 $key .= digest_data($hash, $key . $password . $salt);
49 }
50 return substr($key, 0, $klen);
51 }
52
53 sub _pem_to_binary {
54 my ($data, $password) = @_;
55
56 my ($begin, $obj1, $content, $end, $obj2) = $data =~ m/(----[- ]BEGIN ([^\r\n\-]+KEY)[ -]----)(.*?)(----[- ]END ([^\r\n\-]+)[ -]----)/s;
57 return undef unless $content;
58 $content =~ s/^\s+//sg;
59 $content =~ s/\s+$//sg;
60 $content =~ s/\r\n/\n/sg; # CR-LF >> LF
61 $content =~ s/\r/\n/sg; # CR >> LF
62 $content =~ s/\\\n//sg; # \ + LF
63
64 my ($headers, undef, $b64) = $content =~ /^(([^:]+:.*?\n)*)(.*)$/s;
65 return undef unless $b64;
66 my $binary = _decode_base64($b64);
67 return undef unless $binary;
68
69 my ($ptype, $cipher_name, $iv_hex);
70 for my $h (split /\n/, ($headers||'')) {
71 my ($k, $v) = split /:\s*/, $h, 2;
72 $ptype = $v if $k eq 'Proc-Type';
73 ($cipher_name, $iv_hex) = $v =~ /^\s*(.*?)\s*,\s*([0-9a-fA-F]+)\s*$/ if $k eq 'DEK-Info';
74 }
75
76 if ($cipher_name && $iv_hex && $ptype && $ptype eq '4,ENCRYPTED') {
77 croak "FATAL: encrypted PEM but no password provided" unless defined $password;
78 my $iv = pack("H*", $iv_hex);
79 my ($mode, $klen) = _name2mode($cipher_name);
80 my $key = _password2key($password, $klen, $iv, 'MD5');
81 return $mode->decrypt($binary, $key, $iv);
82 }
83
84 return $binary;
85 }
86
87 sub _asn1_to_pem {
88 my ($data, $header_name, $password, $cipher_name) = @_;
89 my $content = $data;
90 my @headers;
91
92 if ($password) {
93 $cipher_name ||= 'AES-256-CBC';
94 my ($mode, $klen, $ilen) = _name2mode($cipher_name);
95 my $iv = random_bytes($ilen);
96 my $key = _password2key($password, $klen, $iv, 'MD5');
97 $content = $mode->encrypt($data, $key, $iv);
98 push @headers, 'Proc-Type: 4,ENCRYPTED', "DEK-Info: ".uc($cipher_name).",".unpack("H*", $iv);
99 }
100
101 my $rv = "-----BEGIN $header_name-----\n";
102 if (@headers) {
103 $rv .= "$_\n" for @headers;
104 $rv .= "\n";
105 }
106 my @l = _encode_base64($content) =~ /.{1,64}/g;
107 $rv .= join("\n", @l) . "\n";
108 $rv .= "-----END $header_name-----\n";
109 }
1106
1117 sub _ssh_parse {
1128 my $raw = shift;
77 our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
88 our @EXPORT = qw();
99
10 use CryptX qw(_encode_base64 _encode_base64url);
10 #BEWARE: cannot use Crypt::Misc qw(encode_b64 encode_b64u);
11 use CryptX;
1112
1213 sub _trans_prng_name {
1314 my $name = shift;
3132
3233 sub bytes_hex { return unpack("H*", shift->bytes(shift)) }
3334
34 sub bytes_b64 { return _encode_base64(shift->bytes(shift)) }
35
36 sub bytes_b64u { return _encode_base64url(shift->bytes(shift)) }
35 sub bytes_b64 { return CryptX::_encode_base64(shift->bytes(shift)) }
36
37 sub bytes_b64u { return CryptX::_encode_base64url(shift->bytes(shift)) }
3738
3839 sub string {
3940 my ($self, $len) = @_;
22 use strict;
33 use warnings ;
44
5 our $VERSION = '0.028_03';
5 our $VERSION = '0.028_04';
66
77 use base qw(Exporter);
8 our @EXPORT_OK = qw( _encode_base64url _decode_base64url _encode_base64 _decode_base64 _decode_json _encode_json);
8 our @EXPORT_OK = qw( _decode_json _encode_json);
99
1010 require XSLoader;
1111 XSLoader::load('CryptX', $VERSION);
44
55 plan skip_all => "File::Find not installed" unless eval { require File::Find };
66 plan skip_all => "Test::Pod not installed" unless eval { require Test::Pod };
7 plan tests => 73;
7 plan tests => 74;
88
99 my @files;
1010 File::Find::find({ wanted=>sub { push @files, $_ if /\.pm$/ }, no_chdir=>1 }, 'lib');
0 use strict;
1 use warnings;
2 use Test::More tests => 15;
3
4 use Crypt::Misc qw(encode_b64 decode_b64 encode_b64u decode_b64u pem_to_der der_to_pem read_rawfile write_rawfile slow_eq);
5
6 is(encode_b64(pack("H*","702fad4215a04a657f011d3ea5711879c696788c91d2")), "cC+tQhWgSmV/AR0+pXEYecaWeIyR0g==", "encode_b64");
7 is(unpack("H*", decode_b64("cC+tQhWgSmV/AR0+pXEYecaWeIyR0g==")), "702fad4215a04a657f011d3ea5711879c696788c91d2", "decode_b64");
8 is(unpack("H*", decode_b64("cC+tQhWgSmV/AR0+pXEYecaWeIyR0g")), "702fad4215a04a657f011d3ea5711879c696788c91d2", "decode_b64/relaxed1");
9 is(unpack("H*", decode_b64("cC+tQh\nWgSmV/A\nR0+pXEYec\naWeIyR0g")), "702fad4215a04a657f011d3ea5711879c696788c91d2", "decode_b64/relaxed2");
10 is(unpack("H*", decode_b64("cC+tQh\r\nWgSmV/A\r\nR0+pXEYec\r\naWeIyR0g")), "702fad4215a04a657f011d3ea5711879c696788c91d2", "decode_b64/relaxed3");
11 is(unpack("H*", decode_b64("cC+tQh WgSmV/A R0+pXEYec aWeIyR0g")), "702fad4215a04a657f011d3ea5711879c696788c91d2", "decode_b64/relaxed4");
12 is(unpack("H*", decode_b64("cC+tQh\tWgSmV/A\tR0+pXEYec\taWeIyR0g")), "702fad4215a04a657f011d3ea5711879c696788c91d2", "decode_b64/relaxed5");
13
14 is(encode_b64u(pack("H*","702fad4215a04a657f011d3ea5711879c696788c91d2")), "cC-tQhWgSmV_AR0-pXEYecaWeIyR0g", "encode_b64u");
15 is(unpack("H*", decode_b64u("cC-tQhWgSmV_AR0-pXEYecaWeIyR0g")), "702fad4215a04a657f011d3ea5711879c696788c91d2", "decode_b64u");
16 is(unpack("H*", decode_b64u("cC-tQhWgSmV_AR0-pXEYecaWeIyR0g==")), "702fad4215a04a657f011d3ea5711879c696788c91d2", "decode_b64u/padded");
17 is(unpack("H*", decode_b64u("cC-tQh\nWgSmV_A\nR0-pXEYec\naWeIyR0g")), "702fad4215a04a657f011d3ea5711879c696788c91d2", "decode_b64u/relaxed1");
18 is(unpack("H*", decode_b64u("cC-tQh\r\nWgSmV_A\r\nR0-pXEYec\r\naWeIyR0g")), "702fad4215a04a657f011d3ea5711879c696788c91d2", "decode_b64u/relaxed2");
19 is(unpack("H*", decode_b64u("cC-tQh WgSmV_A R0-pXEYec aWeIyR0g")), "702fad4215a04a657f011d3ea5711879c696788c91d2", "decode_b64u/relaxed3");
20 is(unpack("H*", decode_b64u("cC-tQh\tWgSmV_A\tR0-pXEYec\taWeIyR0g")), "702fad4215a04a657f011d3ea5711879c696788c91d2", "decode_b64u/relaxed4");
21
22 write_rawfile("tmp.$$.file", "a\nb\r\nc\rd\te");
23 ok(slow_eq(read_rawfile("tmp.$$.file"), "a\nb\r\nc\rd\te"), "slow_eq + read_rawfile + write_rawfile");
24 unlink "tmp.$$.file";
22
33 use Test::More tests => 246;
44 use Crypt::PK::RSA;
5 use CryptX qw(_decode_base64);
5 use Crypt::Misc qw(decode_b64);
66
77 my $data = [ #test vectors generated by: OpenSSL 1.0.1e 11 Feb 2013
88 {ID=>'key-512-1',SIZE=>512,PRI=>'632B6C7F984EA022C2B3D507A3A0886678F8794B151E16EA696883B0305B2A984EB6CBE3CC56025852CCE742A51655A8CADE5BE73EBE75CEEF70CAA72F82A801',PUB=>'C5920D48C0DB954D7834FA7C74DB7C91714C97EF2DA4B35DA302D75A0E08AD3B7C05296533C71DF5045F66DDD2E1F9A0D487CDAFE4137214F7764D2BE25D0D29',SIGSHA1=>'v/ZzE0JT8xakMsHhh2qVcEm1r/odXZAfSr+JK/B2trzq3UrzUwYfWgM7NtO2L6kU0wyNCVTy+gMpGECfaAEiqA==',SIGSHA256=>'pjOjBMaGXx7XZ+uNgfszCD1yy9WeLwgdM/1eP987j+s6hGaIjHKOm2PAxXZ6cEqYi1QQsMybe3F9UhL2b5ws9A==',SIGSHA512=>'',ENC=>'mQw7zaZdwthCgpBdV/BxdzMp9yUMOSFHogB7DvwCYztRlqlc8bXnJUsa6yasLARaN2rbb3dyIN+apNW+wZkvrg==',PRIDER=>'MIIBOgIBAAJBAMWSDUjA25VNeDT6fHTbfJFxTJfvLaSzXaMC11oOCK07fAUpZTPHHfUEX2bd0uH5oNSHza/kE3IU93ZNK+JdDSkCAwEAAQJAYytsf5hOoCLCs9UHo6CIZnj4eUsVHhbqaWiDsDBbKphOtsvjzFYCWFLM50KlFlWoyt5b5z6+dc7vcMqnL4KoAQIhAPLj363QXovzYxztngqfImgsXOSBwgTpmnKylb6oSbfVAiEA0Dv50hhAHRneuo0M4nat47wIvc5MmJVS4ecf1N4bngUCIEbwgRLd6c9MPaVkTSVjBwSP+G2Q7F7M75wSRqQRuL4lAiEAqDzcsQ6gtiJRnh0ZnNpP0Z/43AkSP3DdfuByClTMsVUCIH3TYvzcDPJO1K9KTLNXGOSAhkh3QE3wJBLZCI7jm3LY',PUBDER=>'MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAMWSDUjA25VNeDT6fHTbfJFxTJfvLaSzXaMC11oOCK07fAUpZTPHHfUEX2bd0uH5oNSHza/kE3IU93ZNK+JdDSkCAwEAAQ=='},
4545
4646 sub test_rsa {
4747 my $h = shift;
48 my $rsa_pri = Crypt::PK::RSA->new->import_key(\_decode_base64($h->{PRIDER}));
49 my $rsa_pub = Crypt::PK::RSA->new->import_key(\_decode_base64($h->{PUBDER}));
48 my $rsa_pri = Crypt::PK::RSA->new->import_key(\decode_b64($h->{PRIDER}));
49 my $rsa_pub = Crypt::PK::RSA->new->import_key(\decode_b64($h->{PUBDER}));
5050 my $rsa_pri_h = $rsa_pri->key2hash;
5151 my $rsa_pub_h = $rsa_pub->key2hash;
5252 is($rsa_pri_h->{d}, $h->{PRI}, "$h->{ID}/PRI");
5353 is($rsa_pri_h->{N}, $h->{PUB}, "$h->{ID}/PUB");
5454 is($rsa_pub_h->{N}, $h->{PUB}, "$h->{ID}/PUB");
55 is( $rsa_pri->decrypt(_decode_base64($h->{ENC}), 'v1.5'), 'test-data', "$h->{ID}/ENC") || return 0;
56 ok( $rsa_pub->verify_message(_decode_base64($h->{SIGSHA1}), 'test-data', 'SHA1', 'v1.5'), "$h->{ID}/SIGSHA1") || return 0;
57 ok( $rsa_pub->verify_message(_decode_base64($h->{SIGSHA256}), 'test-data', 'SHA256', 'v1.5'), "$h->{ID}/SIGSHA256") || return 0;
55 is( $rsa_pri->decrypt(decode_b64($h->{ENC}), 'v1.5'), 'test-data', "$h->{ID}/ENC") || return 0;
56 ok( $rsa_pub->verify_message(decode_b64($h->{SIGSHA1}), 'test-data', 'SHA1', 'v1.5'), "$h->{ID}/SIGSHA1") || return 0;
57 ok( $rsa_pub->verify_message(decode_b64($h->{SIGSHA256}), 'test-data', 'SHA256', 'v1.5'), "$h->{ID}/SIGSHA256") || return 0;
5858 return 1 if !$h->{SIGSHA512}; #SHA512 might be too big for short RSA keys
59 ok( $rsa_pub->verify_message(_decode_base64($h->{SIGSHA512}), 'test-data', 'SHA512', 'v1.5'), "$h->{ID}/SIGSHA512") || return 0;
59 ok( $rsa_pub->verify_message(decode_b64($h->{SIGSHA512}), 'test-data', 'SHA512', 'v1.5'), "$h->{ID}/SIGSHA512") || return 0;
6060 return 1;
6161 }
6262