File Coverage

blib/lib/GPLVote/SignDoc/Client.pm
Criterion Covered Total %
statement 61 90 67.7
branch 4 8 50.0
condition n/a
subroutine 17 20 85.0
pod 4 6 66.6
total 86 124 69.3


line stmt bran cond sub pod time code
1             package GPLVote::SignDoc::Client;
2              
3             # Copyright (c) 2014, Andrey Velikoredchanin.
4             # This library is free software released under the GNU Lesser General
5             # Public License, Version 3. Please read the important licensing and
6             # disclaimer information included below.
7              
8             # $Id: Client.pm,v 0.7 2015/02/06 17:23:00 Andrey Velikoredchanin $
9              
10 1     1   6959 use Crypt::OpenSSL::RSA;
  1         19028  
  1         48  
11 1     1   556 use Crypt::OpenSSL::AES;
  1         442  
  1         46  
12 1     1   446 use Crypt::CBC;
  1         3780  
  1         30  
13 1     1   616 use Bytes::Random::Secure qw(random_bytes);
  1         8264  
  1         52  
14 1     1   6 use MIME::Base64;
  1         2  
  1         30  
15 1     1   1045 use Digest::SHA qw(sha256_base64 sha256);
  1         2806  
  1         69  
16 1     1   552 use JSON;
  1         43791  
  1         6  
17 1     1   856 use utf8;
  1         7  
  1         4  
18 1     1   1351 use Encode;
  1         9918  
  1         74  
19              
20              
21 1     1   6 use strict;
  1         2  
  1         20  
22 1     1   3 use Exporter;
  1         1  
  1         26  
23 1     1   3 use vars qw($VERSION);
  1         1  
  1         36  
24 1     1   4 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
  1         1  
  1         69  
