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"
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)
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 ASDCP_TEST_NULL_STR(filename);
167 Kumu::FileReader Reader;
168 OPAtomHeader TestHeader;
170 Result_t result = Reader.OpenRead(filename);
172 if ( ASDCP_SUCCESS(result) )
173 result = TestHeader.InitFromFile(Reader); // test UL and OP
175 if ( ASDCP_SUCCESS(result) )
178 if ( ASDCP_SUCCESS(TestHeader.GetMDObjectByType(OBJ_TYPE_ARGS(RGBAEssenceDescriptor))) )
180 if ( ASDCP_SUCCESS(TestHeader.GetMDObjectByType(OBJ_TYPE_ARGS(StereoscopicPictureSubDescriptor))) )
181 type = ESS_JPEG_2000_S;
183 type = ESS_JPEG_2000;
185 else if ( ASDCP_SUCCESS(TestHeader.GetMDObjectByType(OBJ_TYPE_ARGS(WaveAudioDescriptor))) )
186 type = ESS_PCM_24b_48k;
187 else if ( ASDCP_SUCCESS(TestHeader.GetMDObjectByType(OBJ_TYPE_ARGS(MPEG2VideoDescriptor))) )
188 type = ESS_MPEG2_VES;
189 else if ( ASDCP_SUCCESS(TestHeader.GetMDObjectByType(OBJ_TYPE_ARGS(TimedTextDescriptor))) )
190 type = ESS_TIMED_TEXT;
198 ASDCP::RawEssenceType(const char* filename, EssenceType_t& type)
200 ASDCP_TEST_NULL_STR(filename);
202 ASDCP::FrameBuffer FB;
203 Kumu::FileReader Reader;
204 ASDCP::Wav::SimpleWaveHeader WavHeader;
205 ASDCP::AIFF::SimpleAIFFHeader AIFFHeader;
206 Kumu::XMLElement TmpElement("Tmp");
210 Result_t result = FB.Capacity(Wav::MaxWavHeader); // using Wav max because everything else is much smaller
212 if ( Kumu::PathIsFile(filename) )
214 result = Reader.OpenRead(filename);
216 if ( ASDCP_SUCCESS(result) )
218 result = Reader.Read(FB.Data(), FB.Capacity(), &read_count);
222 if ( ASDCP_SUCCESS(result) )
224 const byte_t* p = FB.RoData();
228 while ( p[i] == 0 ) i++;
230 if ( i > 1 && p[i] == 1 && (p[i+1] == ASDCP::MPEG2::SEQ_START || p[i+1] == ASDCP::MPEG2::PIC_START) )
232 type = ESS_MPEG2_VES;
234 else if ( ASDCP_SUCCESS(WavHeader.ReadFromBuffer(p, read_count, &data_offset)) )
236 switch ( WavHeader.samplespersec )
238 case 48000: type = ESS_PCM_24b_48k; break;
239 case 96000: type = ESS_PCM_24b_96k; break;
241 return RESULT_FORMAT;
244 else if ( ASDCP_SUCCESS(AIFFHeader.ReadFromBuffer(p, read_count, &data_offset)) )
246 type = ESS_PCM_24b_48k;
248 else if ( Kumu::StringIsXML((const char*)p, FB.Size()) )
250 type = ESS_TIMED_TEXT;
254 else if ( Kumu::PathIsDirectory(filename) )
256 char next_file[Kumu::MaxFilePath];
257 Kumu::DirScanner Scanner;
258 Result_t result = Scanner.Open(filename);
260 if ( ASDCP_SUCCESS(result) )
262 while ( ASDCP_SUCCESS(Scanner.GetNext(next_file)) )
264 if ( next_file[0] == '.' ) // no hidden files or internal links
267 std::string Str(filename);
270 result = Reader.OpenRead(Str.c_str());
272 if ( ASDCP_SUCCESS(result) )
274 result = Reader.Read(FB.Data(), FB.Capacity(), &read_count);
278 if ( ASDCP_SUCCESS(result) )
280 if ( memcmp(FB.RoData(), ASDCP::JP2K::Magic, sizeof(ASDCP::JP2K::Magic)) == 0 )
281 type = ESS_JPEG_2000;
283 else if ( ASDCP_SUCCESS(WavHeader.ReadFromBuffer(FB.RoData(), read_count, &data_offset)) )
284 type = ESS_PCM_24b_48k;
297 ASDCP::EncryptFrameBuffer(const ASDCP::FrameBuffer& FBin, ASDCP::FrameBuffer& FBout, AESEncContext* Ctx)
299 ASDCP_TEST_NULL(Ctx);
303 Result_t result = FBout.Capacity(calc_esv_length(FBin.Size(), FBin.PlaintextOffset()));
306 byte_t* p = FBout.Data();
308 // write the IV to the frame buffer
313 // encrypt the check value to the frame buffer
314 if ( ASDCP_SUCCESS(result) )
316 result = Ctx->EncryptBlock(ESV_CheckValue, p, CBC_BLOCK_SIZE);
320 // write optional plaintext region
321 if ( FBin.PlaintextOffset() > 0 )
323 assert(FBin.PlaintextOffset() <= FBin.Size());
324 memcpy(p, FBin.RoData(), FBin.PlaintextOffset());
325 p += FBin.PlaintextOffset();
328 ui32_t ct_size = FBin.Size() - FBin.PlaintextOffset();
329 ui32_t diff = ct_size % CBC_BLOCK_SIZE;
330 ui32_t block_size = ct_size - diff;
331 assert((block_size % CBC_BLOCK_SIZE) == 0);
333 // encrypt the ciphertext region essence data
334 if ( ASDCP_SUCCESS(result) )
336 result = Ctx->EncryptBlock(FBin.RoData() + FBin.PlaintextOffset(), p, block_size);
340 // construct and encrypt the padding
341 if ( ASDCP_SUCCESS(result) )
343 byte_t the_last_block[CBC_BLOCK_SIZE];
346 memcpy(the_last_block, FBin.RoData() + FBin.PlaintextOffset() + block_size, diff);
348 for (ui32_t i = 0; diff < CBC_BLOCK_SIZE; diff++, i++ )
349 the_last_block[diff] = i;
351 result = Ctx->EncryptBlock(the_last_block, p, CBC_BLOCK_SIZE);
354 if ( ASDCP_SUCCESS(result) )
355 FBout.Size(calc_esv_length(FBin.Size(), FBin.PlaintextOffset()));
362 ASDCP::DecryptFrameBuffer(const ASDCP::FrameBuffer& FBin, ASDCP::FrameBuffer& FBout, AESDecContext* Ctx)
364 ASDCP_TEST_NULL(Ctx);
365 assert(FBout.Capacity() >= FBin.SourceLength());
367 ui32_t ct_size = FBin.SourceLength() - FBin.PlaintextOffset();
368 ui32_t diff = ct_size % CBC_BLOCK_SIZE;
369 ui32_t block_size = ct_size - diff;
371 assert((block_size % CBC_BLOCK_SIZE) == 0);
373 const byte_t* buf = FBin.RoData();
377 buf += CBC_BLOCK_SIZE;
379 // decrypt and test check value
380 byte_t CheckValue[CBC_BLOCK_SIZE];
381 Result_t result = Ctx->DecryptBlock(buf, CheckValue, CBC_BLOCK_SIZE);
382 buf += CBC_BLOCK_SIZE;
384 if ( memcmp(CheckValue, ESV_CheckValue, CBC_BLOCK_SIZE) != 0 )
385 return RESULT_CHECKFAIL;
387 // copy plaintext region
388 if ( FBin.PlaintextOffset() > 0 )
390 memcpy(FBout.Data(), buf, FBin.PlaintextOffset());
391 buf += FBin.PlaintextOffset();
394 // decrypt all but last block
395 if ( ASDCP_SUCCESS(result) )
397 result = Ctx->DecryptBlock(buf, FBout.Data() + FBin.PlaintextOffset(), block_size);
401 // decrypt last block
402 if ( ASDCP_SUCCESS(result) )
404 byte_t the_last_block[CBC_BLOCK_SIZE];
405 result = Ctx->DecryptBlock(buf, the_last_block, CBC_BLOCK_SIZE);
407 if ( the_last_block[diff] != 0 )
409 DefaultLogSink().Error("Unexpected non-zero padding value.\n");
410 return RESULT_FORMAT;
414 memcpy(FBout.Data() + FBin.PlaintextOffset() + block_size, the_last_block, diff);
417 if ( ASDCP_SUCCESS(result) )
418 FBout.Size(FBin.SourceLength());
426 ASDCP::IntegrityPack::CalcValues(const ASDCP::FrameBuffer& FB, byte_t* AssetID,
427 ui32_t sequence, HMACContext* HMAC)
429 ASDCP_TEST_NULL(AssetID);
430 ASDCP_TEST_NULL(HMAC);
434 static byte_t ber_4[MXF_BER_LENGTH] = {0x83, 0, 0, 0};
436 // update HMAC with essence data
437 HMAC->Update(FB.RoData(), FB.Size());
439 // track file ID length
440 memcpy(p, ber_4, MXF_BER_LENGTH);
445 memcpy(p, AssetID, UUIDlen);
449 memcpy(p, ber_4, MXF_BER_LENGTH);
450 *(p+3) = sizeof(ui64_t);
454 Kumu::i2p<ui64_t>(KM_i64_BE(sequence), p);
458 memcpy(p, ber_4, MXF_BER_LENGTH);
462 // update HMAC with intpack values
463 HMAC->Update(Data, klv_intpack_size - HMAC_SIZE);
465 // finish & write HMAC
467 HMAC->GetHMACValue(p);
469 assert(p + HMAC_SIZE == Data + klv_intpack_size);
476 ASDCP::IntegrityPack::TestValues(const ASDCP::FrameBuffer& FB, byte_t* AssetID,
477 ui32_t sequence, HMACContext* HMAC)
479 ASDCP_TEST_NULL(AssetID);
480 ASDCP_TEST_NULL(HMAC);
482 // find the start of the intpack
483 byte_t* p = (byte_t*)FB.RoData() + ( FB.Size() - klv_intpack_size );
485 // test the AssetID length
486 if ( ! Kumu::read_test_BER(&p, UUIDlen) )
487 return RESULT_HMACFAIL;
490 if ( memcmp(p, AssetID, UUIDlen) != 0 )
492 DefaultLogSink().Error("IntegrityPack failure: AssetID mismatch.\n");
493 return RESULT_HMACFAIL;
497 // test the sequence length
498 if ( ! Kumu::read_test_BER(&p, sizeof(ui64_t)) )
499 return RESULT_HMACFAIL;
501 ui32_t test_sequence = (ui32_t)KM_i64_BE(Kumu::cp2i<ui64_t>(p));
503 // test the sequence value
504 if ( test_sequence != sequence )
506 DefaultLogSink().Error("IntegrityPack failure: sequence is %u, expecting %u.\n", test_sequence, sequence);
507 return RESULT_HMACFAIL;
512 // test the HMAC length
513 if ( ! Kumu::read_test_BER(&p, HMAC_SIZE) )
514 return RESULT_HMACFAIL;
518 HMAC->Update(FB.RoData(), FB.Size() - HMAC_SIZE);
521 return HMAC->TestHMACValue(p);
525 // end AS_DCP_MXF.cpp