2 Copyright (c) 2004-2009, 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
28 \version $Id: AS_DCP_MXF.cpp,v 1.31 2009/08/04 18:43:10 jhurst Exp $
29 \brief AS-DCP library, misc classes and subroutines
32 #include <KM_fileio.h>
34 #include "AS_DCP_internal.h"
42 //------------------------------------------------------------------------------------------
48 ASDCP::operator << (std::ostream& strm, const WriterInfo& Info)
52 strm << " ProductUUID: " << UUID(Info.ProductUUID).EncodeHex(str_buf, 40) << std::endl;
53 strm << " ProductVersion: " << Info.ProductVersion << std::endl;
54 strm << " CompanyName: " << Info.CompanyName << std::endl;
55 strm << " ProductName: " << Info.ProductName << std::endl;
56 strm << " EncryptedEssence: " << (Info.EncryptedEssence ? "Yes" : "No") << std::endl;
58 if ( Info.EncryptedEssence )
60 strm << " HMAC: " << (Info.UsesHMAC ? "Yes" : "No") << std::endl;
61 strm << " ContextID: " << UUID(Info.ContextID).EncodeHex(str_buf, 40) << std::endl;
62 strm << "CryptographicKeyID: " << UUID(Info.CryptographicKeyID).EncodeHex(str_buf, 40) << std::endl;
65 strm << " AssetUUID: " << UUID(Info.AssetUUID).EncodeHex(str_buf, 40) << std::endl;
66 strm << " Label Set Type: " << (Info.LabelSetType == LS_MXF_SMPTE ? "SMPTE" :
67 (Info.LabelSetType == LS_MXF_INTEROP ? "MXF Interop" :
68 "Unknown")) << std::endl;
74 ASDCP::WriterInfoDump(const WriterInfo& Info, FILE* stream)
81 fprintf(stream," ProductUUID: %s\n", UUID(Info.ProductUUID).EncodeHex(str_buf, 40));
86 EncryptedEssence: %s\n",
87 Info.ProductVersion.c_str(),
88 Info.CompanyName.c_str(),
89 Info.ProductName.c_str(),
90 ( Info.EncryptedEssence ? "Yes" : "No" )
93 if ( Info.EncryptedEssence )
95 fprintf(stream, " HMAC: %s\n", ( Info.UsesHMAC ? "Yes" : "No"));
96 fprintf(stream, " ContextID: %s\n", UUID(Info.ContextID).EncodeHex(str_buf, 40));
97 fprintf(stream, "CryptographicKeyID: %s\n", UUID(Info.CryptographicKeyID).EncodeHex(str_buf, 40));
100 fprintf(stream," AssetUUID: %s\n", UUID(Info.AssetUUID).EncodeHex(str_buf, 40));
101 fprintf(stream," Label Set Type: %s\n", ( Info.LabelSetType == LS_MXF_SMPTE ? "SMPTE" :
102 ( Info.LabelSetType == LS_MXF_INTEROP ? "MXF Interop" :
108 ASDCP::MD_to_WriterInfo(Identification* InfoObj, WriterInfo& Info)
110 ASDCP_TEST_NULL(InfoObj);
111 char tmp_str[IdentBufferLen];
113 Info.ProductName = "Unknown Product";
114 Info.ProductVersion = "Unknown Version";
115 Info.CompanyName = "Unknown Company";
116 memset(Info.ProductUUID, 0, UUIDlen);
118 InfoObj->ProductName.EncodeString(tmp_str, IdentBufferLen);
119 if ( *tmp_str ) Info.ProductName = tmp_str;
121 InfoObj->VersionString.EncodeString(tmp_str, IdentBufferLen);
122 if ( *tmp_str ) Info.ProductVersion = tmp_str;
124 InfoObj->CompanyName.EncodeString(tmp_str, IdentBufferLen);
125 if ( *tmp_str ) Info.CompanyName = tmp_str;
127 memcpy(Info.ProductUUID, InfoObj->ProductUID.Value(), UUIDlen);
135 ASDCP::MD_to_CryptoInfo(CryptographicContext* InfoObj, WriterInfo& Info, const Dictionary& Dict)
137 ASDCP_TEST_NULL(InfoObj);
139 Info.EncryptedEssence = true;
140 memcpy(Info.ContextID, InfoObj->ContextID.Value(), UUIDlen);
141 memcpy(Info.CryptographicKeyID, InfoObj->CryptographicKeyID.Value(), UUIDlen);
143 UL MIC_SHA1(Dict.ul(MDD_MICAlgorithm_HMAC_SHA1));
144 UL MIC_NONE(Dict.ul(MDD_MICAlgorithm_NONE));
146 if ( InfoObj->MICAlgorithm == MIC_SHA1 )
147 Info.UsesHMAC = true;
149 else if ( InfoObj->MICAlgorithm == MIC_NONE )
150 Info.UsesHMAC = false;
154 DefaultLogSink().Error("Unexpected MICAlgorithm UL.\n");
155 return RESULT_FORMAT;
164 ASDCP::EssenceType(const char* filename, EssenceType_t& type)
166 const Dictionary* m_Dict = &DefaultCompositeDict();
169 ASDCP_TEST_NULL_STR(filename);
170 Kumu::FileReader Reader;
171 OPAtomHeader TestHeader(m_Dict);
173 Result_t result = Reader.OpenRead(filename);
175 if ( ASDCP_SUCCESS(result) )
176 result = TestHeader.InitFromFile(Reader); // test UL and OP
178 if ( ASDCP_SUCCESS(result) )
181 if ( ASDCP_SUCCESS(TestHeader.GetMDObjectByType(OBJ_TYPE_ARGS(RGBAEssenceDescriptor))) )
183 if ( ASDCP_SUCCESS(TestHeader.GetMDObjectByType(OBJ_TYPE_ARGS(StereoscopicPictureSubDescriptor))) )
184 type = ESS_JPEG_2000_S;
186 type = ESS_JPEG_2000;
188 else if ( ASDCP_SUCCESS(TestHeader.GetMDObjectByType(OBJ_TYPE_ARGS(WaveAudioDescriptor))) )
189 type = ESS_PCM_24b_48k;
190 else if ( ASDCP_SUCCESS(TestHeader.GetMDObjectByType(OBJ_TYPE_ARGS(MPEG2VideoDescriptor))) )
191 type = ESS_MPEG2_VES;
192 else if ( ASDCP_SUCCESS(TestHeader.GetMDObjectByType(OBJ_TYPE_ARGS(TimedTextDescriptor))) )
193 type = ESS_TIMED_TEXT;
201 ASDCP::RawEssenceType(const char* filename, EssenceType_t& type)
203 ASDCP_TEST_NULL_STR(filename);
205 ASDCP::FrameBuffer FB;
206 Kumu::FileReader Reader;
207 ASDCP::Wav::SimpleWaveHeader WavHeader;
208 ASDCP::AIFF::SimpleAIFFHeader AIFFHeader;
209 Kumu::XMLElement TmpElement("Tmp");
213 Result_t result = FB.Capacity(Wav::MaxWavHeader); // using Wav max because everything else is much smaller
215 if ( Kumu::PathIsFile(filename) )
217 result = Reader.OpenRead(filename);
219 if ( ASDCP_SUCCESS(result) )
221 result = Reader.Read(FB.Data(), FB.Capacity(), &read_count);
225 if ( ASDCP_SUCCESS(result) )
227 const byte_t* p = FB.RoData();
231 while ( p[i] == 0 ) i++;
233 if ( i > 1 && p[i] == 1 && (p[i+1] == ASDCP::MPEG2::SEQ_START || p[i+1] == ASDCP::MPEG2::PIC_START) )
235 type = ESS_MPEG2_VES;
237 else if ( memcmp(FB.RoData(), ASDCP::JP2K::Magic, sizeof(ASDCP::JP2K::Magic)) == 0 )
239 type = ESS_JPEG_2000;
241 else if ( ASDCP_SUCCESS(WavHeader.ReadFromBuffer(FB.RoData(), read_count, &data_offset)) )
243 switch ( WavHeader.samplespersec )
245 case 48000: type = ESS_PCM_24b_48k; break;
246 case 96000: type = ESS_PCM_24b_96k; break;
248 return RESULT_FORMAT;
251 else if ( ASDCP_SUCCESS(AIFFHeader.ReadFromBuffer(FB.RoData(), read_count, &data_offset)) )
253 type = ESS_PCM_24b_48k;
255 else if ( Kumu::StringIsXML((const char*)FB.RoData(), FB.Size()) )
257 type = ESS_TIMED_TEXT;
261 else if ( Kumu::PathIsDirectory(filename) )
263 char next_file[Kumu::MaxFilePath];
264 Kumu::DirScanner Scanner;
265 Result_t result = Scanner.Open(filename);
267 if ( ASDCP_SUCCESS(result) )
269 while ( ASDCP_SUCCESS(Scanner.GetNext(next_file)) )
271 if ( next_file[0] == '.' ) // no hidden files or internal links
274 std::string Str(filename);
277 result = Reader.OpenRead(Str.c_str());
279 if ( ASDCP_SUCCESS(result) )
281 result = Reader.Read(FB.Data(), FB.Capacity(), &read_count);
285 if ( ASDCP_SUCCESS(result) )
287 if ( memcmp(FB.RoData(), ASDCP::JP2K::Magic, sizeof(ASDCP::JP2K::Magic)) == 0 )
289 type = ESS_JPEG_2000;
291 else if ( ASDCP_SUCCESS(WavHeader.ReadFromBuffer(FB.RoData(), read_count, &data_offset)) )
293 switch ( WavHeader.samplespersec )
295 case 48000: type = ESS_PCM_24b_48k; break;
296 case 96000: type = ESS_PCM_24b_96k; break;
298 return RESULT_FORMAT;
313 ASDCP::EncryptFrameBuffer(const ASDCP::FrameBuffer& FBin, ASDCP::FrameBuffer& FBout, AESEncContext* Ctx)
315 ASDCP_TEST_NULL(Ctx);
319 Result_t result = FBout.Capacity(calc_esv_length(FBin.Size(), FBin.PlaintextOffset()));
322 byte_t* p = FBout.Data();
324 // write the IV to the frame buffer
329 // encrypt the check value to the frame buffer
330 if ( ASDCP_SUCCESS(result) )
332 result = Ctx->EncryptBlock(ESV_CheckValue, p, CBC_BLOCK_SIZE);
336 // write optional plaintext region
337 if ( FBin.PlaintextOffset() > 0 )
339 assert(FBin.PlaintextOffset() <= FBin.Size());
340 memcpy(p, FBin.RoData(), FBin.PlaintextOffset());
341 p += FBin.PlaintextOffset();
344 ui32_t ct_size = FBin.Size() - FBin.PlaintextOffset();
345 ui32_t diff = ct_size % CBC_BLOCK_SIZE;
346 ui32_t block_size = ct_size - diff;
347 assert((block_size % CBC_BLOCK_SIZE) == 0);
349 // encrypt the ciphertext region essence data
350 if ( ASDCP_SUCCESS(result) )
352 result = Ctx->EncryptBlock(FBin.RoData() + FBin.PlaintextOffset(), p, block_size);
356 // construct and encrypt the padding
357 if ( ASDCP_SUCCESS(result) )
359 byte_t the_last_block[CBC_BLOCK_SIZE];
362 memcpy(the_last_block, FBin.RoData() + FBin.PlaintextOffset() + block_size, diff);
364 for (ui32_t i = 0; diff < CBC_BLOCK_SIZE; diff++, i++ )
365 the_last_block[diff] = i;
367 result = Ctx->EncryptBlock(the_last_block, p, CBC_BLOCK_SIZE);
370 if ( ASDCP_SUCCESS(result) )
371 FBout.Size(calc_esv_length(FBin.Size(), FBin.PlaintextOffset()));
378 ASDCP::DecryptFrameBuffer(const ASDCP::FrameBuffer& FBin, ASDCP::FrameBuffer& FBout, AESDecContext* Ctx)
380 ASDCP_TEST_NULL(Ctx);
381 assert(FBout.Capacity() >= FBin.SourceLength());
383 ui32_t ct_size = FBin.SourceLength() - FBin.PlaintextOffset();
384 ui32_t diff = ct_size % CBC_BLOCK_SIZE;
385 ui32_t block_size = ct_size - diff;
387 assert((block_size % CBC_BLOCK_SIZE) == 0);
389 const byte_t* buf = FBin.RoData();
393 buf += CBC_BLOCK_SIZE;
395 // decrypt and test check value
396 byte_t CheckValue[CBC_BLOCK_SIZE];
397 Result_t result = Ctx->DecryptBlock(buf, CheckValue, CBC_BLOCK_SIZE);
398 buf += CBC_BLOCK_SIZE;
400 if ( memcmp(CheckValue, ESV_CheckValue, CBC_BLOCK_SIZE) != 0 )
401 return RESULT_CHECKFAIL;
403 // copy plaintext region
404 if ( FBin.PlaintextOffset() > 0 )
406 memcpy(FBout.Data(), buf, FBin.PlaintextOffset());
407 buf += FBin.PlaintextOffset();
410 // decrypt all but last block
411 if ( ASDCP_SUCCESS(result) )
413 result = Ctx->DecryptBlock(buf, FBout.Data() + FBin.PlaintextOffset(), block_size);
417 // decrypt last block
418 if ( ASDCP_SUCCESS(result) )
420 byte_t the_last_block[CBC_BLOCK_SIZE];
421 result = Ctx->DecryptBlock(buf, the_last_block, CBC_BLOCK_SIZE);
423 if ( the_last_block[diff] != 0 )
425 DefaultLogSink().Error("Unexpected non-zero padding value.\n");
426 return RESULT_FORMAT;
430 memcpy(FBout.Data() + FBin.PlaintextOffset() + block_size, the_last_block, diff);
433 if ( ASDCP_SUCCESS(result) )
434 FBout.Size(FBin.SourceLength());
442 ASDCP::IntegrityPack::CalcValues(const ASDCP::FrameBuffer& FB, byte_t* AssetID,
443 ui32_t sequence, HMACContext* HMAC)
445 ASDCP_TEST_NULL(AssetID);
446 ASDCP_TEST_NULL(HMAC);
450 static byte_t ber_4[MXF_BER_LENGTH] = {0x83, 0, 0, 0};
452 // update HMAC with essence data
453 HMAC->Update(FB.RoData(), FB.Size());
455 // track file ID length
456 memcpy(p, ber_4, MXF_BER_LENGTH);
461 memcpy(p, AssetID, UUIDlen);
465 memcpy(p, ber_4, MXF_BER_LENGTH);
466 *(p+3) = sizeof(ui64_t);
470 Kumu::i2p<ui64_t>(KM_i64_BE(sequence), p);
474 memcpy(p, ber_4, MXF_BER_LENGTH);
478 // update HMAC with intpack values
479 HMAC->Update(Data, klv_intpack_size - HMAC_SIZE);
481 // finish & write HMAC
483 HMAC->GetHMACValue(p);
485 assert(p + HMAC_SIZE == Data + klv_intpack_size);
492 ASDCP::IntegrityPack::TestValues(const ASDCP::FrameBuffer& FB, byte_t* AssetID,
493 ui32_t sequence, HMACContext* HMAC)
495 ASDCP_TEST_NULL(AssetID);
496 ASDCP_TEST_NULL(HMAC);
498 // find the start of the intpack
499 byte_t* p = (byte_t*)FB.RoData() + ( FB.Size() - klv_intpack_size );
501 // test the AssetID length
502 if ( ! Kumu::read_test_BER(&p, UUIDlen) )
503 return RESULT_HMACFAIL;
506 if ( memcmp(p, AssetID, UUIDlen) != 0 )
508 DefaultLogSink().Error("IntegrityPack failure: AssetID mismatch.\n");
509 return RESULT_HMACFAIL;
513 // test the sequence length
514 if ( ! Kumu::read_test_BER(&p, sizeof(ui64_t)) )
515 return RESULT_HMACFAIL;
517 ui32_t test_sequence = (ui32_t)KM_i64_BE(Kumu::cp2i<ui64_t>(p));
519 // test the sequence value
520 if ( test_sequence != sequence )
522 DefaultLogSink().Error("IntegrityPack failure: sequence is %u, expecting %u.\n", test_sequence, sequence);
523 return RESULT_HMACFAIL;
528 // test the HMAC length
529 if ( ! Kumu::read_test_BER(&p, HMAC_SIZE) )
530 return RESULT_HMACFAIL;
534 HMAC->Update(FB.RoData(), FB.Size() - HMAC_SIZE);
537 return HMAC->TestHMACValue(p);
541 // end AS_DCP_MXF.cpp