25              
26             @ISA = qw(Exporter);
27             @EXPORT = qw(user_sign_is_valid calc_pub_key_id encrypt split_base64);
28             @EXPORT_OK = qw(user_sign_is_valid calc_pub_key_id encrypt split_base64);
29             %EXPORT_TAGS = (DEFAULT => [qw(&user_sign_is_valid &calc_pub_key_id &encrypt &split_base64)]);
30              
31             BEGIN {
32 1     1   430 $VERSION = '0.9';
33             }
34              
35             =head1 NAME
36              
37             GPLVote::SignDoc::Client - module for helping create GPLVote SignDoc client software.
38              
39             =head1 SYNOPSIS
40              
41             use GPLVote::SignDoc::Client;
42              
43             if (user_sign_is_valid($public_key, $sign, $data)) {
44             print "Sign of document is CORRECT\n";
45             } else {
46             print "BAD SIGN!!!\n";
47             };
48              
49             my $pub_key_id = calc_pub_key_id($public_key);
50              
51             my $enc_data = encrypt($public_key, $data);
52            
53             =head1 Methods
54              
55             =head2 user_sign_is_valid(base64_plain public_key, base64_plain sign, raw data, boolean sha256sign)
56              
57             Check signature of data.
58              
59             public_key - RSA public key for check signature. Encoded in Base64 in one string without special
60             begin/finish strings and without line breaks.
61              
62             sign - RSA signature. Some format like public_key.
63              
64             data - signing data for verify signature.
65              
66             sha256sign - boolean flag for check signature as "SHA256withRSA", else checking as "SHA1withRSA".
67              
68             Returning true if signature is valid.
69              
70             =head2 split_base64(base_64 string)
71              
72             Helping method for separate one long line Base64 on different lines with length 72 chars.
73              
74             =head2 calc_pub_key_id(base64_plain public_key)
75              
76             Calculate ID of public key.
77              
78             =head2 encrypt(base64_plain public_key, raw data)
79              
80             Encrypt data over public key.
81              
82             Returning plain Base64 string with encrypted data.
83              
84             =head1 BUGS
85              
86             No known bugs, but this does not mean no bugs exist.
87              
88             =head1 SEE ALSO
89              
90             http://gplvote.org/
91              
92             =head1 MAINTAINER
93              
94             Andrey Velikoredchanin
95              
96             =head1 AUTHOR
97              
98             Andrey Velikoredchanin
99              
100             =head1 COPYRIGHT
101              
102             GPLVote::SignDoc::Client - module for helping create GPLVote SignDoc client software
103             Copyright (c) 2014, Andrey Velikoredchanin.
104              
105             This library is free software; you can redistribute it and/or
106             modify it under the terms of the GNU Lesser General Public
107             License as published by the Free Software Foundation; either
108             version 3 of the License, or (at your option) any later version.
109              
110             BECAUSE THIS LIBRARY IS LICENSED FREE OF CHARGE, THIS LIBRARY IS
111             BEING PROVIDED "AS IS WITH ALL FAULTS," WITHOUT ANY WARRANTIES
112             OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING, WITHOUT
113             LIMITATION, ANY IMPLIED WARRANTIES OF TITLE, NONINFRINGEMENT,
114             MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, AND THE
115             ENTIRE RISK AS TO SATISFACTORY QUALITY, PERFORMANCE, ACCURACY,
116             AND EFFORT IS WITH THE YOU. See the GNU Lesser General Public
117             License for more details.
118              
119             You should have received a copy of the GNU Lesser General Public
120             License along with this library; if not, write to the Free Software
121             Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
122              
123             =cut
124              
125              
126             sub user_sign_is_valid {
127 1     1 1 11 my ($b64_pub_key, $b64_sign, $data, $use_sha256_hash) = @_;
128              
129 1         4 my $b64_open_key = "-----BEGIN PUBLIC KEY-----\n".split_base64($b64_pub_key)."\n-----END PUBLIC KEY-----";
130 1         12 my $dec_sign = decode_base64($b64_sign);
131            
132 1         13 my $rsa = Crypt::OpenSSL::RSA->new_public_key($b64_open_key);
133 1 50       484 $rsa->use_sha256_hash if $use_sha256_hash;
134 1         136 return($rsa->verify($data, $dec_sign));
135             }
136              
137             sub split_base64 {
138 2     2 1 4 my $text = $_[0];
139            
140 2         3 my $res = '';
141 2         6 while ($text ne '') {
142 12 100       18 if (length($text) > 72) {
143 10         16 $res .= substr($text, 0, 72)."\n";
144 10         25 $text = substr($text, 72, length($text) - 72);
145             } else {
146 2         3 $res .= $text;
147 2         6 $text = '';
148             };
149             };
150              
151 2         9 return($res);
152             };
153              
154             sub calc_pub_key_id {
155 0     0 1 0 my $b64_pub_key = $_[0];
156              
157 0         0 my $pub_key = decode_base64($b64_pub_key);
158              
159 0         0 return(sha256_base64($pub_key));
160             };
161              
162             sub encrypt {
163 1     1 1 3 my ($b64_pub_key, $data) = @_;
164              
165 1         4 my $b64_open_key = "-----BEGIN PUBLIC KEY-----\n".split_base64($b64_pub_key)."\n-----END PUBLIC KEY-----";
166              
167 1         29 my $rsa = Crypt::OpenSSL::RSA->new_public_key($b64_open_key);
168 1         26 $rsa->use_pkcs1_padding();
169 1 50       3 if (length($data) <= 256) {
170 1         223 return(encode_base64($rsa->encrypt($data), ''));
171             } else {
172             # First 256 bytes - RSA-encrypted header:
173             # 32 bytes - 256-bits AES key
174             # 16 bytes - IV for AES
175             # 32 bytes - sha256 crc for data
176 0           my $enc_data = '';
177              
178             # Random AES (256 bit)
179 0           my $aes_key = random_bytes(32);
180             # Random IV (
181 0           my $aes_iv = random_bytes(16);
182             # CRC for data
183 0           my $crc = sha256($data);
184            
185 0           $enc_data .= $rsa->encrypt($aes_key.$aes_iv.$crc);
186              
187 0           my $aes_cbc = Crypt::CBC->new( -key => $aes_key,
188             -literal_key => 1,
189             -keysize => 32,
190             -cipher => "Crypt::OpenSSL::AES",
191             -iv => $aes_iv,
192             -header => 'none' );
193            
194 0           $enc_data .= $aes_cbc->encrypt($data);
195              
196 0           return(encode_base64($enc_data, ''));
197             };
198             };
199              
200             sub to_hash {
201 0     0 0   my ($json) = @_;
202 0           my $h;
203 0           my $js = JSON->new();
204             # for invalid json
205 0           $js->relaxed(1);
206             # convert to utf-8
207 0           $js->utf8;
208 0           eval {
209             # eval required for no exception if bad json
210 0           $h = $js->decode($json);
211             };
212 0           undef($js);
213 0           return($h);
214             };
215              
216             sub from_hash {
217 0     0 0   my ($h, $pretty) = @_;
218              
219 0           my $s = '';
220 0           my $js = JSON->new();
221             # for invalid json
222 0           $js->relaxed(1);
223 0 0         $js->pretty(1) if ($pretty);
224 0           eval {
225             # eval required for no exception if bad json
226 0           $s = $js->encode($h);
227             };
228 0           undef($js);
229              
230 0           return($s);
231             };
232              
233             1;