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