2 Copyright (c) 2004-2007, John Hurst
5 Redistribution and use in source and binary forms, with or without
6 modification, are permitted provided that the following conditions
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.
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.
27 /*! \file AS_DCP_MXF.cpp
29 \brief AS-DCP library, misc classes and subroutines
32 #include <KM_fileio.h>
34 #include "AS_DCP_internal.h"
40 //------------------------------------------------------------------------------------------
46 ASDCP::WriterInfoDump(const WriterInfo& Info, FILE* stream)
53 fprintf(stream," ProductUUID: %s\n", UUID(Info.ProductUUID).EncodeHex(str_buf, 40));
58 EncryptedEssence: %s\n",
59 Info.ProductVersion.c_str(),
60 Info.CompanyName.c_str(),
61 Info.ProductName.c_str(),
62 ( Info.EncryptedEssence ? "Yes" : "No" )
65 if ( Info.EncryptedEssence )
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));
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" :
80 ASDCP::MD_to_WriterInfo(Identification* InfoObj, WriterInfo& Info)
82 ASDCP_TEST_NULL(InfoObj);
83 char tmp_str[IdentBufferLen];
85 Info.ProductName = "Unknown Product";
86 Info.ProductVersion = "Unknown Version";
87 Info.CompanyName = "Unknown Company";
88 memset(Info.ProductUUID, 0, UUIDlen);
90 InfoObj->ProductName.EncodeString(tmp_str, IdentBufferLen);
91 if ( *tmp_str ) Info.ProductName = tmp_str;
93 InfoObj->VersionString.EncodeString(tmp_str, IdentBufferLen);
94 if ( *tmp_str ) Info.ProductVersion = tmp_str;
96 InfoObj->CompanyName.EncodeString(tmp_str, IdentBufferLen);
97 if ( *tmp_str ) Info.CompanyName = tmp_str;
99 memcpy(Info.ProductUUID, InfoObj->ProductUID.Value(), UUIDlen);
107 ASDCP::MD_to_CryptoInfo(CryptographicContext* InfoObj, WriterInfo& Info)
109 ASDCP_TEST_NULL(InfoObj);
111 Info.EncryptedEssence = true;
112 memcpy(Info.ContextID, InfoObj->ContextID.Value(), UUIDlen);
113 memcpy(Info.CryptographicKeyID, InfoObj->CryptographicKeyID.Value(), UUIDlen);
115 UL MIC_SHA1(Dict::ul(MDD_MICAlgorithm_HMAC_SHA1));
116 UL MIC_NONE(Dict::ul(MDD_MICAlgorithm_NONE));
118 if ( InfoObj->MICAlgorithm == MIC_SHA1 )
119 Info.UsesHMAC = true;
121 else if ( InfoObj->MICAlgorithm == MIC_NONE )
122 Info.UsesHMAC = false;
126 DefaultLogSink().Error("Unexpected MICAlgorithm UL.\n");
127 return RESULT_FORMAT;
136 ASDCP::EssenceType(const char* filename, EssenceType_t& type)
138 ASDCP_TEST_NULL_STR(filename);
139 Kumu::FileReader Reader;
140 OPAtomHeader TestHeader;
142 Result_t result = Reader.OpenRead(filename);
144 if ( ASDCP_SUCCESS(result) )
145 result = TestHeader.InitFromFile(Reader); // test UL and OP
147 if ( ASDCP_SUCCESS(result) )
150 if ( ASDCP_SUCCESS(TestHeader.GetMDObjectByType(OBJ_TYPE_ARGS(RGBAEssenceDescriptor))) )
151 type = ESS_JPEG_2000;
154 if ( ASDCP_SUCCESS(TestHeader.GetMDObjectByType(OBJ_TYPE_ARGS(WaveAudioDescriptor))) )
155 type = ESS_PCM_24b_48k;
158 if ( ASDCP_SUCCESS(TestHeader.GetMDObjectByType(OBJ_TYPE_ARGS(MPEG2VideoDescriptor))) )
159 type = ESS_MPEG2_VES;
162 if ( ASDCP_SUCCESS(TestHeader.GetMDObjectByType(OBJ_TYPE_ARGS(DCTimedTextDescriptor))) )
163 type = ESS_TIMED_TEXT;
174 ASDCP::RawEssenceType(const char* filename, EssenceType_t& type)
176 ASDCP_TEST_NULL_STR(filename);
178 ASDCP::FrameBuffer FB;
179 Kumu::FileReader Reader;
180 ASDCP::Wav::SimpleWaveHeader WavHeader;
181 ASDCP::AIFF::SimpleAIFFHeader AIFFHeader;
182 Kumu::XMLElement TmpElement("Tmp");
186 Result_t result = FB.Capacity(Wav::MaxWavHeader); // using Wav max because everything else is much smaller
188 if ( Kumu::PathIsFile(filename) )
190 result = Reader.OpenRead(filename);
192 if ( ASDCP_SUCCESS(result) )
194 result = Reader.Read(FB.Data(), FB.Capacity(), &read_count);
198 if ( ASDCP_SUCCESS(result) )
200 const byte_t* p = FB.RoData();
204 while ( p[i] == 0 ) i++;
206 if ( i > 1 && p[i] == 1 && (p[i+1] == ASDCP::MPEG2::SEQ_START || p[i+1] == ASDCP::MPEG2::PIC_START) )
207 type = ESS_MPEG2_VES;
209 else if ( Kumu::StringIsXML((const char*)p, FB.Size()) )
210 type = ESS_TIMED_TEXT;
212 else if ( ASDCP_SUCCESS(WavHeader.ReadFromBuffer(p, read_count, &data_offset)) )
213 type = ESS_PCM_24b_48k;
215 else if ( ASDCP_SUCCESS(AIFFHeader.ReadFromBuffer(p, read_count, &data_offset)) )
216 type = ESS_PCM_24b_48k;
219 else if ( Kumu::PathIsDirectory(filename) )
221 char next_file[Kumu::MaxFilePath];
222 Kumu::DirScanner Scanner;
223 Result_t result = Scanner.Open(filename);
225 if ( ASDCP_SUCCESS(result) )
227 while ( ASDCP_SUCCESS(Scanner.GetNext(next_file)) )
229 if ( next_file[0] == '.' ) // no hidden files or internal links
232 std::string Str(filename);
235 result = Reader.OpenRead(Str.c_str());
237 if ( ASDCP_SUCCESS(result) )
239 result = Reader.Read(FB.Data(), FB.Capacity(), &read_count);
243 if ( ASDCP_SUCCESS(result) )
245 if ( memcmp(FB.RoData(), ASDCP::JP2K::Magic, sizeof(ASDCP::JP2K::Magic)) == 0 )
246 type = ESS_JPEG_2000;
248 else if ( ASDCP_SUCCESS(WavHeader.ReadFromBuffer(FB.RoData(), read_count, &data_offset)) )
249 type = ESS_PCM_24b_48k;
262 ASDCP::EncryptFrameBuffer(const ASDCP::FrameBuffer& FBin, ASDCP::FrameBuffer& FBout, AESEncContext* Ctx)
264 ASDCP_TEST_NULL(Ctx);
268 Result_t result = FBout.Capacity(calc_esv_length(FBin.Size(), FBin.PlaintextOffset()));
271 byte_t* p = FBout.Data();
273 // write the IV to the frame buffer
278 // encrypt the check value to the frame buffer
279 if ( ASDCP_SUCCESS(result) )
281 result = Ctx->EncryptBlock(ESV_CheckValue, p, CBC_BLOCK_SIZE);
285 // write optional plaintext region
286 if ( FBin.PlaintextOffset() > 0 )
288 assert(FBin.PlaintextOffset() <= FBin.Size());
289 memcpy(p, FBin.RoData(), FBin.PlaintextOffset());
290 p += FBin.PlaintextOffset();
293 ui32_t ct_size = FBin.Size() - FBin.PlaintextOffset();
294 ui32_t diff = ct_size % CBC_BLOCK_SIZE;
295 ui32_t block_size = ct_size - diff;
296 assert((block_size % CBC_BLOCK_SIZE) == 0);
298 // encrypt the ciphertext region essence data
299 if ( ASDCP_SUCCESS(result) )
301 result = Ctx->EncryptBlock(FBin.RoData() + FBin.PlaintextOffset(), p, block_size);
305 // construct and encrypt the padding
306 if ( ASDCP_SUCCESS(result) )
308 byte_t the_last_block[CBC_BLOCK_SIZE];
311 memcpy(the_last_block, FBin.RoData() + FBin.PlaintextOffset() + block_size, diff);
313 for (ui32_t i = 0; diff < CBC_BLOCK_SIZE; diff++, i++ )
314 the_last_block[diff] = i;
316 result = Ctx->EncryptBlock(the_last_block, p, CBC_BLOCK_SIZE);
319 if ( ASDCP_SUCCESS(result) )
320 FBout.Size(calc_esv_length(FBin.Size(), FBin.PlaintextOffset()));
327 ASDCP::DecryptFrameBuffer(const ASDCP::FrameBuffer& FBin, ASDCP::FrameBuffer& FBout, AESDecContext* Ctx)
329 ASDCP_TEST_NULL(Ctx);
330 assert(FBout.Capacity() >= FBin.SourceLength());
332 ui32_t ct_size = FBin.SourceLength() - FBin.PlaintextOffset();
333 ui32_t diff = ct_size % CBC_BLOCK_SIZE;
334 ui32_t block_size = ct_size - diff;
336 assert((block_size % CBC_BLOCK_SIZE) == 0);
338 const byte_t* buf = FBin.RoData();
342 buf += CBC_BLOCK_SIZE;
344 // decrypt and test check value
345 byte_t CheckValue[CBC_BLOCK_SIZE];
346 Result_t result = Ctx->DecryptBlock(buf, CheckValue, CBC_BLOCK_SIZE);
347 buf += CBC_BLOCK_SIZE;
349 if ( memcmp(CheckValue, ESV_CheckValue, CBC_BLOCK_SIZE) != 0 )
350 return RESULT_CHECKFAIL;
352 // copy plaintext region
353 if ( FBin.PlaintextOffset() > 0 )
355 memcpy(FBout.Data(), buf, FBin.PlaintextOffset());
356 buf += FBin.PlaintextOffset();
359 // decrypt all but last block
360 if ( ASDCP_SUCCESS(result) )
362 result = Ctx->DecryptBlock(buf, FBout.Data() + FBin.PlaintextOffset(), block_size);
366 // decrypt last block
367 if ( ASDCP_SUCCESS(result) )
369 byte_t the_last_block[CBC_BLOCK_SIZE];
370 result = Ctx->DecryptBlock(buf, the_last_block, CBC_BLOCK_SIZE);
372 if ( the_last_block[diff] != 0 )
374 DefaultLogSink().Error("Unexpected non-zero padding value.\n");
375 return RESULT_FORMAT;
379 memcpy(FBout.Data() + FBin.PlaintextOffset() + block_size, the_last_block, diff);
382 if ( ASDCP_SUCCESS(result) )
383 FBout.Size(FBin.SourceLength());
391 ASDCP::IntegrityPack::CalcValues(const ASDCP::FrameBuffer& FB, byte_t* AssetID,
392 ui32_t sequence, HMACContext* HMAC)
394 ASDCP_TEST_NULL(AssetID);
395 ASDCP_TEST_NULL(HMAC);
399 static byte_t ber_4[MXF_BER_LENGTH] = {0x83, 0};
401 // update HMAC with essence data
402 HMAC->Update(FB.RoData(), FB.Size());
404 // track file ID length
405 memcpy(p, ber_4, MXF_BER_LENGTH);
410 memcpy(p, AssetID, UUIDlen);
414 memcpy(p, ber_4, MXF_BER_LENGTH);
415 *(p+3) = sizeof(ui64_t);
419 Kumu::i2p<ui64_t>(KM_i64_BE(sequence), p);
423 memcpy(p, ber_4, MXF_BER_LENGTH);
427 // update HMAC with intpack values
428 HMAC->Update(Data, klv_intpack_size - HMAC_SIZE);
430 // finish & write HMAC
432 HMAC->GetHMACValue(p);
434 assert(p + HMAC_SIZE == Data + klv_intpack_size);
441 ASDCP::IntegrityPack::TestValues(const ASDCP::FrameBuffer& FB, byte_t* AssetID,
442 ui32_t sequence, HMACContext* HMAC)
444 ASDCP_TEST_NULL(AssetID);
445 ASDCP_TEST_NULL(HMAC);
447 // find the start of the intpack
448 byte_t* p = (byte_t*)FB.RoData() + ( FB.Size() - klv_intpack_size );
450 // test the AssetID length
451 if ( ! Kumu::read_test_BER(&p, UUIDlen) )
452 return RESULT_HMACFAIL;
455 if ( memcmp(p, AssetID, UUIDlen) != 0 )
457 DefaultLogSink().Error("IntegrityPack failure: AssetID mismatch.\n");
458 return RESULT_HMACFAIL;
462 // test the sequence length
463 if ( ! Kumu::read_test_BER(&p, sizeof(ui64_t)) )
464 return RESULT_HMACFAIL;
466 ui32_t test_sequence = (ui32_t)KM_i64_BE(Kumu::cp2i<ui64_t>(p));
468 // test the sequence value
469 if ( test_sequence != sequence )
471 DefaultLogSink().Error("IntegrityPack failure: sequence is %u, expecting %u.\n", test_sequence, sequence);
472 return RESULT_HMACFAIL;
477 // test the HMAC length
478 if ( ! Kumu::read_test_BER(&p, HMAC_SIZE) )
479 return RESULT_HMACFAIL;
483 HMAC->Update(FB.RoData(), FB.Size() - HMAC_SIZE);
486 return HMAC->TestHMACValue(p);
490 // end AS_DCP_MXF.cpp