added CCM OO interface: new-add-add-done
Karel Miko
6 years ago
0 | 0 | Changes for CryptX |
1 | ||
2 | TODO: | |
3 | - XS croaks should report the "real caller" (Crypt::Mac::*, Crypt::Mode::*, ...) | |
4 | - CCM interface new-add-add-done | |
5 | 1 | |
6 | 2 | 0.054_* 2017-10-XX |
7 | 3 | - new Crypt::Cipher::IDEA |
8 | 4 | - new Crypt::Cipher::Serpent |
9 | 5 | - new Crypt::Stream::Salsa20 |
6 | - added CCM OO interface: new-add-add-done | |
10 | 7 | |
11 | 8 | 0.054 2017-10-12 |
12 | 9 | - fix Crypt::PK::DSA verify |
32 | 32 | |
33 | 33 | typedef struct ccm_struct { /* used by Crypt::AuthEnc::CCM */ |
34 | 34 | ccm_state state; |
35 | int direction; | |
36 | int tag_len; | |
37 | unsigned long pt_len; | |
35 | 38 | int id; |
36 | 39 | } *Crypt__AuthEnc__CCM; |
37 | 40 |
91 | 91 | XPUSHs(sv_2mortal(pt)); |
92 | 92 | } |
93 | 93 | } |
94 | ||
95 | Crypt::AuthEnc::CCM | |
96 | _new(char * cipher_name, SV * key, SV * nonce, SV * adata, int tag_len, int pt_len) | |
97 | CODE: | |
98 | { | |
99 | unsigned char *k=NULL; | |
100 | STRLEN k_len=0; | |
101 | unsigned char *n=NULL; | |
102 | STRLEN n_len=0; | |
103 | unsigned char *h=NULL; | |
104 | STRLEN h_len=0; | |
105 | int rv, id; | |
106 | ||
107 | if (tag_len < 1 || tag_len > MAXBLOCKSIZE) croak("FATAL: invalid tag_len %d", tag_len); | |
108 | if (pt_len < 0) croak("FATAL: invalid pt_len"); | |
109 | if (!SvPOK(key)) croak("FATAL: key must be string/buffer scalar"); | |
110 | k = (unsigned char *) SvPVbyte(key, k_len); | |
111 | if (!SvPOK(nonce)) croak("FATAL: nonce must be string/buffer scalar"); | |
112 | n = (unsigned char *) SvPVbyte(nonce, n_len); | |
113 | if (!SvPOK(adata)) croak("FATAL: adata must be string/buffer scalar"); | |
114 | h = (unsigned char *) SvPVbyte(adata, h_len); | |
115 | ||
116 | id = find_cipher(cipher_name); | |
117 | if (id == -1) croak("FATAL: find_cipfer failed for '%s'", cipher_name); | |
118 | ||
119 | Newz(0, RETVAL, 1, struct ccm_struct); | |
120 | if (!RETVAL) croak("FATAL: Newz failed"); | |
121 | ||
122 | rv = ccm_init(&RETVAL->state, id, k, (unsigned long)k_len, pt_len, tag_len, h_len); | |
123 | if (rv != CRYPT_OK) { | |
124 | Safefree(RETVAL); | |
125 | croak("FATAL: ccm_init failed: %s", error_to_string(rv)); | |
126 | } | |
127 | rv = ccm_add_nonce(&RETVAL->state, n, (unsigned long)n_len); | |
128 | if (rv != CRYPT_OK) { | |
129 | Safefree(RETVAL); | |
130 | croak("FATAL: ccm_add_nonce failed: %s", error_to_string(rv)); | |
131 | } | |
132 | rv = ccm_add_aad(&RETVAL->state, h, (unsigned long)h_len); | |
133 | if (rv != CRYPT_OK) { | |
134 | Safefree(RETVAL); | |
135 | croak("FATAL: ccm_add_aad failed: %s", error_to_string(rv)); | |
136 | } | |
137 | RETVAL->direction = -1; | |
138 | RETVAL->tag_len = tag_len; | |
139 | RETVAL->pt_len = pt_len; | |
140 | } | |
141 | OUTPUT: | |
142 | RETVAL | |
143 | ||
144 | void | |
145 | DESTROY(Crypt::AuthEnc::CCM self) | |
146 | CODE: | |
147 | Safefree(self); | |
148 | ||
149 | Crypt::AuthEnc::CCM | |
150 | clone(Crypt::AuthEnc::CCM self) | |
151 | CODE: | |
152 | Newz(0, RETVAL, 1, struct ccm_struct); | |
153 | if (!RETVAL) croak("FATAL: Newz failed"); | |
154 | Copy(&self->state, &RETVAL->state, 1, struct ccm_struct); | |
155 | OUTPUT: | |
156 | RETVAL | |
157 | ||
158 | SV * | |
159 | encrypt_add(Crypt::AuthEnc::CCM self, SV * data) | |
160 | CODE: | |
161 | { | |
162 | int rv; | |
163 | STRLEN in_data_len; | |
164 | unsigned char *in_data, *out_data; | |
165 | ||
166 | in_data = (unsigned char *)SvPVbyte(data, in_data_len); | |
167 | if (in_data_len == 0) { | |
168 | RETVAL = newSVpvn("", 0); | |
169 | } | |
170 | else { | |
171 | if (self->direction == -1) self->direction = CCM_ENCRYPT; | |
172 | if (self->direction != CCM_ENCRYPT) { | |
173 | croak("FATAL: encrypt_add failed: wrong direction"); | |
174 | } | |
175 | if (self->pt_len < in_data_len) croak("FATAL: encrypt_add failed: pt_len mismatch"); | |
176 | RETVAL = NEWSV(0, in_data_len); | |
177 | SvPOK_only(RETVAL); | |
178 | SvCUR_set(RETVAL, in_data_len); | |
179 | out_data = (unsigned char *)SvPVX(RETVAL); | |
180 | rv = ccm_process(&self->state, in_data, (unsigned long)in_data_len, out_data, self->direction); | |
181 | if (rv != CRYPT_OK) { | |
182 | SvREFCNT_dec(RETVAL); | |
183 | croak("FATAL: ccm_process failed: %s", error_to_string(rv)); | |
184 | } | |
185 | self->pt_len -= in_data_len; | |
186 | } | |
187 | } | |
188 | OUTPUT: | |
189 | RETVAL | |
190 | ||
191 | SV * | |
192 | decrypt_add(Crypt::AuthEnc::CCM self, SV * data) | |
193 | CODE: | |
194 | { | |
195 | int rv, i; | |
196 | STRLEN in_data_len; | |
197 | unsigned char *in_data, *out_data; | |
198 | ||
199 | in_data = (unsigned char *)SvPVbyte(data, in_data_len); | |
200 | if (in_data_len == 0) { | |
201 | RETVAL = newSVpvn("", 0); | |
202 | } | |
203 | else { | |
204 | if (self->direction == -1) self->direction = CCM_DECRYPT; | |
205 | if (self->direction != CCM_DECRYPT) { | |
206 | croak("FATAL: decrypt_add failed: wrong direction"); | |
207 | } | |
208 | if (self->pt_len < in_data_len) croak("FATAL: decrypt_add failed: pt_len mismatch"); | |
209 | RETVAL = NEWSV(0, in_data_len); | |
210 | SvPOK_only(RETVAL); | |
211 | SvCUR_set(RETVAL, in_data_len); | |
212 | out_data = (unsigned char *)SvPVX(RETVAL); | |
213 | rv = ccm_process(&self->state, out_data, (unsigned long)in_data_len, in_data, CCM_DECRYPT); | |
214 | if (rv != CRYPT_OK) { | |
215 | SvREFCNT_dec(RETVAL); | |
216 | croak("FATAL: ccm_process failed: %s", error_to_string(rv)); | |
217 | } | |
218 | self->pt_len -= in_data_len; | |
219 | } | |
220 | } | |
221 | OUTPUT: | |
222 | RETVAL | |
223 | ||
224 | void | |
225 | encrypt_done(Crypt::AuthEnc::CCM self) | |
226 | PPCODE: | |
227 | { | |
228 | int rv; | |
229 | unsigned char tag[MAXBLOCKSIZE]; | |
230 | unsigned long tag_len = self->tag_len; | |
231 | ||
232 | if (self->direction != CCM_ENCRYPT) { | |
233 | croak("FATAL: encrypt_done failed: wrong direction"); | |
234 | } | |
235 | if (self->pt_len != 0) croak("FATAL: encrypt_done failed: pt_len mismatch"); | |
236 | rv = ccm_done(&self->state, tag, &tag_len); | |
237 | if (rv != CRYPT_OK) croak("FATAL: ccm_done failed: %s", error_to_string(rv)); | |
238 | XPUSHs(sv_2mortal(newSVpvn((char*)tag, tag_len))); | |
239 | } | |
240 | ||
241 | void | |
242 | decrypt_done(Crypt::AuthEnc::CCM self, ...) | |
243 | PPCODE: | |
244 | { | |
245 | int rv; | |
246 | unsigned char tag[MAXBLOCKSIZE]; | |
247 | unsigned long tag_len = self->tag_len; | |
248 | STRLEN expected_tag_len; | |
249 | unsigned char *expected_tag; | |
250 | ||
251 | if (self->direction != CCM_DECRYPT) { | |
252 | croak("FATAL: decrypt_done failed: wrong direction"); | |
253 | } | |
254 | if (self->pt_len != 0) croak("FATAL: decrypt_done failed: pt_len mismatch"); | |
255 | rv = ccm_done(&self->state, tag, &tag_len); | |
256 | if (rv != CRYPT_OK) croak("FATAL: ccm_done failed: %s", error_to_string(rv)); | |
257 | if (items == 1) { | |
258 | XPUSHs(sv_2mortal(newSVpvn((char*)tag, tag_len))); | |
259 | } | |
260 | else { | |
261 | if (!SvPOK(ST(1))) croak("FATAL: expected_tag must be string/buffer scalar"); | |
262 | expected_tag = (unsigned char *) SvPVbyte(ST(1), expected_tag_len); | |
263 | if (expected_tag_len!=tag_len) { | |
264 | XPUSHs(sv_2mortal(newSViv(0))); /* false */ | |
265 | } | |
266 | else if (memNE(expected_tag, tag, tag_len)) { | |
267 | XPUSHs(sv_2mortal(newSViv(0))); /* false */ | |
268 | } | |
269 | else { | |
270 | XPUSHs(sv_2mortal(newSViv(1))); /* true */ | |
271 | } | |
272 | } | |
273 | } |
11 | 11 | use CryptX; |
12 | 12 | use Crypt::Cipher; |
13 | 13 | |
14 | ### the following functions are implemented in XS: | |
15 | # - _memory_encrypt | |
16 | # - _memory_decrypt | |
14 | sub new { | |
15 | my ($class, $cipher, $key, $iv, $adata, $tag_len, $pt_len) = @_; | |
16 | return _new(Crypt::Cipher::_trans_cipher_name($cipher), $key, $iv, $adata, $tag_len, $pt_len); | |
17 | } | |
17 | 18 | |
18 | 19 | sub ccm_encrypt_authenticate { |
19 | 20 | my $cipher_name = shift; |
20 | 21 | my $key = shift; |
21 | my $nonce = shift; | |
22 | my $iv = shift; | |
22 | 23 | my $adata = shift; |
23 | 24 | my $tag_len = shift; |
24 | 25 | my $plaintext = shift; |
25 | return _memory_encrypt(Crypt::Cipher::_trans_cipher_name($cipher_name), $key, $nonce, $adata, $tag_len, $plaintext); | |
26 | ||
27 | $iv = "" if !defined $iv; | |
28 | $adata = "" if !defined $adata; | |
29 | $plaintext = "" if !defined $plaintext; | |
30 | ||
31 | return _memory_encrypt(Crypt::Cipher::_trans_cipher_name($cipher_name), $key, $iv, $adata, $tag_len, $plaintext); | |
32 | #my $m = Crypt::AuthEnc::CCM->new($cipher_name, $key, $iv, $adata, $tag_len, length($plaintext)); | |
33 | #my $ct = $m->encrypt_add($plaintext); | |
34 | #my $tag = $m->encrypt_done(); | |
35 | #return ($ct, $tag); | |
26 | 36 | } |
27 | 37 | |
28 | 38 | sub ccm_decrypt_verify { |
29 | 39 | my $cipher_name = shift; |
30 | 40 | my $key = shift; |
31 | my $nonce = shift; | |
41 | my $iv = shift; | |
32 | 42 | my $adata = shift; |
33 | 43 | my $ciphertext = shift; |
34 | 44 | my $tag = shift; |
35 | return _memory_decrypt(Crypt::Cipher::_trans_cipher_name($cipher_name), $key, $nonce, $adata, $ciphertext, $tag); | |
45 | ||
46 | $iv = "" if !defined $iv; | |
47 | $adata = "" if !defined $adata; | |
48 | $ciphertext = "" if !defined $ciphertext; | |
49 | ||
50 | return _memory_decrypt(Crypt::Cipher::_trans_cipher_name($cipher_name), $key, $iv, $adata, $ciphertext, $tag); | |
51 | #my $m = Crypt::AuthEnc::CCM->new($cipher_name, $key, $iv, $adata, length($tag), length($ciphertext)); | |
52 | #my $pt = $m->decrypt_add($ciphertext); | |
53 | #return $m->decrypt_done($tag) ? $pt : undef; | |
36 | 54 | } |
37 | 55 | |
38 | 56 | 1; |
45 | 63 | |
46 | 64 | =head1 SYNOPSIS |
47 | 65 | |
66 | ### OO interface | |
67 | use Crypt::AuthEnc::CCM; | |
68 | ||
69 | # encrypt and authenticate | |
70 | $ae = Crypt::AuthEnc::CCM->new("AES", $key, $iv, $adata, $tag_len, $pt_len); | |
71 | $ct = $ae->encrypt_add('data1'); | |
72 | $ct .= $ae->encrypt_add('data2'); | |
73 | $ct .= $ae->encrypt_add('data3'); | |
74 | $tag = $ae->encrypt_done(); | |
75 | ||
76 | # decrypt and verify | |
77 | $ae = Crypt::AuthEnc::CCM->new("AES", $key, $iv, $adata, $tag_len, $pt_len); | |
78 | $pt = $ae->decrypt_add('ciphertext1'); | |
79 | $pt .= $ae->decrypt_add('ciphertext2'); | |
80 | $pt .= $ae->decrypt_add('ciphertext3'); | |
81 | $tag = $ae->decrypt_done(); | |
82 | die "decrypt failed" unless $tag eq $expected_tag; | |
83 | ||
84 | #or | |
85 | $result = $ae->decrypt_done($expected_tag); # 0 or 1 | |
86 | ||
48 | 87 | ### functional interface |
49 | 88 | use Crypt::AuthEnc::CCM qw(ccm_encrypt_authenticate ccm_decrypt_verify); |
50 | 89 | |
51 | my ($ciphertext, $tag) = ccm_encrypt_authenticate('AES', $key, $nonce, $adata, $tag_len, $plaintext); | |
52 | my $plaintext = ccm_decrypt_verify('AES', $key, $nonce, $adata, $ciphertext, $tag); | |
90 | ($ciphertext, $tag) = ccm_encrypt_authenticate('AES', $key, $nonce, $adata, $tag_len, $plaintext); | |
91 | $plaintext = ccm_decrypt_verify('AES', $key, $nonce, $adata, $ciphertext, $tag); | |
53 | 92 | |
54 | 93 | =head1 DESCRIPTION |
55 | 94 | |
88 | 127 | |
89 | 128 | # on error returns undef |
90 | 129 | |
130 | =head1 METHODS | |
131 | ||
132 | =head2 new | |
133 | ||
134 | my $ae = Crypt::AuthEnc::CCM->new($cipher, $key, $nonce, $adata, $tag_len, $pt_len); | |
135 | ||
136 | # $cipher .. 'AES' or name of any other cipher with 16-byte block len | |
137 | # $key ..... key of proper length (e.g. 128/192/256bits for AES) | |
138 | # $nonce ... unique nonce/salt (no need to keep it secret) | |
139 | # $adata ... additional authenticated data | |
140 | # $tag_len . required length of output tag | |
141 | # $pt_len .. expected length of plaintext/ciphertext to encrypt/decrypt | |
142 | ||
143 | =head2 encrypt_add | |
144 | ||
145 | $ciphertext = $ae->encrypt_add($data); #can be called multiple times | |
146 | ||
147 | =head2 encrypt_done | |
148 | ||
149 | $tag = $ae->encrypt_done(); | |
150 | ||
151 | =head2 decrypt_add | |
152 | ||
153 | $plaintext = $ae->decrypt_add($ciphertext); #can be called multiple times | |
154 | ||
155 | =head2 decrypt_done | |
156 | ||
157 | my $result = $ae->decrypt_done($tag); # returns 1 (success) or 0 (failure) | |
158 | #or | |
159 | my $tag = $ae->decrypt_done; # returns $tag value | |
160 | ||
161 | =head2 clone | |
162 | ||
163 | my $ae_new = $ae->clone; | |
164 | ||
91 | 165 | =head1 SEE ALSO |
92 | 166 | |
93 | 167 | =over |
0 | 0 | use strict; |
1 | 1 | use warnings; |
2 | 2 | |
3 | use Test::More tests => 6; | |
3 | use Test::More tests => 12; | |
4 | 4 | |
5 | 5 | use Crypt::AuthEnc::CCM qw( ccm_encrypt_authenticate ccm_decrypt_verify ); |
6 | 6 | |
7 | 7 | my $nonce = "random-nonce"; |
8 | 8 | my $key = "12345678901234561234567890123456"; |
9 | ||
10 | { | |
11 | my $pt = "plain_half"; | |
12 | my $ct; | |
13 | ||
14 | my $m1 = Crypt::AuthEnc::CCM->new("AES", $key, $nonce, "abc", 16, 20); | |
15 | $ct = $m1->encrypt_add($pt); | |
16 | $ct .= $m1->encrypt_add($pt); | |
17 | my $tag = $m1->encrypt_done; | |
18 | ||
19 | is(unpack('H*', $ct), "96b0114ff47da72e92631aadce84f203a8168b20", "enc: ciphertext"); | |
20 | is(unpack('H*', $tag), "fdc41ec07673ec132f1910ba771b9530", "enc: tag"); | |
21 | ||
22 | my $d1 = Crypt::AuthEnc::CCM->new("AES", $key, $nonce, "abc", 16, 20); | |
23 | my $pt2 = $d1->decrypt_add($ct); | |
24 | my $tag2 = $d1->decrypt_done(); | |
25 | ||
26 | is($pt2, "plain_halfplain_half", "dec1: plaintext"); | |
27 | is(unpack('H*', $tag2), "fdc41ec07673ec132f1910ba771b9530", "dec1: tag"); | |
28 | ||
29 | my $d2 = Crypt::AuthEnc::CCM->new("AES", $key, $nonce, "abc", 16, 20); | |
30 | my $pt3; | |
31 | $pt3 .= $d2->decrypt_add(substr($ct,$_-1,1)) for (1..length($ct)); | |
32 | my $tag3 = $d2->decrypt_done(); | |
33 | ||
34 | is($pt3, "plain_halfplain_half", "dec2: plaintext"); | |
35 | is(unpack('H*', $tag3), "fdc41ec07673ec132f1910ba771b9530", "dec2: tag"); | |
36 | } | |
9 | 37 | |
10 | 38 | { |
11 | 39 | my ($ct, $tag) = ccm_encrypt_authenticate('AES', $key, $nonce, "header-abc", 16, "plain_halfplain_half"); |