pre-release commit
[asdcplib.git] / src / AS_DCP_MXF.cpp
1 /*
2 Copyright (c) 2004-2005, 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 "AS_DCP_internal.h"
33 #include "FileIO.h"
34 #include "DirScanner.h"
35 #include "JP2K.h"
36 #include "Wav.h"
37 #include "MDD.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", bin2hex(Info.ProductUUID, 16, 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", bin2hex(Info.ContextID, 16, str_buf, 40));
69       fprintf(stream, "CryptographicKeyID: %s\n", bin2hex(Info.CryptographicKeyID, 16, str_buf, 40));
70     }
71
72   fprintf(stream,"         AssetUUID: %s\n", bin2hex(Info.AssetUUID, 16, str_buf, 40));
73 }
74
75 //
76 Result_t
77 ASDCP::MD_to_WriterInfo(Identification* InfoObj, WriterInfo& Info)
78 {
79   ASDCP_TEST_NULL(InfoObj);
80   char tmp_str[IdentBufferLen];
81
82   Info.ProductName = "Unknown Product";
83   Info.ProductVersion = "Unknown Version";
84   Info.CompanyName = "Unknown Company";
85   memset(Info.ProductUUID, 0, UUIDlen);
86
87   InfoObj->ProductName.ToString(tmp_str);
88   if ( *tmp_str ) Info.ProductName = tmp_str;
89
90   InfoObj->VersionString.ToString(tmp_str);
91   if ( *tmp_str ) Info.ProductVersion = tmp_str;
92
93   InfoObj->CompanyName.ToString(tmp_str);
94   if ( *tmp_str ) Info.CompanyName = tmp_str;
95
96   memcpy(Info.ProductUUID, InfoObj->ProductUID.Data(), UUIDlen);
97
98   return RESULT_OK;
99 }
100
101
102 //
103 Result_t
104 ASDCP::MD_to_CryptoInfo(CryptographicContext* InfoObj, WriterInfo& Info)
105 {
106   ASDCP_TEST_NULL(InfoObj);
107
108   Info.EncryptedEssence = true;
109   memcpy(Info.ContextID, InfoObj->ContextID.Data(), UUIDlen);
110   memcpy(Info.CryptographicKeyID, InfoObj->CryptographicKeyID.Data(), UUIDlen);
111
112   UL MIC_SHA1(MICAlgorithm_HMAC_SHA1);
113   UL MIC_NONE(MICAlgorithm_NONE);
114
115   if ( InfoObj->MICAlgorithm == MIC_SHA1 )
116     Info.UsesHMAC = true;
117
118   else if ( InfoObj->MICAlgorithm == MIC_NONE )
119     Info.UsesHMAC = false;
120
121   else
122     {
123       DefaultLogSink().Error("Unexpected MICAlgorithm UL.\n");
124       return RESULT_FORMAT;
125     }
126
127   return RESULT_OK;
128 }
129
130 #if 0
131
132
133 //
134 // add DMS CryptographicFramework entry to source package
135 void
136 ASDCP::AddDMScrypt(PackagePtr SourcePackage, WriterInfo& Descr, const byte_t* SourceEssenceContainerLabel)
137 {
138   assert(SourceEssenceContainerLabel);
139
140   TrackPtr MPDMTrack = SourcePackage->AddDMTrack(); // zero parameters = static
141   DMSegmentPtr MPDMSegment = MPDMTrack->AddDMSegment();
142
143   MDObject* Crypto_DMS_Ptr = new MDObject("CryptographicFramework");
144   MPDMSegment->AddChild("DMFramework")->MakeLink(*Crypto_DMS_Ptr);
145
146   MDObject* Crypto_DMS_BasicPtr = new MDObject("CryptographicContext");         
147   Crypto_DMS_Ptr->AddChild("ContextSR")->MakeLink(*Crypto_DMS_BasicPtr);
148
149   UUID ContextID(Descr.ContextID);
150   Crypto_DMS_BasicPtr->SetValue("ContextID", DataChunk(UUIDlen, ContextID.GetValue())); // UUID
151   Crypto_DMS_BasicPtr->SetValue("SourceEssenceContainer",
152                                 DataChunk(klv_key_size, SourceEssenceContainerLabel)); // Label
153   Crypto_DMS_BasicPtr->SetValue("CipherAlgorithm", DataChunk(klv_key_size, CipherAlgorithm_AES)); // UL Key
154
155   Crypto_DMS_BasicPtr->SetValue("MICAlgorithm",
156                                 DataChunk(KeyLen,
157                                           (Descr.UsesHMAC ?
158                                            MICAlgorithm_HMAC_SHA1
159                                            : MICAlgorithm_NONE))); // UL Key
160
161   UUID CryptographicKeyID(Descr.CryptographicKeyID);
162
163   Crypto_DMS_BasicPtr->SetValue("CryptographicKeyID", DataChunk(UUIDlen, CryptographicKeyID.GetValue())); // UUID
164 }
165
166
167 //
168 //
169 ASDCP::Result_t
170 ASDCP::FindObject(const char* filename, const char* objname, FILE* stream)
171 {
172   ASDCP_TEST_NULL_STR(filename);
173   ASDCP_TEST_NULL_STR(objname);
174
175   if ( stream == 0 )
176     stream = stderr;
177
178   ASDCP::h__Reader Reader;
179   Result_t result = Reader.OpenMXFRead(filename);
180
181   if ( ASDCP_FAILURE(result) )
182     return result;
183
184   MDObject* DescObj = Reader.GetMDObjectByType(objname);
185
186   if ( DescObj )
187     {
188       DumpMDObject(*DescObj, " ", stream);
189       return RESULT_OK;
190     }
191
192   return RESULT_FAIL;
193 }
194 #endif
195
196 //
197 //
198 ASDCP::Result_t
199 ASDCP::EssenceType(const char* filename, EssenceType_t& type)
200 {
201   ASDCP_TEST_NULL_STR(filename);
202   FileReader   Reader;
203   OPAtomHeader TestHeader;
204
205   Result_t result = Reader.OpenRead(filename);
206
207   if ( ASDCP_SUCCESS(result) )
208     result = TestHeader.InitFromFile(Reader); // test UL and OP
209
210   if ( ASDCP_SUCCESS(result) )
211     {
212       type = ESS_UNKNOWN;
213       if ( ASDCP_SUCCESS(TestHeader.GetMDObjectByType(OBJ_TYPE_ARGS(JPEG2000PictureSubDescriptor))) )
214         type = ESS_JPEG_2000;
215       else
216         {
217           if ( ASDCP_SUCCESS(TestHeader.GetMDObjectByType(OBJ_TYPE_ARGS(WaveAudioDescriptor))) )
218             type = ESS_PCM_24b_48k;
219           else
220             {
221               if ( ASDCP_SUCCESS(TestHeader.GetMDObjectByType(OBJ_TYPE_ARGS(MPEG2VideoDescriptor))) )
222                 type = ESS_MPEG2_VES;
223             }
224         }
225     }
226
227   return result;
228 }
229
230
231 //
232 ASDCP::Result_t
233 ASDCP::RawEssenceType(const char* filename, EssenceType_t& type)
234 {
235   ASDCP_TEST_NULL_STR(filename);
236   type = ESS_UNKNOWN;
237   ASDCP::FrameBuffer FB;
238   FileReader Reader;
239   ui32_t read_count;
240   Result_t result = FB.Capacity(Wav::MaxWavHeader); // using Wav max because everything else is much smaller
241
242   if ( ASDCP::PathIsFile(filename) )
243     {
244       result = Reader.OpenRead(filename);
245
246       if ( ASDCP_SUCCESS(result) )
247         {
248           result = Reader.Read(FB.Data(), FB.Capacity(), &read_count);
249           Reader.Close();
250         }
251
252       if ( ASDCP_SUCCESS(result) )
253         {
254           ASDCP::Wav::SimpleWaveHeader WavHeader;
255           ui32_t data_offset;
256           const byte_t* p = FB.RoData();
257
258           if ( p[0] == 0 &&  p[1] == 0 &&  p[2] == 1 &&  (p[3] == 0xb3 || p[3] == 0) )
259             type = ESS_MPEG2_VES;
260
261           else if ( ASDCP_SUCCESS(WavHeader.ReadFromBuffer(p, read_count, &data_offset)) )
262             type = ESS_PCM_24b_48k;
263         }
264     }
265   else if ( ASDCP::PathIsDirectory(filename) )
266     {
267       char next_file[ASDCP_MAX_PATH];
268       DirScanner Scanner;
269       Result_t result = Scanner.Open(filename);
270
271       if ( ASDCP_SUCCESS(result) )
272         {
273           while ( ASDCP_SUCCESS(Scanner.GetNext(next_file)) )
274             {
275               if ( next_file[0] == '.' ) // no hidden files or internal links
276                 continue;
277
278               std::string Str(filename);
279               Str += "/";
280               Str += next_file;
281               result = Reader.OpenRead(Str.c_str());
282
283               if ( ASDCP_SUCCESS(result) )
284                 {
285                   result = Reader.Read(FB.Data(), FB.Capacity(), &read_count);
286                   Reader.Close();
287                 }
288
289               if ( ASDCP_SUCCESS(result)
290                    && ( memcmp(FB.RoData(), ASDCP::JP2K::Magic, sizeof(ASDCP::JP2K::Magic)) == 0 ) )
291                 type = ESS_JPEG_2000;
292
293               break;
294             }
295         }
296     }
297
298   return result;
299 }
300
301 //
302 Result_t
303 ASDCP::EncryptFrameBuffer(const ASDCP::FrameBuffer& FBin, ASDCP::FrameBuffer& FBout, AESEncContext* Ctx)
304 {
305   ASDCP_TEST_NULL(Ctx);
306   FBout.Size(0);
307
308   // size the buffer
309   Result_t result = FBout.Capacity(calc_esv_length(FBin.Size(), FBin.PlaintextOffset()));
310
311   // write the IV
312   byte_t* p = FBout.Data();
313
314   // write the IV to the frame buffer
315   Ctx->GetIVec(p);
316   p += CBC_BLOCK_SIZE;
317
318
319   // encrypt the check value to the frame buffer
320   if ( ASDCP_SUCCESS(result) )
321     {
322       result = Ctx->EncryptBlock(ESV_CheckValue, p, CBC_BLOCK_SIZE);
323       p += CBC_BLOCK_SIZE;
324     }
325
326   // write optional plaintext region
327   if ( FBin.PlaintextOffset() > 0 )
328     {
329       assert(FBin.PlaintextOffset() <= FBin.Size());
330       memcpy(p, FBin.RoData(), FBin.PlaintextOffset());
331       p += FBin.PlaintextOffset();
332     }
333
334   ui32_t ct_size = FBin.Size() - FBin.PlaintextOffset();
335   ui32_t diff = ct_size % CBC_BLOCK_SIZE;
336   ui32_t block_size = ct_size - diff;
337   assert((block_size % CBC_BLOCK_SIZE) == 0);
338
339   // encrypt the ciphertext region essence data
340   if ( ASDCP_SUCCESS(result) )
341     {
342       result = Ctx->EncryptBlock(FBin.RoData() + FBin.PlaintextOffset(), p, block_size);
343       p += block_size;
344     }
345
346   // construct and encrypt the padding
347   if ( ASDCP_SUCCESS(result) )
348     {
349       byte_t the_last_block[CBC_BLOCK_SIZE];
350
351       if ( diff > 0 )
352         memcpy(the_last_block, FBin.RoData() + FBin.PlaintextOffset() + block_size, diff);
353
354       for (ui32_t i = 0; diff < CBC_BLOCK_SIZE; diff++, i++ )
355         the_last_block[diff] = i;
356
357       result = Ctx->EncryptBlock(the_last_block, p, CBC_BLOCK_SIZE);
358     }
359
360   if ( ASDCP_SUCCESS(result) )
361     FBout.Size(calc_esv_length(FBin.Size(), FBin.PlaintextOffset()));
362
363   return result;
364 }
365
366 //
367 Result_t
368 ASDCP::DecryptFrameBuffer(const ASDCP::FrameBuffer& FBin, ASDCP::FrameBuffer& FBout, AESDecContext* Ctx)
369 {
370   ASDCP_TEST_NULL(Ctx);
371   assert(FBout.Capacity() >= FBin.SourceLength());
372
373   ui32_t ct_size = FBin.SourceLength() - FBin.PlaintextOffset();
374   ui32_t diff = ct_size % CBC_BLOCK_SIZE;
375   ui32_t block_size = ct_size - diff;
376   assert(block_size);
377   assert((block_size % CBC_BLOCK_SIZE) == 0);
378
379   const byte_t* buf = FBin.RoData();
380
381   // get ivec
382   Ctx->SetIVec(buf);
383   buf += CBC_BLOCK_SIZE;
384
385   // decrypt and test check value
386   byte_t CheckValue[CBC_BLOCK_SIZE];
387   Result_t result = Ctx->DecryptBlock(buf, CheckValue, CBC_BLOCK_SIZE);
388   buf += CBC_BLOCK_SIZE;
389
390   if ( memcmp(CheckValue, ESV_CheckValue, CBC_BLOCK_SIZE) != 0 )
391     return RESULT_CHECKFAIL;
392
393   // copy plaintext region
394   if ( FBin.PlaintextOffset() > 0 )
395     {
396       memcpy(FBout.Data(), buf, FBin.PlaintextOffset());
397       buf += FBin.PlaintextOffset();
398     }
399
400   // decrypt all but last block
401   if ( ASDCP_SUCCESS(result) )
402     {
403       result = Ctx->DecryptBlock(buf, FBout.Data() + FBin.PlaintextOffset(), block_size);
404       buf += block_size;
405     }
406
407   // decrypt last block
408   if ( ASDCP_SUCCESS(result) )
409     {
410       byte_t the_last_block[CBC_BLOCK_SIZE];
411       result = Ctx->DecryptBlock(buf, the_last_block, CBC_BLOCK_SIZE);
412
413       if ( the_last_block[diff] != 0 )
414         {
415           DefaultLogSink().Error("Unexpected non-zero padding value.\n");
416           return RESULT_FORMAT;
417         }
418
419       if ( diff > 0 )
420         memcpy(FBout.Data() + FBin.PlaintextOffset() + block_size, the_last_block, diff);
421     }
422
423   if ( ASDCP_SUCCESS(result) )
424     FBout.Size(FBin.SourceLength());
425
426   return result;
427 }
428
429
430 //
431 Result_t
432 ASDCP::IntegrityPack::CalcValues(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   byte_t* p = Data;
438   HMAC->Reset();
439
440   static byte_t ber_4[klv_length_size] = {0x83, 0};
441
442   // update HMAC with essence data
443   HMAC->Update(FB.RoData(), FB.Size());
444
445   // track file ID length
446   memcpy(p, ber_4, klv_length_size);
447   *(p+3) = UUIDlen;;
448   p += klv_length_size;
449
450   // track file ID
451   memcpy(p, AssetID, UUIDlen);
452   p += UUIDlen;
453
454   // sequence length
455   memcpy(p, ber_4, klv_length_size);
456   *(p+3) = sizeof(ui64_t);
457   p += klv_length_size;
458
459   // sequence number
460   i2p<ui64_t>(ASDCP_i64_BE(sequence), p);
461   p += sizeof(ui64_t);
462
463   // HMAC length
464   memcpy(p, ber_4, klv_length_size);
465   *(p+3) = HMAC_SIZE;
466   p += klv_length_size;
467
468   // update HMAC with intpack values
469   HMAC->Update(Data, klv_intpack_size - HMAC_SIZE);
470
471   // finish & write HMAC
472   HMAC->Finalize();
473   HMAC->GetHMACValue(p);
474
475   assert(p + HMAC_SIZE == Data + klv_intpack_size);
476
477   return RESULT_OK;
478 }
479
480
481 Result_t
482 ASDCP::IntegrityPack::TestValues(const ASDCP::FrameBuffer& FB, byte_t* AssetID,
483                                  ui32_t sequence, HMACContext* HMAC)
484 {
485   ASDCP_TEST_NULL(AssetID);
486   ASDCP_TEST_NULL(HMAC);
487
488   // find the start of the intpack
489   byte_t* p = (byte_t*)FB.RoData() + ( FB.Size() - klv_intpack_size );
490
491   // test the AssetID length
492   if ( ! read_test_BER(&p, UUIDlen) )
493         return RESULT_HMACFAIL;
494
495   // test the AssetID
496   if ( memcmp(p, AssetID, UUIDlen) != 0 )
497     {
498       DefaultLogSink().Error("IntegrityPack failure: AssetID mismatch.\n");
499       return RESULT_HMACFAIL;
500     }
501   p += UUIDlen;
502   
503   // test the sequence length
504   if ( ! read_test_BER(&p, sizeof(ui64_t)) )
505         return RESULT_HMACFAIL;
506
507   ui32_t test_sequence = (ui32_t)ASDCP_i64_BE(cp2i<ui64_t>(p));
508
509   // test the sequence value
510   if ( test_sequence != sequence )
511     {
512       DefaultLogSink().Error("IntegrityPack failure: sequence is %lu, expecting %lu.\n", test_sequence, sequence);
513       return RESULT_HMACFAIL;
514     }
515
516   p += sizeof(ui64_t);
517
518   // test the HMAC length
519   if ( ! read_test_BER(&p, HMAC_SIZE) )
520         return RESULT_HMACFAIL;
521
522   // test the HMAC
523   HMAC->Reset();
524   HMAC->Update(FB.RoData(), FB.Size() - HMAC_SIZE);
525   HMAC->Finalize();
526
527   return HMAC->TestHMACValue(p);
528 }
529
530 //------------------------------------------------------------------------------------------
531 //
532
533
534 //
535 ASDCP::Result_t
536 ASDCP::KLVReader::ReadKLFromFile(ASDCP::FileReader& Reader)
537 {
538   ui32_t read_count;
539   m_HeaderLength = klv_key_size + klv_length_size;
540   Result_t result = Reader.Read(m_Key, m_HeaderLength, &read_count);
541   assert(read_count == m_HeaderLength);
542
543   if ( ASDCP_SUCCESS(result) )
544     {
545       m_BERLength = BER_length(m_Key + klv_key_size);
546       
547       if ( m_BERLength != klv_length_size )
548         {
549           ASDCP::DefaultLogSink().Error("Found packet with BER length %lu; being less efficient...\n",
550                                         m_BERLength);
551           // TODO: recover the correct BER value
552           // and reposition the file pointer
553           assert(0);
554         }
555
556       if ( ! read_BER(m_Key + klv_key_size, &m_Length) )
557         return RESULT_FAIL;
558     }
559   
560   return result;
561 }
562
563 //
564 // end AS_DCP_MXF.cpp
565 //