1 /*****************************************************************
2 |
3 |    AP4 - Stream Cipher
4 |
5 |    Copyright 2002-2008 Axiomatic Systems, LLC
6 |
7 |
8 |    This file is part of Bento4/AP4 (MP4 Atom Processing Library).
9 |
10 |    Unless you have obtained Bento4 under a difference license,
11 |    this version of Bento4 is Bento4|GPL.
12 |    Bento4|GPL is free software; you can redistribute it and/or modify
13 |    it under the terms of the GNU General Public License as published by
14 |    the Free Software Foundation; either version 2, or (at your option)
15 |    any later version.
16 |
17 |    Bento4|GPL is distributed in the hope that it will be useful,
18 |    but WITHOUT ANY WARRANTY; without even the implied warranty of
19 |    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20 |    GNU General Public License for more details.
21 |
22 |    You should have received a copy of the GNU General Public License
23 |    along with Bento4|GPL; see the file COPYING.  If not, write to the
24 |    Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
25 |    02111-1307, USA.
26 |
27 ****************************************************************/
28 
29 /*----------------------------------------------------------------------
30 |   includes
31 +---------------------------------------------------------------------*/
32 #include "Ap4StreamCipher.h"
33 #include "Ap4Utils.h"
34 
35 /*----------------------------------------------------------------------
36 |   AP4_CtrStreamCipher::AP4_CtrStreamCipher
37 +---------------------------------------------------------------------*/
AP4_CtrStreamCipher(AP4_BlockCipher * block_cipher,AP4_Size counter_size)38 AP4_CtrStreamCipher::AP4_CtrStreamCipher(AP4_BlockCipher* block_cipher,
39                                          AP4_Size         counter_size) :
40     m_StreamOffset(0),
41     m_CounterSize(counter_size),
42     m_CacheValid(false),
43     m_BlockCipher(block_cipher)
44 {
45     if (m_CounterSize > 16) m_CounterSize = 16;
46 
47     // reset the stream offset
48     AP4_SetMemory(m_IV, 0, AP4_CIPHER_BLOCK_SIZE);
49     SetStreamOffset(0);
50     SetIV(NULL);
51 }
52 
53 /*----------------------------------------------------------------------
54 |   AP4_CtrStreamCipher::~AP4_CtrStreamCipher
55 +---------------------------------------------------------------------*/
~AP4_CtrStreamCipher()56 AP4_CtrStreamCipher::~AP4_CtrStreamCipher()
57 {
58     delete m_BlockCipher;
59 }
60 
61 /*----------------------------------------------------------------------
62 |   AP4_CtrStreamCipher::SetIV
63 +---------------------------------------------------------------------*/
64 AP4_Result
SetIV(const AP4_UI08 * iv)65 AP4_CtrStreamCipher::SetIV(const AP4_UI08* iv)
66 {
67     if (iv) {
68         AP4_CopyMemory(m_IV, iv, AP4_CIPHER_BLOCK_SIZE);
69     } else {
70         AP4_SetMemory(m_IV, 0, AP4_CIPHER_BLOCK_SIZE);
71     }
72 
73     // for the stream offset back to 0
74     m_CacheValid = false;
75     return SetStreamOffset(0);
76 }
77 
78 /*----------------------------------------------------------------------
79 |   AP4_CtrStreamCipher::SetStreamOffset
80 +---------------------------------------------------------------------*/
81 AP4_Result
SetStreamOffset(AP4_UI64 offset,AP4_Cardinal * preroll)82 AP4_CtrStreamCipher::SetStreamOffset(AP4_UI64      offset,
83                                      AP4_Cardinal* preroll)
84 {
85     // do nothing if we're already at that offset
86     if (offset == m_StreamOffset) return AP4_SUCCESS;
87 
88     // update the offset
89     m_CacheValid = false;
90     m_StreamOffset = offset;
91 
92     // no preroll in CTR mode
93     if (preroll != NULL) *preroll = 0;
94 
95     return AP4_SUCCESS;
96 }
97 
98 /*----------------------------------------------------------------------
99 |   AP4_CtrStreamCipher::ComputeCounter
100 +---------------------------------------------------------------------*/
101 void
ComputeCounter(AP4_UI64 stream_offset,AP4_UI08 counter_block[AP4_CIPHER_BLOCK_SIZE])102 AP4_CtrStreamCipher::ComputeCounter(AP4_UI64 stream_offset,
103                                     AP4_UI08 counter_block[AP4_CIPHER_BLOCK_SIZE])
104 {
105     // setup counter offset bytes
106     AP4_UI64 counter_offset = stream_offset/AP4_CIPHER_BLOCK_SIZE;
107     AP4_UI08 counter_offset_bytes[8];
108     AP4_BytesFromUInt64BE(counter_offset_bytes, counter_offset);
109 
110     // compute the new counter
111     unsigned int carry = 0;
112     for (unsigned int i=0; i<m_CounterSize; i++) {
113         unsigned int o = AP4_CIPHER_BLOCK_SIZE-1-i;
114         unsigned int x = m_IV[o];
115         unsigned int y = (i<8)?counter_offset_bytes[7-i]:0;
116         unsigned int sum = x+y+carry;
117         counter_block[o] = (AP4_UI08)(sum&0xFF);
118         carry = ((sum >= 0x100)?1:0);
119     }
120     for (unsigned int i=m_CounterSize; i<AP4_CIPHER_BLOCK_SIZE; i++) {
121         unsigned int o = AP4_CIPHER_BLOCK_SIZE-1-i;
122         counter_block[o] = m_IV[o];
123     }
124 }
125 
126 /*----------------------------------------------------------------------
127 |   AP4_CtrStreamCipher::ProcessBuffer
128 +---------------------------------------------------------------------*/
129 AP4_Result
ProcessBuffer(const AP4_UI08 * in,AP4_Size in_size,AP4_UI08 * out,AP4_Size * out_size,bool)130 AP4_CtrStreamCipher::ProcessBuffer(const AP4_UI08* in,
131                                    AP4_Size        in_size,
132                                    AP4_UI08*       out,
133                                    AP4_Size*       out_size   /* = NULL */,
134                                    bool            /* is_last_buffer */)
135 {
136     if (m_BlockCipher == NULL) return AP4_ERROR_INVALID_STATE;
137 
138     if (out_size != NULL && *out_size < in_size) {
139         *out_size = in_size;
140         return AP4_ERROR_BUFFER_TOO_SMALL;
141     }
142 
143     // in CTR mode, the output is the same size as the input
144     if (out_size != NULL) *out_size = in_size;
145 
146     // deal with inputs not aligned on block boundaries
147     if (m_StreamOffset%AP4_CIPHER_BLOCK_SIZE) {
148         unsigned int cache_offset = (unsigned int)(m_StreamOffset%AP4_CIPHER_BLOCK_SIZE);
149         if (!m_CacheValid) {
150             // fill the cache
151             AP4_UI08 block[AP4_CIPHER_BLOCK_SIZE] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
152             AP4_UI08 counter_block[AP4_CIPHER_BLOCK_SIZE];
153             ComputeCounter(m_StreamOffset-cache_offset, counter_block);
154             AP4_Result result = m_BlockCipher->Process(block, AP4_CIPHER_BLOCK_SIZE, m_CacheBlock, counter_block);
155             if (AP4_FAILED(result)) {
156                 if (out_size) *out_size = 0;
157                 return result;
158             }
159             m_CacheValid = true;
160         }
161         unsigned int partial = AP4_CIPHER_BLOCK_SIZE-cache_offset;
162         if (partial > in_size) partial = in_size;
163         for (unsigned int i=0; i<partial; i++) {
164             out[i] = in[i]^m_CacheBlock[i+cache_offset];
165         }
166 
167         // advance to the end of the partial block
168         m_StreamOffset += partial;
169         in             += partial;
170         out            += partial;
171         in_size        -= partial;
172     }
173 
174     // process all the remaining bytes in the buffer
175     if (in_size) {
176         // the cache won't be valid anymore
177         m_CacheValid = false;
178 
179         // compute the counter
180         AP4_UI08 counter_block[AP4_CIPHER_BLOCK_SIZE];
181         ComputeCounter(m_StreamOffset, counter_block);
182 
183         // process the data
184         AP4_Result result = m_BlockCipher->Process(in, in_size, out, counter_block);
185         if (AP4_FAILED(result)) {
186             if (out_size) *out_size = 0;
187             return result;
188         }
189         m_StreamOffset += in_size;
190         return result;
191     }
192 
193     return AP4_SUCCESS;
194 }
195 
196 /*----------------------------------------------------------------------
197 |   AP4_CbcStreamCipher::AP4_CbcStreamCipher
198 +---------------------------------------------------------------------*/
AP4_CbcStreamCipher(AP4_BlockCipher * block_cipher)199 AP4_CbcStreamCipher::AP4_CbcStreamCipher(AP4_BlockCipher* block_cipher) :
200     m_StreamOffset(0),
201     m_OutputSkip(0),
202     m_InBlockFullness(0),
203     m_ChainBlockFullness(AP4_CIPHER_BLOCK_SIZE),
204     m_BlockCipher(block_cipher),
205     m_Eos(false)
206 {
207     AP4_SetMemory(m_Iv, 0, AP4_CIPHER_BLOCK_SIZE);
208     AP4_SetMemory(m_ChainBlock, 0, AP4_CIPHER_BLOCK_SIZE);
209 }
210 
211 /*----------------------------------------------------------------------
212 |   AP4_CbcStreamCipher::~AP4_CbcStreamCipher
213 +---------------------------------------------------------------------*/
~AP4_CbcStreamCipher()214 AP4_CbcStreamCipher::~AP4_CbcStreamCipher()
215 {
216     delete m_BlockCipher;
217 }
218 
219 /*----------------------------------------------------------------------
220 |   AP4_CbcStreamCipher::SetIV
221 +---------------------------------------------------------------------*/
222 AP4_Result
SetIV(const AP4_UI08 * iv)223 AP4_CbcStreamCipher::SetIV(const AP4_UI08* iv)
224 {
225     AP4_CopyMemory(m_Iv, iv, AP4_CIPHER_BLOCK_SIZE);
226     m_StreamOffset = 0;
227     m_Eos = false;
228     AP4_CopyMemory(m_ChainBlock, m_Iv, AP4_CIPHER_BLOCK_SIZE);
229     m_ChainBlockFullness = AP4_CIPHER_BLOCK_SIZE;
230     m_InBlockFullness = 0;
231     m_OutputSkip = 0;
232     return AP4_SUCCESS;
233 }
234 
235 /*----------------------------------------------------------------------
236 |   AP4_CbcStreamCipher::SetStreamOffset
237 +---------------------------------------------------------------------*/
238 AP4_Result
SetStreamOffset(AP4_UI64 offset,AP4_Cardinal * preroll)239 AP4_CbcStreamCipher::SetStreamOffset(AP4_UI64       offset,
240                                      AP4_Cardinal*  preroll)
241 {
242     // does not make sense for encryption
243     if (m_BlockCipher->GetDirection() == AP4_BlockCipher::ENCRYPT) {
244         return AP4_ERROR_NOT_SUPPORTED;
245     }
246 
247     // check params
248     if (preroll == NULL) return AP4_ERROR_INVALID_PARAMETERS;
249 
250     // reset the end of stream flag
251     m_Eos = false;
252 
253     // invalidate the chain block
254     m_ChainBlockFullness = 0;
255 
256     // flush cached data
257     m_InBlockFullness = 0;
258 
259     // compute the preroll
260     if (offset < AP4_CIPHER_BLOCK_SIZE) {
261         AP4_CopyMemory(m_ChainBlock, m_Iv, AP4_CIPHER_BLOCK_SIZE);
262         m_ChainBlockFullness = AP4_CIPHER_BLOCK_SIZE;
263         *preroll = (AP4_Cardinal)offset;
264     } else {
265         *preroll = (AP4_Cardinal) ((offset%AP4_CIPHER_BLOCK_SIZE) + AP4_CIPHER_BLOCK_SIZE);
266     }
267 
268     m_StreamOffset = offset-*preroll;
269     m_OutputSkip   = (AP4_Size)(offset%AP4_CIPHER_BLOCK_SIZE);
270     return AP4_SUCCESS;
271 }
272 
273 
274 /*----------------------------------------------------------------------
275 |   AP4_CbcStreamCipher::EncryptBuffer
276 +---------------------------------------------------------------------*/
277 AP4_Result
EncryptBuffer(const AP4_UI08 * in,AP4_Size in_size,AP4_UI08 * out,AP4_Size * out_size,bool is_last_buffer)278 AP4_CbcStreamCipher::EncryptBuffer(const AP4_UI08* in,
279                                    AP4_Size        in_size,
280                                    AP4_UI08*       out,
281                                    AP4_Size*       out_size,
282                                    bool            is_last_buffer)
283 {
284     // we don't do much checking here because this method is only called
285     // from ProcessBuffer(), which does all the parameter checking
286 
287     // compute how many blocks we span
288     AP4_UI64 start_block   = (m_StreamOffset-m_InBlockFullness)/AP4_CIPHER_BLOCK_SIZE;
289     AP4_UI64 end_block     = (m_StreamOffset+in_size)/AP4_CIPHER_BLOCK_SIZE;
290     AP4_UI32 blocks_needed = (AP4_UI32)(end_block-start_block);
291 
292     // compute how many blocks we will need to produce
293     if (is_last_buffer) {
294         ++blocks_needed;
295     }
296     if (*out_size < blocks_needed*AP4_CIPHER_BLOCK_SIZE) {
297         *out_size = blocks_needed*AP4_CIPHER_BLOCK_SIZE;
298         return AP4_ERROR_BUFFER_TOO_SMALL;
299     }
300     *out_size = blocks_needed*AP4_CIPHER_BLOCK_SIZE;
301 
302     // finish any incomplete block from a previous call
303     unsigned int offset = (unsigned int)(m_StreamOffset%AP4_CIPHER_BLOCK_SIZE);
304     AP4_ASSERT(m_InBlockFullness == offset);
305     if (offset) {
306         unsigned int chunk = AP4_CIPHER_BLOCK_SIZE-offset;
307         if (chunk > in_size) chunk = in_size;
308         for (unsigned int x=0; x<chunk; x++) {
309             m_InBlock[x+offset] = in[x];
310         }
311         in                += chunk;
312         in_size           -= chunk;
313         m_StreamOffset    += chunk;
314         m_InBlockFullness += chunk;
315         if (offset+chunk == AP4_CIPHER_BLOCK_SIZE) {
316             // we have filled the input block, encrypt it
317             AP4_Result result = m_BlockCipher->Process(m_InBlock, AP4_CIPHER_BLOCK_SIZE, out, m_ChainBlock);
318             AP4_CopyMemory(m_ChainBlock, out, AP4_CIPHER_BLOCK_SIZE);
319             m_InBlockFullness = 0;
320             if (AP4_FAILED(result)) {
321                 *out_size = 0;
322                 return result;
323             }
324             out += AP4_CIPHER_BLOCK_SIZE;
325         }
326     }
327 
328     // encrypt the whole blocks
329     unsigned int block_count = in_size/AP4_CIPHER_BLOCK_SIZE;
330     if (block_count) {
331         AP4_ASSERT(m_InBlockFullness == 0);
332         AP4_UI32 blocks_size = block_count*AP4_CIPHER_BLOCK_SIZE;
333         AP4_Result result = m_BlockCipher->Process(in, blocks_size, out, m_ChainBlock);
334         AP4_CopyMemory(m_ChainBlock, out+blocks_size-AP4_CIPHER_BLOCK_SIZE, AP4_CIPHER_BLOCK_SIZE);
335         if (AP4_FAILED(result)) {
336             *out_size = 0;
337             return result;
338         }
339         in             += blocks_size;
340         out            += blocks_size;
341         in_size        -= blocks_size;
342         m_StreamOffset += blocks_size;
343     }
344 
345     // deal with what's left
346     if (in_size) {
347         AP4_ASSERT(in_size < AP4_CIPHER_BLOCK_SIZE);
348         for (unsigned int x=0; x<in_size; x++) {
349             m_InBlock[x+m_InBlockFullness] = in[x];
350         }
351         m_InBlockFullness += in_size;
352         m_StreamOffset    += in_size;
353     }
354 
355     // pad if needed
356     if (is_last_buffer) {
357         AP4_ASSERT(m_InBlockFullness == m_StreamOffset%AP4_CIPHER_BLOCK_SIZE);
358         AP4_UI08 pad_byte = AP4_CIPHER_BLOCK_SIZE-(AP4_UI08)(m_StreamOffset%AP4_CIPHER_BLOCK_SIZE);
359         for (unsigned int x=AP4_CIPHER_BLOCK_SIZE-pad_byte; x<AP4_CIPHER_BLOCK_SIZE; x++) {
360             m_InBlock[x] = pad_byte;
361         }
362         AP4_Result result = m_BlockCipher->Process(m_InBlock, AP4_CIPHER_BLOCK_SIZE, out, m_ChainBlock);
363         AP4_CopyMemory(m_ChainBlock, out, AP4_CIPHER_BLOCK_SIZE);
364         m_InBlockFullness = 0;
365         if (AP4_FAILED(result)) {
366             *out_size = 0;
367             return result;
368         }
369     }
370 
371     return AP4_SUCCESS;
372 }
373 
374 /*----------------------------------------------------------------------
375 |   AP4_CbcStreamCipher::DecryptBuffer
376 +---------------------------------------------------------------------*/
377 AP4_Result
DecryptBuffer(const AP4_UI08 * in,AP4_Size in_size,AP4_UI08 * out,AP4_Size * out_size,bool is_last_buffer)378 AP4_CbcStreamCipher::DecryptBuffer(const AP4_UI08* in,
379                                    AP4_Size        in_size,
380                                    AP4_UI08*       out,
381                                    AP4_Size*       out_size,
382                                    bool            is_last_buffer)
383 {
384     // we don't do much checking here because this method is only called
385     // from ProcessBuffer(), which does all the parameter checking
386 
387     // deal chain-block buffering
388     if (m_ChainBlockFullness != AP4_CIPHER_BLOCK_SIZE) {
389         unsigned int needed = AP4_CIPHER_BLOCK_SIZE-m_ChainBlockFullness;
390         unsigned int chunk = (in_size > needed) ? needed : in_size;
391         AP4_CopyMemory(&m_ChainBlock[m_ChainBlockFullness], in, chunk);
392         in_size              -= chunk;
393         in                   += chunk;
394         m_ChainBlockFullness += chunk;
395         m_StreamOffset       += chunk;
396         if (m_ChainBlockFullness != AP4_CIPHER_BLOCK_SIZE) {
397             // not enough to continue
398             *out_size = 0;
399             return AP4_SUCCESS;
400         }
401     }
402     AP4_ASSERT(m_ChainBlockFullness == AP4_CIPHER_BLOCK_SIZE);
403 
404     // compute how many blocks we span
405     AP4_UI64 start_block   = (m_StreamOffset-m_InBlockFullness)/AP4_CIPHER_BLOCK_SIZE;
406     AP4_UI64 end_block     = (m_StreamOffset+in_size)/AP4_CIPHER_BLOCK_SIZE;
407     AP4_UI32 blocks_needed = (AP4_UI32)(end_block-start_block);
408 
409     // compute how many blocks we will need to produce
410     if (*out_size < blocks_needed*AP4_CIPHER_BLOCK_SIZE) {
411         *out_size = blocks_needed*AP4_CIPHER_BLOCK_SIZE;
412         return AP4_ERROR_BUFFER_TOO_SMALL;
413     }
414     *out_size = blocks_needed*AP4_CIPHER_BLOCK_SIZE;
415     if (blocks_needed && m_OutputSkip) *out_size -= m_OutputSkip;
416 
417     // shortcut
418     if (in_size == 0) return AP4_SUCCESS;
419 
420     // deal with in-block buffering
421     // NOTE: if we have bytes to skip on output, always use the in-block buffer for
422     // the first block, even if we're aligned, this makes the code simpler
423     AP4_ASSERT(m_InBlockFullness < AP4_CIPHER_BLOCK_SIZE);
424     if (m_OutputSkip || m_InBlockFullness) {
425         unsigned int needed = AP4_CIPHER_BLOCK_SIZE-m_InBlockFullness;
426         unsigned int chunk = (in_size > needed) ? needed : in_size;
427         AP4_CopyMemory(&m_InBlock[m_InBlockFullness], in, chunk);
428         in_size           -= chunk;
429         in                += chunk;
430         m_InBlockFullness += chunk;
431         m_StreamOffset    += chunk;
432         if (m_InBlockFullness != AP4_CIPHER_BLOCK_SIZE) {
433             // not enough to continue
434             *out_size = 0;
435             return AP4_SUCCESS;
436         }
437         AP4_UI08 out_block[AP4_CIPHER_BLOCK_SIZE];
438         AP4_Result result = m_BlockCipher->Process(m_InBlock, AP4_CIPHER_BLOCK_SIZE, out_block, m_ChainBlock);
439         m_InBlockFullness = 0;
440         if (AP4_FAILED(result)) {
441             *out_size = 0;
442             return result;
443         }
444         AP4_CopyMemory(m_ChainBlock, m_InBlock, AP4_CIPHER_BLOCK_SIZE);
445         if (m_OutputSkip) {
446             AP4_ASSERT(m_OutputSkip < AP4_CIPHER_BLOCK_SIZE);
447             AP4_CopyMemory(out, &out_block[m_OutputSkip], AP4_CIPHER_BLOCK_SIZE-m_OutputSkip);
448             out += AP4_CIPHER_BLOCK_SIZE-m_OutputSkip;
449             m_OutputSkip = 0;
450         } else {
451             AP4_CopyMemory(out, out_block, AP4_CIPHER_BLOCK_SIZE);
452             out += AP4_CIPHER_BLOCK_SIZE;
453         }
454     }
455     AP4_ASSERT(m_InBlockFullness == 0);
456     AP4_ASSERT(m_OutputSkip == 0);
457 
458     // process full blocks
459     unsigned int block_count = in_size/AP4_CIPHER_BLOCK_SIZE;
460     if (block_count) {
461         AP4_UI32 blocks_size = block_count*AP4_CIPHER_BLOCK_SIZE;
462         AP4_Result result = m_BlockCipher->Process(in, blocks_size, out, m_ChainBlock);
463         AP4_CopyMemory(m_ChainBlock, in+blocks_size-AP4_CIPHER_BLOCK_SIZE, AP4_CIPHER_BLOCK_SIZE);
464         if (AP4_FAILED(result)) {
465             *out_size = 0;
466             return result;
467         }
468         in             += blocks_size;
469         out            += blocks_size;
470         in_size        -= blocks_size;
471         m_StreamOffset += blocks_size;
472     }
473 
474     // buffer partial block leftovers
475     if (in_size) {
476         AP4_ASSERT(in_size < AP4_CIPHER_BLOCK_SIZE);
477         AP4_CopyMemory(m_InBlock, in, in_size);
478         m_InBlockFullness = in_size;
479         m_StreamOffset   += in_size;
480     }
481 
482     // deal with padding
483     if (is_last_buffer) {
484         AP4_UI08 pad_byte = *(out-1);
485         if (pad_byte > AP4_CIPHER_BLOCK_SIZE ||
486             *out_size < pad_byte) {
487             *out_size = 0;
488             return AP4_ERROR_INVALID_FORMAT;
489         }
490         *out_size -= pad_byte;
491     }
492 
493     return AP4_SUCCESS;
494 }
495 
496 /*----------------------------------------------------------------------
497 |   AP4_CbcStreamCipher::ProcessBuffer
498 +---------------------------------------------------------------------*/
499 AP4_Result
ProcessBuffer(const AP4_UI08 * in,AP4_Size in_size,AP4_UI08 * out,AP4_Size * out_size,bool is_last_buffer)500 AP4_CbcStreamCipher::ProcessBuffer(const AP4_UI08* in,
501                                    AP4_Size        in_size,
502                                    AP4_UI08*       out,
503                                    AP4_Size*       out_size,
504                                    bool            is_last_buffer)
505 {
506     // check the parameters
507     if (out_size == NULL) return AP4_ERROR_INVALID_PARAMETERS;
508 
509     // check the state
510     if (m_BlockCipher == NULL || m_Eos) {
511         *out_size = 0;
512         return AP4_ERROR_INVALID_STATE;
513     }
514     if (is_last_buffer) m_Eos = true;
515 
516     if (m_BlockCipher->GetDirection() == AP4_BlockCipher::ENCRYPT) {
517         return EncryptBuffer(in, in_size, out, out_size, is_last_buffer);
518     } else {
519         return DecryptBuffer(in, in_size, out, out_size, is_last_buffer);
520     }
521 }
522