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