oops
[asdcplib.git] / src / AS_DCP_MXF.cpp
1 /*
2 Copyright (c) 2004-2007, John Hurst
3 All rights reserved.
4
5 Redistribution and use in source and binary forms, with or without
6 modification, are permitted provided that the following conditions
7 are met:
8 1. Redistributions of source code must retain the above copyright
9    notice, this list of conditions and the following disclaimer.
10 2. Redistributions in binary form must reproduce the above copyright
11    notice, this list of conditions and the following disclaimer in the
12    documentation and/or other materials provided with the distribution.
13 3. The name of the author may not be used to endorse or promote products
14    derived from this software without specific prior written permission.
15
16 THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
17 IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
18 OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
19 IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
20 INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
21 NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
25 THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27 /*! \file    AS_DCP_MXF.cpp
28     \version $Id$
29     \brief   AS-DCP library, misc classes and subroutines
30 */
31
32 #include <KM_fileio.h>
33 #include <KM_xml.h>
34 #include "AS_DCP_internal.h"
35 #include "JP2K.h"
36 #include "MPEG.h"
37 #include "Wav.h"
38
39
40 //------------------------------------------------------------------------------------------
41 // misc subroutines
42
43
44 //
45 void
46 ASDCP::WriterInfoDump(const WriterInfo& Info, FILE* stream)
47 {
48   if ( stream == 0 )
49     stream = stderr;
50
51   char str_buf[40];
52
53   fprintf(stream,"       ProductUUID: %s\n", UUID(Info.ProductUUID).EncodeHex(str_buf, 40));
54   fprintf(stream,"\
55     ProductVersion: %s\n\
56        CompanyName: %s\n\
57        ProductName: %s\n\
58   EncryptedEssence: %s\n",
59           Info.ProductVersion.c_str(),
60           Info.CompanyName.c_str(),
61           Info.ProductName.c_str(),
62           ( Info.EncryptedEssence ? "Yes" : "No" )
63           );
64
65   if ( Info.EncryptedEssence )
66     {
67       fprintf(stream, "              HMAC: %s\n", ( Info.UsesHMAC ? "Yes" : "No"));
68       fprintf(stream, "         ContextID: %s\n", UUID(Info.ContextID).EncodeHex(str_buf, 40));
69       fprintf(stream, "CryptographicKeyID: %s\n", UUID(Info.CryptographicKeyID).EncodeHex(str_buf, 40));
70     }
71
72   fprintf(stream,"         AssetUUID: %s\n", UUID(Info.AssetUUID).EncodeHex(str_buf, 40));
73   fprintf(stream,"    Label Set Type: %s\n", ( Info.LabelSetType == LS_MXF_SMPTE ? "SMPTE" :
74                                                ( Info.LabelSetType == LS_MXF_INTEROP ? "MXF Interop" :
75                                                  "Unknown" ) ));
76 }
77
78 //
79 Result_t
80 ASDCP::MD_to_WriterInfo(Identification* InfoObj, WriterInfo& Info)
81 {
82   ASDCP_TEST_NULL(InfoObj);
83   char tmp_str[IdentBufferLen];
84
85   Info.ProductName = "Unknown Product";
86   Info.ProductVersion = "Unknown Version";
87   Info.CompanyName = "Unknown Company";
88   memset(Info.ProductUUID, 0, UUIDlen);
89
90   InfoObj->ProductName.EncodeString(tmp_str, IdentBufferLen);
91   if ( *tmp_str ) Info.ProductName = tmp_str;
92
93   InfoObj->VersionString.EncodeString(tmp_str, IdentBufferLen);
94   if ( *tmp_str ) Info.ProductVersion = tmp_str;
95
96   InfoObj->CompanyName.EncodeString(tmp_str, IdentBufferLen);
97   if ( *tmp_str ) Info.CompanyName = tmp_str;
98
99   memcpy(Info.ProductUUID, InfoObj->ProductUID.Value(), UUIDlen);
100
101   return RESULT_OK;
102 }
103
104
105 //
106 Result_t
107 ASDCP::MD_to_CryptoInfo(CryptographicContext* InfoObj, WriterInfo& Info)
108 {
109   ASDCP_TEST_NULL(InfoObj);
110
111   Info.EncryptedEssence = true;
112   memcpy(Info.ContextID, InfoObj->ContextID.Value(), UUIDlen);
113   memcpy(Info.CryptographicKeyID, InfoObj->CryptographicKeyID.Value(), UUIDlen);
114
115   UL MIC_SHA1(Dict::ul(MDD_MICAlgorithm_HMAC_SHA1));
116   UL MIC_NONE(Dict::ul(MDD_MICAlgorithm_NONE));
117
118   if ( InfoObj->MICAlgorithm == MIC_SHA1 )
119     Info.UsesHMAC = true;
120
121   else if ( InfoObj->MICAlgorithm == MIC_NONE )
122     Info.UsesHMAC = false;
123
124   else
125     {
126       DefaultLogSink().Error("Unexpected MICAlgorithm UL.\n");
127       return RESULT_FORMAT;
128     }
129
130   return RESULT_OK;
131 }
132
133 //
134 //
135 ASDCP::Result_t
136 ASDCP::EssenceType(const char* filename, EssenceType_t& type)
137 {
138   ASDCP_TEST_NULL_STR(filename);
139   Kumu::FileReader   Reader;
140   OPAtomHeader TestHeader;
141
142   Result_t result = Reader.OpenRead(filename);
143
144   if ( ASDCP_SUCCESS(result) )
145     result = TestHeader.InitFromFile(Reader); // test UL and OP
146
147   if ( ASDCP_SUCCESS(result) )
148     {
149       type = ESS_UNKNOWN;
150       if ( ASDCP_SUCCESS(TestHeader.GetMDObjectByType(OBJ_TYPE_ARGS(RGBAEssenceDescriptor))) )
151         type = ESS_JPEG_2000;
152       else
153         {
154           if ( ASDCP_SUCCESS(TestHeader.GetMDObjectByType(OBJ_TYPE_ARGS(WaveAudioDescriptor))) )
155             type = ESS_PCM_24b_48k;
156           else
157             {
158               if ( ASDCP_SUCCESS(TestHeader.GetMDObjectByType(OBJ_TYPE_ARGS(MPEG2VideoDescriptor))) )
159                 type = ESS_MPEG2_VES;
160               else
161                 {
162                   if ( ASDCP_SUCCESS(TestHeader.GetMDObjectByType(OBJ_TYPE_ARGS(DCTimedTextDescriptor))) )
163                     type = ESS_TIMED_TEXT;
164                 }
165             }
166         }
167     }
168
169   return result;
170 }
171
172 //
173 ASDCP::Result_t
174 ASDCP::RawEssenceType(const char* filename, EssenceType_t& type)
175 {
176   ASDCP_TEST_NULL_STR(filename);
177   type = ESS_UNKNOWN;
178   ASDCP::FrameBuffer FB;
179   Kumu::FileReader Reader;
180   ASDCP::Wav::SimpleWaveHeader WavHeader;
181   ASDCP::AIFF::SimpleAIFFHeader AIFFHeader;
182   Kumu::XMLElement TmpElement("Tmp");
183
184   ui32_t data_offset;
185   ui32_t read_count;
186   Result_t result = FB.Capacity(Wav::MaxWavHeader); // using Wav max because everything else is much smaller
187
188   if ( Kumu::PathIsFile(filename) )
189     {
190       result = Reader.OpenRead(filename);
191
192       if ( ASDCP_SUCCESS(result) )
193         {
194           result = Reader.Read(FB.Data(), FB.Capacity(), &read_count);
195           Reader.Close();
196         }
197
198       if ( ASDCP_SUCCESS(result) )
199         {
200           const byte_t* p = FB.RoData();
201           FB.Size(read_count);
202
203           ui32_t i = 0;
204           while ( p[i] == 0 ) i++;
205
206           if ( i > 1 && p[i] == 1 &&  (p[i+1] == ASDCP::MPEG2::SEQ_START || p[i+1] == ASDCP::MPEG2::PIC_START) )
207             type = ESS_MPEG2_VES;
208
209           else if ( Kumu::StringIsXML((const char*)p, FB.Size()) )
210             type = ESS_TIMED_TEXT;
211
212           else if ( ASDCP_SUCCESS(WavHeader.ReadFromBuffer(p, read_count, &data_offset)) )
213             type = ESS_PCM_24b_48k;
214
215           else if ( ASDCP_SUCCESS(AIFFHeader.ReadFromBuffer(p, read_count, &data_offset)) )
216             type = ESS_PCM_24b_48k;
217         }
218     }
219   else if ( Kumu::PathIsDirectory(filename) )
220     {
221       char next_file[Kumu::MaxFilePath];
222       Kumu::DirScanner Scanner;
223       Result_t result = Scanner.Open(filename);
224
225       if ( ASDCP_SUCCESS(result) )
226         {
227           while ( ASDCP_SUCCESS(Scanner.GetNext(next_file)) )
228             {
229               if ( next_file[0] == '.' ) // no hidden files or internal links
230                 continue;
231
232               std::string Str(filename);
233               Str += "/";
234               Str += next_file;
235               result = Reader.OpenRead(Str.c_str());
236
237               if ( ASDCP_SUCCESS(result) )
238                 {
239                   result = Reader.Read(FB.Data(), FB.Capacity(), &read_count);
240                   Reader.Close();
241                 }
242
243               if ( ASDCP_SUCCESS(result) )
244                 {
245                   if ( memcmp(FB.RoData(), ASDCP::JP2K::Magic, sizeof(ASDCP::JP2K::Magic)) == 0 )
246                     type = ESS_JPEG_2000;
247
248                   else if ( ASDCP_SUCCESS(WavHeader.ReadFromBuffer(FB.RoData(), read_count, &data_offset)) )
249                     type = ESS_PCM_24b_48k;
250                 }
251
252               break;
253             }
254         }
255     }
256
257   return result;
258 }
259
260 //
261 Result_t
262 ASDCP::EncryptFrameBuffer(const ASDCP::FrameBuffer& FBin, ASDCP::FrameBuffer& FBout, AESEncContext* Ctx)
263 {
264   ASDCP_TEST_NULL(Ctx);
265   FBout.Size(0);
266
267   // size the buffer
268   Result_t result = FBout.Capacity(calc_esv_length(FBin.Size(), FBin.PlaintextOffset()));
269
270   // write the IV
271   byte_t* p = FBout.Data();
272
273   // write the IV to the frame buffer
274   Ctx->GetIVec(p);
275   p += CBC_BLOCK_SIZE;
276
277
278   // encrypt the check value to the frame buffer
279   if ( ASDCP_SUCCESS(result) )
280     {
281       result = Ctx->EncryptBlock(ESV_CheckValue, p, CBC_BLOCK_SIZE);
282       p += CBC_BLOCK_SIZE;
283     }
284
285   // write optional plaintext region
286   if ( FBin.PlaintextOffset() > 0 )
287     {
288       assert(FBin.PlaintextOffset() <= FBin.Size());
289       memcpy(p, FBin.RoData(), FBin.PlaintextOffset());
290       p += FBin.PlaintextOffset();
291     }
292
293   ui32_t ct_size = FBin.Size() - FBin.PlaintextOffset();
294   ui32_t diff = ct_size % CBC_BLOCK_SIZE;
295   ui32_t block_size = ct_size - diff;
296   assert((block_size % CBC_BLOCK_SIZE) == 0);
297
298   // encrypt the ciphertext region essence data
299   if ( ASDCP_SUCCESS(result) )
300     {
301       result = Ctx->EncryptBlock(FBin.RoData() + FBin.PlaintextOffset(), p, block_size);
302       p += block_size;
303     }
304
305   // construct and encrypt the padding
306   if ( ASDCP_SUCCESS(result) )
307     {
308       byte_t the_last_block[CBC_BLOCK_SIZE];
309
310       if ( diff > 0 )
311         memcpy(the_last_block, FBin.RoData() + FBin.PlaintextOffset() + block_size, diff);
312
313       for (ui32_t i = 0; diff < CBC_BLOCK_SIZE; diff++, i++ )
314         the_last_block[diff] = i;
315
316       result = Ctx->EncryptBlock(the_last_block, p, CBC_BLOCK_SIZE);
317     }
318
319   if ( ASDCP_SUCCESS(result) )
320     FBout.Size(calc_esv_length(FBin.Size(), FBin.PlaintextOffset()));
321
322   return result;
323 }
324
325 //
326 Result_t
327 ASDCP::DecryptFrameBuffer(const ASDCP::FrameBuffer& FBin, ASDCP::FrameBuffer& FBout, AESDecContext* Ctx)
328 {
329   ASDCP_TEST_NULL(Ctx);
330   assert(FBout.Capacity() >= FBin.SourceLength());
331
332   ui32_t ct_size = FBin.SourceLength() - FBin.PlaintextOffset();
333   ui32_t diff = ct_size % CBC_BLOCK_SIZE;
334   ui32_t block_size = ct_size - diff;
335   assert(block_size);
336   assert((block_size % CBC_BLOCK_SIZE) == 0);
337
338   const byte_t* buf = FBin.RoData();
339
340   // get ivec
341   Ctx->SetIVec(buf);
342   buf += CBC_BLOCK_SIZE;
343
344   // decrypt and test check value
345   byte_t CheckValue[CBC_BLOCK_SIZE];
346   Result_t result = Ctx->DecryptBlock(buf, CheckValue, CBC_BLOCK_SIZE);
347   buf += CBC_BLOCK_SIZE;
348
349   if ( memcmp(CheckValue, ESV_CheckValue, CBC_BLOCK_SIZE) != 0 )
350     return RESULT_CHECKFAIL;
351
352   // copy plaintext region
353   if ( FBin.PlaintextOffset() > 0 )
354     {
355       memcpy(FBout.Data(), buf, FBin.PlaintextOffset());
356       buf += FBin.PlaintextOffset();
357     }
358
359   // decrypt all but last block
360   if ( ASDCP_SUCCESS(result) )
361     {
362       result = Ctx->DecryptBlock(buf, FBout.Data() + FBin.PlaintextOffset(), block_size);
363       buf += block_size;
364     }
365
366   // decrypt last block
367   if ( ASDCP_SUCCESS(result) )
368     {
369       byte_t the_last_block[CBC_BLOCK_SIZE];
370       result = Ctx->DecryptBlock(buf, the_last_block, CBC_BLOCK_SIZE);
371
372       if ( the_last_block[diff] != 0 )
373         {
374           DefaultLogSink().Error("Unexpected non-zero padding value.\n");
375           return RESULT_FORMAT;
376         }
377
378       if ( diff > 0 )
379         memcpy(FBout.Data() + FBin.PlaintextOffset() + block_size, the_last_block, diff);
380     }
381
382   if ( ASDCP_SUCCESS(result) )
383     FBout.Size(FBin.SourceLength());
384
385   return result;
386 }
387
388
389 //
390 Result_t
391 ASDCP::IntegrityPack::CalcValues(const ASDCP::FrameBuffer& FB, byte_t* AssetID,
392                                  ui32_t sequence, HMACContext* HMAC)
393 {
394   ASDCP_TEST_NULL(AssetID);
395   ASDCP_TEST_NULL(HMAC);
396   byte_t* p = Data;
397   HMAC->Reset();
398
399   static byte_t ber_4[MXF_BER_LENGTH] = {0x83, 0};
400
401   // update HMAC with essence data
402   HMAC->Update(FB.RoData(), FB.Size());
403
404   // track file ID length
405   memcpy(p, ber_4, MXF_BER_LENGTH);
406   *(p+3) = UUIDlen;;
407   p += MXF_BER_LENGTH;
408
409   // track file ID
410   memcpy(p, AssetID, UUIDlen);
411   p += UUIDlen;
412
413   // sequence length
414   memcpy(p, ber_4, MXF_BER_LENGTH);
415   *(p+3) = sizeof(ui64_t);
416   p += MXF_BER_LENGTH;
417
418   // sequence number
419   Kumu::i2p<ui64_t>(KM_i64_BE(sequence), p);
420   p += sizeof(ui64_t);
421
422   // HMAC length
423   memcpy(p, ber_4, MXF_BER_LENGTH);
424   *(p+3) = HMAC_SIZE;
425   p += MXF_BER_LENGTH;
426
427   // update HMAC with intpack values
428   HMAC->Update(Data, klv_intpack_size - HMAC_SIZE);
429
430   // finish & write HMAC
431   HMAC->Finalize();
432   HMAC->GetHMACValue(p);
433
434   assert(p + HMAC_SIZE == Data + klv_intpack_size);
435
436   return RESULT_OK;
437 }
438
439
440 Result_t
441 ASDCP::IntegrityPack::TestValues(const ASDCP::FrameBuffer& FB, byte_t* AssetID,
442                                  ui32_t sequence, HMACContext* HMAC)
443 {
444   ASDCP_TEST_NULL(AssetID);
445   ASDCP_TEST_NULL(HMAC);
446
447   // find the start of the intpack
448   byte_t* p = (byte_t*)FB.RoData() + ( FB.Size() - klv_intpack_size );
449
450   // test the AssetID length
451   if ( ! Kumu::read_test_BER(&p, UUIDlen) )
452         return RESULT_HMACFAIL;
453
454   // test the AssetID
455   if ( memcmp(p, AssetID, UUIDlen) != 0 )
456     {
457       DefaultLogSink().Error("IntegrityPack failure: AssetID mismatch.\n");
458       return RESULT_HMACFAIL;
459     }
460   p += UUIDlen;
461   
462   // test the sequence length
463   if ( ! Kumu::read_test_BER(&p, sizeof(ui64_t)) )
464         return RESULT_HMACFAIL;
465
466   ui32_t test_sequence = (ui32_t)KM_i64_BE(Kumu::cp2i<ui64_t>(p));
467
468   // test the sequence value
469   if ( test_sequence != sequence )
470     {
471       DefaultLogSink().Error("IntegrityPack failure: sequence is %u, expecting %u.\n", test_sequence, sequence);
472       return RESULT_HMACFAIL;
473     }
474
475   p += sizeof(ui64_t);
476
477   // test the HMAC length
478   if ( ! Kumu::read_test_BER(&p, HMAC_SIZE) )
479         return RESULT_HMACFAIL;
480
481   // test the HMAC
482   HMAC->Reset();
483   HMAC->Update(FB.RoData(), FB.Size() - HMAC_SIZE);
484   HMAC->Finalize();
485
486   return HMAC->TestHMACValue(p);
487 }
488
489 //
490 // end AS_DCP_MXF.cpp
491 //