192965d78416c8f4d905a4cb8328bf8589594429
[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         {
152           if ( ASDCP_SUCCESS(TestHeader.GetMDObjectByType(OBJ_TYPE_ARGS(StereoscopicPictureSubDescriptor))) )
153             type = ESS_JPEG_2000_S;
154           else
155             type = ESS_JPEG_2000;
156         }
157       else if ( ASDCP_SUCCESS(TestHeader.GetMDObjectByType(OBJ_TYPE_ARGS(WaveAudioDescriptor))) )
158         type = ESS_PCM_24b_48k;
159       else if ( ASDCP_SUCCESS(TestHeader.GetMDObjectByType(OBJ_TYPE_ARGS(MPEG2VideoDescriptor))) )
160         type = ESS_MPEG2_VES;
161       else if ( ASDCP_SUCCESS(TestHeader.GetMDObjectByType(OBJ_TYPE_ARGS(TimedTextDescriptor))) )
162         type = ESS_TIMED_TEXT;
163     }
164
165   return result;
166 }
167
168 //
169 ASDCP::Result_t
170 ASDCP::RawEssenceType(const char* filename, EssenceType_t& type)
171 {
172   ASDCP_TEST_NULL_STR(filename);
173   type = ESS_UNKNOWN;
174   ASDCP::FrameBuffer FB;
175   Kumu::FileReader Reader;
176   ASDCP::Wav::SimpleWaveHeader WavHeader;
177   ASDCP::AIFF::SimpleAIFFHeader AIFFHeader;
178   Kumu::XMLElement TmpElement("Tmp");
179
180   ui32_t data_offset;
181   ui32_t read_count;
182   Result_t result = FB.Capacity(Wav::MaxWavHeader); // using Wav max because everything else is much smaller
183
184   if ( Kumu::PathIsFile(filename) )
185     {
186       result = Reader.OpenRead(filename);
187
188       if ( ASDCP_SUCCESS(result) )
189         {
190           result = Reader.Read(FB.Data(), FB.Capacity(), &read_count);
191           Reader.Close();
192         }
193
194       if ( ASDCP_SUCCESS(result) )
195         {
196           const byte_t* p = FB.RoData();
197           FB.Size(read_count);
198
199           ui32_t i = 0;
200           while ( p[i] == 0 ) i++;
201
202           if ( i > 1 && p[i] == 1 &&  (p[i+1] == ASDCP::MPEG2::SEQ_START || p[i+1] == ASDCP::MPEG2::PIC_START) )
203             type = ESS_MPEG2_VES;
204
205           else if ( ASDCP_SUCCESS(WavHeader.ReadFromBuffer(p, read_count, &data_offset)) )
206             type = ESS_PCM_24b_48k;
207
208           else if ( ASDCP_SUCCESS(AIFFHeader.ReadFromBuffer(p, read_count, &data_offset)) )
209             type = ESS_PCM_24b_48k;
210
211           else if ( Kumu::StringIsXML((const char*)p, FB.Size()) )
212             type = ESS_TIMED_TEXT;
213         }
214     }
215   else if ( Kumu::PathIsDirectory(filename) )
216     {
217       char next_file[Kumu::MaxFilePath];
218       Kumu::DirScanner Scanner;
219       Result_t result = Scanner.Open(filename);
220
221       if ( ASDCP_SUCCESS(result) )
222         {
223           while ( ASDCP_SUCCESS(Scanner.GetNext(next_file)) )
224             {
225               if ( next_file[0] == '.' ) // no hidden files or internal links
226                 continue;
227
228               std::string Str(filename);
229               Str += "/";
230               Str += next_file;
231               result = Reader.OpenRead(Str.c_str());
232
233               if ( ASDCP_SUCCESS(result) )
234                 {
235                   result = Reader.Read(FB.Data(), FB.Capacity(), &read_count);
236                   Reader.Close();
237                 }
238
239               if ( ASDCP_SUCCESS(result) )
240                 {
241                   if ( memcmp(FB.RoData(), ASDCP::JP2K::Magic, sizeof(ASDCP::JP2K::Magic)) == 0 )
242                     type = ESS_JPEG_2000;
243
244                   else if ( ASDCP_SUCCESS(WavHeader.ReadFromBuffer(FB.RoData(), read_count, &data_offset)) )
245                     type = ESS_PCM_24b_48k;
246                 }
247
248               break;
249             }
250         }
251     }
252
253   return result;
254 }
255
256 //
257 Result_t
258 ASDCP::EncryptFrameBuffer(const ASDCP::FrameBuffer& FBin, ASDCP::FrameBuffer& FBout, AESEncContext* Ctx)
259 {
260   ASDCP_TEST_NULL(Ctx);
261   FBout.Size(0);
262
263   // size the buffer
264   Result_t result = FBout.Capacity(calc_esv_length(FBin.Size(), FBin.PlaintextOffset()));
265
266   // write the IV
267   byte_t* p = FBout.Data();
268
269   // write the IV to the frame buffer
270   Ctx->GetIVec(p);
271   p += CBC_BLOCK_SIZE;
272
273
274   // encrypt the check value to the frame buffer
275   if ( ASDCP_SUCCESS(result) )
276     {
277       result = Ctx->EncryptBlock(ESV_CheckValue, p, CBC_BLOCK_SIZE);
278       p += CBC_BLOCK_SIZE;
279     }
280
281   // write optional plaintext region
282   if ( FBin.PlaintextOffset() > 0 )
283     {
284       assert(FBin.PlaintextOffset() <= FBin.Size());
285       memcpy(p, FBin.RoData(), FBin.PlaintextOffset());
286       p += FBin.PlaintextOffset();
287     }
288
289   ui32_t ct_size = FBin.Size() - FBin.PlaintextOffset();
290   ui32_t diff = ct_size % CBC_BLOCK_SIZE;
291   ui32_t block_size = ct_size - diff;
292   assert((block_size % CBC_BLOCK_SIZE) == 0);
293
294   // encrypt the ciphertext region essence data
295   if ( ASDCP_SUCCESS(result) )
296     {
297       result = Ctx->EncryptBlock(FBin.RoData() + FBin.PlaintextOffset(), p, block_size);
298       p += block_size;
299     }
300
301   // construct and encrypt the padding
302   if ( ASDCP_SUCCESS(result) )
303     {
304       byte_t the_last_block[CBC_BLOCK_SIZE];
305
306       if ( diff > 0 )
307         memcpy(the_last_block, FBin.RoData() + FBin.PlaintextOffset() + block_size, diff);
308
309       for (ui32_t i = 0; diff < CBC_BLOCK_SIZE; diff++, i++ )
310         the_last_block[diff] = i;
311
312       result = Ctx->EncryptBlock(the_last_block, p, CBC_BLOCK_SIZE);
313     }
314
315   if ( ASDCP_SUCCESS(result) )
316     FBout.Size(calc_esv_length(FBin.Size(), FBin.PlaintextOffset()));
317
318   return result;
319 }
320
321 //
322 Result_t
323 ASDCP::DecryptFrameBuffer(const ASDCP::FrameBuffer& FBin, ASDCP::FrameBuffer& FBout, AESDecContext* Ctx)
324 {
325   ASDCP_TEST_NULL(Ctx);
326   assert(FBout.Capacity() >= FBin.SourceLength());
327
328   ui32_t ct_size = FBin.SourceLength() - FBin.PlaintextOffset();
329   ui32_t diff = ct_size % CBC_BLOCK_SIZE;
330   ui32_t block_size = ct_size - diff;
331   assert(block_size);
332   assert((block_size % CBC_BLOCK_SIZE) == 0);
333
334   const byte_t* buf = FBin.RoData();
335
336   // get ivec
337   Ctx->SetIVec(buf);
338   buf += CBC_BLOCK_SIZE;
339
340   // decrypt and test check value
341   byte_t CheckValue[CBC_BLOCK_SIZE];
342   Result_t result = Ctx->DecryptBlock(buf, CheckValue, CBC_BLOCK_SIZE);
343   buf += CBC_BLOCK_SIZE;
344
345   if ( memcmp(CheckValue, ESV_CheckValue, CBC_BLOCK_SIZE) != 0 )
346     return RESULT_CHECKFAIL;
347
348   // copy plaintext region
349   if ( FBin.PlaintextOffset() > 0 )
350     {
351       memcpy(FBout.Data(), buf, FBin.PlaintextOffset());
352       buf += FBin.PlaintextOffset();
353     }
354
355   // decrypt all but last block
356   if ( ASDCP_SUCCESS(result) )
357     {
358       result = Ctx->DecryptBlock(buf, FBout.Data() + FBin.PlaintextOffset(), block_size);
359       buf += block_size;
360     }
361
362   // decrypt last block
363   if ( ASDCP_SUCCESS(result) )
364     {
365       byte_t the_last_block[CBC_BLOCK_SIZE];
366       result = Ctx->DecryptBlock(buf, the_last_block, CBC_BLOCK_SIZE);
367
368       if ( the_last_block[diff] != 0 )
369         {
370           DefaultLogSink().Error("Unexpected non-zero padding value.\n");
371           return RESULT_FORMAT;
372         }
373
374       if ( diff > 0 )
375         memcpy(FBout.Data() + FBin.PlaintextOffset() + block_size, the_last_block, diff);
376     }
377
378   if ( ASDCP_SUCCESS(result) )
379     FBout.Size(FBin.SourceLength());
380
381   return result;
382 }
383
384
385 //
386 Result_t
387 ASDCP::IntegrityPack::CalcValues(const ASDCP::FrameBuffer& FB, byte_t* AssetID,
388                                  ui32_t sequence, HMACContext* HMAC)
389 {
390   ASDCP_TEST_NULL(AssetID);
391   ASDCP_TEST_NULL(HMAC);
392   byte_t* p = Data;
393   HMAC->Reset();
394
395   static byte_t ber_4[MXF_BER_LENGTH] = {0x83, 0};
396
397   // update HMAC with essence data
398   HMAC->Update(FB.RoData(), FB.Size());
399
400   // track file ID length
401   memcpy(p, ber_4, MXF_BER_LENGTH);
402   *(p+3) = UUIDlen;;
403   p += MXF_BER_LENGTH;
404
405   // track file ID
406   memcpy(p, AssetID, UUIDlen);
407   p += UUIDlen;
408
409   // sequence length
410   memcpy(p, ber_4, MXF_BER_LENGTH);
411   *(p+3) = sizeof(ui64_t);
412   p += MXF_BER_LENGTH;
413
414   // sequence number
415   Kumu::i2p<ui64_t>(KM_i64_BE(sequence), p);
416   p += sizeof(ui64_t);
417
418   // HMAC length
419   memcpy(p, ber_4, MXF_BER_LENGTH);
420   *(p+3) = HMAC_SIZE;
421   p += MXF_BER_LENGTH;
422
423   // update HMAC with intpack values
424   HMAC->Update(Data, klv_intpack_size - HMAC_SIZE);
425
426   // finish & write HMAC
427   HMAC->Finalize();
428   HMAC->GetHMACValue(p);
429
430   assert(p + HMAC_SIZE == Data + klv_intpack_size);
431
432   return RESULT_OK;
433 }
434
435
436 Result_t
437 ASDCP::IntegrityPack::TestValues(const ASDCP::FrameBuffer& FB, byte_t* AssetID,
438                                  ui32_t sequence, HMACContext* HMAC)
439 {
440   ASDCP_TEST_NULL(AssetID);
441   ASDCP_TEST_NULL(HMAC);
442
443   // find the start of the intpack
444   byte_t* p = (byte_t*)FB.RoData() + ( FB.Size() - klv_intpack_size );
445
446   // test the AssetID length
447   if ( ! Kumu::read_test_BER(&p, UUIDlen) )
448         return RESULT_HMACFAIL;
449
450   // test the AssetID
451   if ( memcmp(p, AssetID, UUIDlen) != 0 )
452     {
453       DefaultLogSink().Error("IntegrityPack failure: AssetID mismatch.\n");
454       return RESULT_HMACFAIL;
455     }
456   p += UUIDlen;
457   
458   // test the sequence length
459   if ( ! Kumu::read_test_BER(&p, sizeof(ui64_t)) )
460         return RESULT_HMACFAIL;
461
462   ui32_t test_sequence = (ui32_t)KM_i64_BE(Kumu::cp2i<ui64_t>(p));
463
464   // test the sequence value
465   if ( test_sequence != sequence )
466     {
467       DefaultLogSink().Error("IntegrityPack failure: sequence is %u, expecting %u.\n", test_sequence, sequence);
468       return RESULT_HMACFAIL;
469     }
470
471   p += sizeof(ui64_t);
472
473   // test the HMAC length
474   if ( ! Kumu::read_test_BER(&p, HMAC_SIZE) )
475         return RESULT_HMACFAIL;
476
477   // test the HMAC
478   HMAC->Reset();
479   HMAC->Update(FB.RoData(), FB.Size() - HMAC_SIZE);
480   HMAC->Finalize();
481
482   return HMAC->TestHMACValue(p);
483 }
484
485 //
486 // end AS_DCP_MXF.cpp
487 //