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