334d253754286e542a86e330d54ec799af6e4485
[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 #include <iostream>
39 #include <iomanip>
40
41
42 //------------------------------------------------------------------------------------------
43 // misc subroutines
44
45
46 //
47 std::ostream&
48 ASDCP::operator << (std::ostream& strm, const WriterInfo& Info)
49 {
50   char str_buf[40];
51
52   strm << "       ProductUUID: " << UUID(Info.ProductUUID).EncodeHex(str_buf, 40) << std::endl;
53   strm << "    ProductVersion: " << Info.ProductVersion << std::endl;
54   strm << "       CompanyName: " << Info.CompanyName << std::endl;
55   strm << "       ProductName: " << Info.ProductName << std::endl;
56   strm << "  EncryptedEssence: " << (Info.EncryptedEssence ? "Yes" : "No") << std::endl;
57
58   if ( Info.EncryptedEssence )
59     {
60       strm << "              HMAC: " << (Info.UsesHMAC ? "Yes" : "No") << std::endl;
61       strm << "         ContextID: " << UUID(Info.ContextID).EncodeHex(str_buf, 40) << std::endl;
62       strm << "CryptographicKeyID: " << UUID(Info.CryptographicKeyID).EncodeHex(str_buf, 40) << std::endl;
63     }
64
65   strm << "         AssetUUID: " << UUID(Info.AssetUUID).EncodeHex(str_buf, 40) << std::endl;
66   strm << "    Label Set Type: " << (Info.LabelSetType == LS_MXF_SMPTE ? "SMPTE" :
67                                      (Info.LabelSetType == LS_MXF_INTEROP ? "MXF Interop" :
68                                       "Unknown")) << std::endl;
69   return strm;
70 }
71
72 //
73 void
74 ASDCP::WriterInfoDump(const WriterInfo& Info, FILE* stream)
75 {
76   if ( stream == 0 )
77     stream = stderr;
78
79   char str_buf[40];
80
81   fprintf(stream,"       ProductUUID: %s\n", UUID(Info.ProductUUID).EncodeHex(str_buf, 40));
82   fprintf(stream,"\
83     ProductVersion: %s\n\
84        CompanyName: %s\n\
85        ProductName: %s\n\
86   EncryptedEssence: %s\n",
87           Info.ProductVersion.c_str(),
88           Info.CompanyName.c_str(),
89           Info.ProductName.c_str(),
90           ( Info.EncryptedEssence ? "Yes" : "No" )
91           );
92
93   if ( Info.EncryptedEssence )
94     {
95       fprintf(stream, "              HMAC: %s\n", ( Info.UsesHMAC ? "Yes" : "No"));
96       fprintf(stream, "         ContextID: %s\n", UUID(Info.ContextID).EncodeHex(str_buf, 40));
97       fprintf(stream, "CryptographicKeyID: %s\n", UUID(Info.CryptographicKeyID).EncodeHex(str_buf, 40));
98     }
99
100   fprintf(stream,"         AssetUUID: %s\n", UUID(Info.AssetUUID).EncodeHex(str_buf, 40));
101   fprintf(stream,"    Label Set Type: %s\n", ( Info.LabelSetType == LS_MXF_SMPTE ? "SMPTE" :
102                                                ( Info.LabelSetType == LS_MXF_INTEROP ? "MXF Interop" :
103                                                  "Unknown" ) ));
104 }
105
106 //
107 Result_t
108 ASDCP::MD_to_WriterInfo(Identification* InfoObj, WriterInfo& Info)
109 {
110   ASDCP_TEST_NULL(InfoObj);
111   char tmp_str[IdentBufferLen];
112
113   Info.ProductName = "Unknown Product";
114   Info.ProductVersion = "Unknown Version";
115   Info.CompanyName = "Unknown Company";
116   memset(Info.ProductUUID, 0, UUIDlen);
117
118   InfoObj->ProductName.EncodeString(tmp_str, IdentBufferLen);
119   if ( *tmp_str ) Info.ProductName = tmp_str;
120
121   InfoObj->VersionString.EncodeString(tmp_str, IdentBufferLen);
122   if ( *tmp_str ) Info.ProductVersion = tmp_str;
123
124   InfoObj->CompanyName.EncodeString(tmp_str, IdentBufferLen);
125   if ( *tmp_str ) Info.CompanyName = tmp_str;
126
127   memcpy(Info.ProductUUID, InfoObj->ProductUID.Value(), UUIDlen);
128
129   return RESULT_OK;
130 }
131
132
133 //
134 Result_t
135 ASDCP::MD_to_CryptoInfo(CryptographicContext* InfoObj, WriterInfo& Info)
136 {
137   ASDCP_TEST_NULL(InfoObj);
138
139   Info.EncryptedEssence = true;
140   memcpy(Info.ContextID, InfoObj->ContextID.Value(), UUIDlen);
141   memcpy(Info.CryptographicKeyID, InfoObj->CryptographicKeyID.Value(), UUIDlen);
142
143   UL MIC_SHA1(Dict::ul(MDD_MICAlgorithm_HMAC_SHA1));
144   UL MIC_NONE(Dict::ul(MDD_MICAlgorithm_NONE));
145
146   if ( InfoObj->MICAlgorithm == MIC_SHA1 )
147     Info.UsesHMAC = true;
148
149   else if ( InfoObj->MICAlgorithm == MIC_NONE )
150     Info.UsesHMAC = false;
151
152   else
153     {
154       DefaultLogSink().Error("Unexpected MICAlgorithm UL.\n");
155       return RESULT_FORMAT;
156     }
157
158   return RESULT_OK;
159 }
160
161 //
162 //
163 ASDCP::Result_t
164 ASDCP::EssenceType(const char* filename, EssenceType_t& type)
165 {
166   ASDCP_TEST_NULL_STR(filename);
167   Kumu::FileReader   Reader;
168   OPAtomHeader TestHeader;
169
170   Result_t result = Reader.OpenRead(filename);
171
172   if ( ASDCP_SUCCESS(result) )
173     result = TestHeader.InitFromFile(Reader); // test UL and OP
174
175   if ( ASDCP_SUCCESS(result) )
176     {
177       type = ESS_UNKNOWN;
178       if ( ASDCP_SUCCESS(TestHeader.GetMDObjectByType(OBJ_TYPE_ARGS(RGBAEssenceDescriptor))) )
179         {
180           if ( ASDCP_SUCCESS(TestHeader.GetMDObjectByType(OBJ_TYPE_ARGS(StereoscopicPictureSubDescriptor))) )
181             type = ESS_JPEG_2000_S;
182           else
183             type = ESS_JPEG_2000;
184         }
185       else if ( ASDCP_SUCCESS(TestHeader.GetMDObjectByType(OBJ_TYPE_ARGS(WaveAudioDescriptor))) )
186         type = ESS_PCM_24b_48k;
187       else if ( ASDCP_SUCCESS(TestHeader.GetMDObjectByType(OBJ_TYPE_ARGS(MPEG2VideoDescriptor))) )
188         type = ESS_MPEG2_VES;
189       else if ( ASDCP_SUCCESS(TestHeader.GetMDObjectByType(OBJ_TYPE_ARGS(TimedTextDescriptor))) )
190         type = ESS_TIMED_TEXT;
191     }
192
193   return result;
194 }
195
196 //
197 ASDCP::Result_t
198 ASDCP::RawEssenceType(const char* filename, EssenceType_t& type)
199 {
200   ASDCP_TEST_NULL_STR(filename);
201   type = ESS_UNKNOWN;
202   ASDCP::FrameBuffer FB;
203   Kumu::FileReader Reader;
204   ASDCP::Wav::SimpleWaveHeader WavHeader;
205   ASDCP::AIFF::SimpleAIFFHeader AIFFHeader;
206   Kumu::XMLElement TmpElement("Tmp");
207
208   ui32_t data_offset;
209   ui32_t read_count;
210   Result_t result = FB.Capacity(Wav::MaxWavHeader); // using Wav max because everything else is much smaller
211
212   if ( Kumu::PathIsFile(filename) )
213     {
214       result = Reader.OpenRead(filename);
215
216       if ( ASDCP_SUCCESS(result) )
217         {
218           result = Reader.Read(FB.Data(), FB.Capacity(), &read_count);
219           Reader.Close();
220         }
221
222       if ( ASDCP_SUCCESS(result) )
223         {
224           const byte_t* p = FB.RoData();
225           FB.Size(read_count);
226
227           ui32_t i = 0;
228           while ( p[i] == 0 ) i++;
229
230           if ( i > 1 && p[i] == 1 &&  (p[i+1] == ASDCP::MPEG2::SEQ_START || p[i+1] == ASDCP::MPEG2::PIC_START) )
231             type = ESS_MPEG2_VES;
232
233           else if ( ASDCP_SUCCESS(WavHeader.ReadFromBuffer(p, read_count, &data_offset)) )
234             type = ESS_PCM_24b_48k;
235
236           else if ( ASDCP_SUCCESS(AIFFHeader.ReadFromBuffer(p, read_count, &data_offset)) )
237             type = ESS_PCM_24b_48k;
238
239           else if ( Kumu::StringIsXML((const char*)p, FB.Size()) )
240             type = ESS_TIMED_TEXT;
241         }
242     }
243   else if ( Kumu::PathIsDirectory(filename) )
244     {
245       char next_file[Kumu::MaxFilePath];
246       Kumu::DirScanner Scanner;
247       Result_t result = Scanner.Open(filename);
248
249       if ( ASDCP_SUCCESS(result) )
250         {
251           while ( ASDCP_SUCCESS(Scanner.GetNext(next_file)) )
252             {
253               if ( next_file[0] == '.' ) // no hidden files or internal links
254                 continue;
255
256               std::string Str(filename);
257               Str += "/";
258               Str += next_file;
259               result = Reader.OpenRead(Str.c_str());
260
261               if ( ASDCP_SUCCESS(result) )
262                 {
263                   result = Reader.Read(FB.Data(), FB.Capacity(), &read_count);
264                   Reader.Close();
265                 }
266
267               if ( ASDCP_SUCCESS(result) )
268                 {
269                   if ( memcmp(FB.RoData(), ASDCP::JP2K::Magic, sizeof(ASDCP::JP2K::Magic)) == 0 )
270                     type = ESS_JPEG_2000;
271
272                   else if ( ASDCP_SUCCESS(WavHeader.ReadFromBuffer(FB.RoData(), read_count, &data_offset)) )
273                     type = ESS_PCM_24b_48k;
274                 }
275
276               break;
277             }
278         }
279     }
280
281   return result;
282 }
283
284 //
285 Result_t
286 ASDCP::EncryptFrameBuffer(const ASDCP::FrameBuffer& FBin, ASDCP::FrameBuffer& FBout, AESEncContext* Ctx)
287 {
288   ASDCP_TEST_NULL(Ctx);
289   FBout.Size(0);
290
291   // size the buffer
292   Result_t result = FBout.Capacity(calc_esv_length(FBin.Size(), FBin.PlaintextOffset()));
293
294   // write the IV
295   byte_t* p = FBout.Data();
296
297   // write the IV to the frame buffer
298   Ctx->GetIVec(p);
299   p += CBC_BLOCK_SIZE;
300
301
302   // encrypt the check value to the frame buffer
303   if ( ASDCP_SUCCESS(result) )
304     {
305       result = Ctx->EncryptBlock(ESV_CheckValue, p, CBC_BLOCK_SIZE);
306       p += CBC_BLOCK_SIZE;
307     }
308
309   // write optional plaintext region
310   if ( FBin.PlaintextOffset() > 0 )
311     {
312       assert(FBin.PlaintextOffset() <= FBin.Size());
313       memcpy(p, FBin.RoData(), FBin.PlaintextOffset());
314       p += FBin.PlaintextOffset();
315     }
316
317   ui32_t ct_size = FBin.Size() - FBin.PlaintextOffset();
318   ui32_t diff = ct_size % CBC_BLOCK_SIZE;
319   ui32_t block_size = ct_size - diff;
320   assert((block_size % CBC_BLOCK_SIZE) == 0);
321
322   // encrypt the ciphertext region essence data
323   if ( ASDCP_SUCCESS(result) )
324     {
325       result = Ctx->EncryptBlock(FBin.RoData() + FBin.PlaintextOffset(), p, block_size);
326       p += block_size;
327     }
328
329   // construct and encrypt the padding
330   if ( ASDCP_SUCCESS(result) )
331     {
332       byte_t the_last_block[CBC_BLOCK_SIZE];
333
334       if ( diff > 0 )
335         memcpy(the_last_block, FBin.RoData() + FBin.PlaintextOffset() + block_size, diff);
336
337       for (ui32_t i = 0; diff < CBC_BLOCK_SIZE; diff++, i++ )
338         the_last_block[diff] = i;
339
340       result = Ctx->EncryptBlock(the_last_block, p, CBC_BLOCK_SIZE);
341     }
342
343   if ( ASDCP_SUCCESS(result) )
344     FBout.Size(calc_esv_length(FBin.Size(), FBin.PlaintextOffset()));
345
346   return result;
347 }
348
349 //
350 Result_t
351 ASDCP::DecryptFrameBuffer(const ASDCP::FrameBuffer& FBin, ASDCP::FrameBuffer& FBout, AESDecContext* Ctx)
352 {
353   ASDCP_TEST_NULL(Ctx);
354   assert(FBout.Capacity() >= FBin.SourceLength());
355
356   ui32_t ct_size = FBin.SourceLength() - FBin.PlaintextOffset();
357   ui32_t diff = ct_size % CBC_BLOCK_SIZE;
358   ui32_t block_size = ct_size - diff;
359   assert(block_size);
360   assert((block_size % CBC_BLOCK_SIZE) == 0);
361
362   const byte_t* buf = FBin.RoData();
363
364   // get ivec
365   Ctx->SetIVec(buf);
366   buf += CBC_BLOCK_SIZE;
367
368   // decrypt and test check value
369   byte_t CheckValue[CBC_BLOCK_SIZE];
370   Result_t result = Ctx->DecryptBlock(buf, CheckValue, CBC_BLOCK_SIZE);
371   buf += CBC_BLOCK_SIZE;
372
373   if ( memcmp(CheckValue, ESV_CheckValue, CBC_BLOCK_SIZE) != 0 )
374     return RESULT_CHECKFAIL;
375
376   // copy plaintext region
377   if ( FBin.PlaintextOffset() > 0 )
378     {
379       memcpy(FBout.Data(), buf, FBin.PlaintextOffset());
380       buf += FBin.PlaintextOffset();
381     }
382
383   // decrypt all but last block
384   if ( ASDCP_SUCCESS(result) )
385     {
386       result = Ctx->DecryptBlock(buf, FBout.Data() + FBin.PlaintextOffset(), block_size);
387       buf += block_size;
388     }
389
390   // decrypt last block
391   if ( ASDCP_SUCCESS(result) )
392     {
393       byte_t the_last_block[CBC_BLOCK_SIZE];
394       result = Ctx->DecryptBlock(buf, the_last_block, CBC_BLOCK_SIZE);
395
396       if ( the_last_block[diff] != 0 )
397         {
398           DefaultLogSink().Error("Unexpected non-zero padding value.\n");
399           return RESULT_FORMAT;
400         }
401
402       if ( diff > 0 )
403         memcpy(FBout.Data() + FBin.PlaintextOffset() + block_size, the_last_block, diff);
404     }
405
406   if ( ASDCP_SUCCESS(result) )
407     FBout.Size(FBin.SourceLength());
408
409   return result;
410 }
411
412
413 //
414 Result_t
415 ASDCP::IntegrityPack::CalcValues(const ASDCP::FrameBuffer& FB, byte_t* AssetID,
416                                  ui32_t sequence, HMACContext* HMAC)
417 {
418   ASDCP_TEST_NULL(AssetID);
419   ASDCP_TEST_NULL(HMAC);
420   byte_t* p = Data;
421   HMAC->Reset();
422
423   static byte_t ber_4[MXF_BER_LENGTH] = {0x83, 0};
424
425   // update HMAC with essence data
426   HMAC->Update(FB.RoData(), FB.Size());
427
428   // track file ID length
429   memcpy(p, ber_4, MXF_BER_LENGTH);
430   *(p+3) = UUIDlen;;
431   p += MXF_BER_LENGTH;
432
433   // track file ID
434   memcpy(p, AssetID, UUIDlen);
435   p += UUIDlen;
436
437   // sequence length
438   memcpy(p, ber_4, MXF_BER_LENGTH);
439   *(p+3) = sizeof(ui64_t);
440   p += MXF_BER_LENGTH;
441
442   // sequence number
443   Kumu::i2p<ui64_t>(KM_i64_BE(sequence), p);
444   p += sizeof(ui64_t);
445
446   // HMAC length
447   memcpy(p, ber_4, MXF_BER_LENGTH);
448   *(p+3) = HMAC_SIZE;
449   p += MXF_BER_LENGTH;
450
451   // update HMAC with intpack values
452   HMAC->Update(Data, klv_intpack_size - HMAC_SIZE);
453
454   // finish & write HMAC
455   HMAC->Finalize();
456   HMAC->GetHMACValue(p);
457
458   assert(p + HMAC_SIZE == Data + klv_intpack_size);
459
460   return RESULT_OK;
461 }
462
463
464 Result_t
465 ASDCP::IntegrityPack::TestValues(const ASDCP::FrameBuffer& FB, byte_t* AssetID,
466                                  ui32_t sequence, HMACContext* HMAC)
467 {
468   ASDCP_TEST_NULL(AssetID);
469   ASDCP_TEST_NULL(HMAC);
470
471   // find the start of the intpack
472   byte_t* p = (byte_t*)FB.RoData() + ( FB.Size() - klv_intpack_size );
473
474   // test the AssetID length
475   if ( ! Kumu::read_test_BER(&p, UUIDlen) )
476         return RESULT_HMACFAIL;
477
478   // test the AssetID
479   if ( memcmp(p, AssetID, UUIDlen) != 0 )
480     {
481       DefaultLogSink().Error("IntegrityPack failure: AssetID mismatch.\n");
482       return RESULT_HMACFAIL;
483     }
484   p += UUIDlen;
485   
486   // test the sequence length
487   if ( ! Kumu::read_test_BER(&p, sizeof(ui64_t)) )
488         return RESULT_HMACFAIL;
489
490   ui32_t test_sequence = (ui32_t)KM_i64_BE(Kumu::cp2i<ui64_t>(p));
491
492   // test the sequence value
493   if ( test_sequence != sequence )
494     {
495       DefaultLogSink().Error("IntegrityPack failure: sequence is %u, expecting %u.\n", test_sequence, sequence);
496       return RESULT_HMACFAIL;
497     }
498
499   p += sizeof(ui64_t);
500
501   // test the HMAC length
502   if ( ! Kumu::read_test_BER(&p, HMAC_SIZE) )
503         return RESULT_HMACFAIL;
504
505   // test the HMAC
506   HMAC->Reset();
507   HMAC->Update(FB.RoData(), FB.Size() - HMAC_SIZE);
508   HMAC->Finalize();
509
510   return HMAC->TestHMACValue(p);
511 }
512
513 //
514 // end AS_DCP_MXF.cpp
515 //