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
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();
168 ASDCP_TEST_NULL_STR(filename);
169 Kumu::FileReader Reader;
170 OPAtomHeader TestHeader(m_Dict);
172 Result_t result = Reader.OpenRead(filename);
174 if ( ASDCP_SUCCESS(result) )
175 result = TestHeader.InitFromFile(Reader); // test UL and OP
177 if ( ASDCP_SUCCESS(result) )
180 if ( ASDCP_SUCCESS(TestHeader.GetMDObjectByType(OBJ_TYPE_ARGS(RGBAEssenceDescriptor))) )
182 if ( ASDCP_SUCCESS(TestHeader.GetMDObjectByType(OBJ_TYPE_ARGS(StereoscopicPictureSubDescriptor))) )
183 type = ESS_JPEG_2000_S;
185 type = ESS_JPEG_2000;
187 else if ( ASDCP_SUCCESS(TestHeader.GetMDObjectByType(OBJ_TYPE_ARGS(WaveAudioDescriptor))) )
188 type = ESS_PCM_24b_48k;
189 else if ( ASDCP_SUCCESS(TestHeader.GetMDObjectByType(OBJ_TYPE_ARGS(MPEG2VideoDescriptor))) )
190 type = ESS_MPEG2_VES;
191 else if ( ASDCP_SUCCESS(TestHeader.GetMDObjectByType(OBJ_TYPE_ARGS(TimedTextDescriptor))) )
192 type = ESS_TIMED_TEXT;
200 ASDCP::RawEssenceType(const char* filename, EssenceType_t& type)
202 ASDCP_TEST_NULL_STR(filename);
204 ASDCP::FrameBuffer FB;
205 Kumu::FileReader Reader;
206 ASDCP::Wav::SimpleWaveHeader WavHeader;
207 ASDCP::AIFF::SimpleAIFFHeader AIFFHeader;
208 Kumu::XMLElement TmpElement("Tmp");
212 Result_t result = FB.Capacity(Wav::MaxWavHeader); // using Wav max because everything else is much smaller
214 if ( Kumu::PathIsFile(filename) )
216 result = Reader.OpenRead(filename);
218 if ( ASDCP_SUCCESS(result) )
220 result = Reader.Read(FB.Data(), FB.Capacity(), &read_count);
224 if ( ASDCP_SUCCESS(result) )
226 const byte_t* p = FB.RoData();
230 while ( p[i] == 0 ) i++;
232 if ( i > 1 && p[i] == 1 && (p[i+1] == ASDCP::MPEG2::SEQ_START || p[i+1] == ASDCP::MPEG2::PIC_START) )
234 type = ESS_MPEG2_VES;
236 else if ( ASDCP_SUCCESS(WavHeader.ReadFromBuffer(p, read_count, &data_offset)) )
238 switch ( WavHeader.samplespersec )
240 case 48000: type = ESS_PCM_24b_48k; break;
241 case 96000: type = ESS_PCM_24b_96k; break;
243 return RESULT_FORMAT;
246 else if ( ASDCP_SUCCESS(AIFFHeader.ReadFromBuffer(p, read_count, &data_offset)) )
248 type = ESS_PCM_24b_48k;
250 else if ( Kumu::StringIsXML((const char*)p, FB.Size()) )
252 type = ESS_TIMED_TEXT;
256 else if ( Kumu::PathIsDirectory(filename) )
258 char next_file[Kumu::MaxFilePath];
259 Kumu::DirScanner Scanner;
260 Result_t result = Scanner.Open(filename);
262 if ( ASDCP_SUCCESS(result) )
264 while ( ASDCP_SUCCESS(Scanner.GetNext(next_file)) )
266 if ( next_file[0] == '.' ) // no hidden files or internal links
269 std::string Str(filename);
272 result = Reader.OpenRead(Str.c_str());
274 if ( ASDCP_SUCCESS(result) )
276 result = Reader.Read(FB.Data(), FB.Capacity(), &read_count);
280 if ( ASDCP_SUCCESS(result) )
282 if ( memcmp(FB.RoData(), ASDCP::JP2K::Magic, sizeof(ASDCP::JP2K::Magic)) == 0 )
283 type = ESS_JPEG_2000;
285 else if ( ASDCP_SUCCESS(WavHeader.ReadFromBuffer(FB.RoData(), read_count, &data_offset)) )
286 type = ESS_PCM_24b_48k;
299 ASDCP::EncryptFrameBuffer(const ASDCP::FrameBuffer& FBin, ASDCP::FrameBuffer& FBout, AESEncContext* Ctx)
301 ASDCP_TEST_NULL(Ctx);
305 Result_t result = FBout.Capacity(calc_esv_length(FBin.Size(), FBin.PlaintextOffset()));
308 byte_t* p = FBout.Data();
310 // write the IV to the frame buffer
315 // encrypt the check value to the frame buffer
316 if ( ASDCP_SUCCESS(result) )
318 result = Ctx->EncryptBlock(ESV_CheckValue, p, CBC_BLOCK_SIZE);
322 // write optional plaintext region
323 if ( FBin.PlaintextOffset() > 0 )
325 assert(FBin.PlaintextOffset() <= FBin.Size());
326 memcpy(p, FBin.RoData(), FBin.PlaintextOffset());
327 p += FBin.PlaintextOffset();
330 ui32_t ct_size = FBin.Size() - FBin.PlaintextOffset();
331 ui32_t diff = ct_size % CBC_BLOCK_SIZE;
332 ui32_t block_size = ct_size - diff;
333 assert((block_size % CBC_BLOCK_SIZE) == 0);
335 // encrypt the ciphertext region essence data
336 if ( ASDCP_SUCCESS(result) )
338 result = Ctx->EncryptBlock(FBin.RoData() + FBin.PlaintextOffset(), p, block_size);
342 // construct and encrypt the padding
343 if ( ASDCP_SUCCESS(result) )
345 byte_t the_last_block[CBC_BLOCK_SIZE];
348 memcpy(the_last_block, FBin.RoData() + FBin.PlaintextOffset() + block_size, diff);
350 for (ui32_t i = 0; diff < CBC_BLOCK_SIZE; diff++, i++ )
351 the_last_block[diff] = i;
353 result = Ctx->EncryptBlock(the_last_block, p, CBC_BLOCK_SIZE);
356 if ( ASDCP_SUCCESS(result) )
357 FBout.Size(calc_esv_length(FBin.Size(), FBin.PlaintextOffset()));
364 ASDCP::DecryptFrameBuffer(const ASDCP::FrameBuffer& FBin, ASDCP::FrameBuffer& FBout, AESDecContext* Ctx)
366 ASDCP_TEST_NULL(Ctx);
367 assert(FBout.Capacity() >= FBin.SourceLength());
369 ui32_t ct_size = FBin.SourceLength() - FBin.PlaintextOffset();
370 ui32_t diff = ct_size % CBC_BLOCK_SIZE;
371 ui32_t block_size = ct_size - diff;
373 assert((block_size % CBC_BLOCK_SIZE) == 0);
375 const byte_t* buf = FBin.RoData();
379 buf += CBC_BLOCK_SIZE;
381 // decrypt and test check value
382 byte_t CheckValue[CBC_BLOCK_SIZE];
383 Result_t result = Ctx->DecryptBlock(buf, CheckValue, CBC_BLOCK_SIZE);
384 buf += CBC_BLOCK_SIZE;
386 if ( memcmp(CheckValue, ESV_CheckValue, CBC_BLOCK_SIZE) != 0 )
387 return RESULT_CHECKFAIL;
389 // copy plaintext region
390 if ( FBin.PlaintextOffset() > 0 )
392 memcpy(FBout.Data(), buf, FBin.PlaintextOffset());
393 buf += FBin.PlaintextOffset();
396 // decrypt all but last block
397 if ( ASDCP_SUCCESS(result) )
399 result = Ctx->DecryptBlock(buf, FBout.Data() + FBin.PlaintextOffset(), block_size);
403 // decrypt last block
404 if ( ASDCP_SUCCESS(result) )
406 byte_t the_last_block[CBC_BLOCK_SIZE];
407 result = Ctx->DecryptBlock(buf, the_last_block, CBC_BLOCK_SIZE);
409 if ( the_last_block[diff] != 0 )
411 DefaultLogSink().Error("Unexpected non-zero padding value.\n");
412 return RESULT_FORMAT;
416 memcpy(FBout.Data() + FBin.PlaintextOffset() + block_size, the_last_block, diff);
419 if ( ASDCP_SUCCESS(result) )
420 FBout.Size(FBin.SourceLength());
428 ASDCP::IntegrityPack::CalcValues(const ASDCP::FrameBuffer& FB, byte_t* AssetID,
429 ui32_t sequence, HMACContext* HMAC)
431 ASDCP_TEST_NULL(AssetID);
432 ASDCP_TEST_NULL(HMAC);
436 static byte_t ber_4[MXF_BER_LENGTH] = {0x83, 0, 0, 0};
438 // update HMAC with essence data
439 HMAC->Update(FB.RoData(), FB.Size());
441 // track file ID length
442 memcpy(p, ber_4, MXF_BER_LENGTH);
447 memcpy(p, AssetID, UUIDlen);
451 memcpy(p, ber_4, MXF_BER_LENGTH);
452 *(p+3) = sizeof(ui64_t);
456 Kumu::i2p<ui64_t>(KM_i64_BE(sequence), p);
460 memcpy(p, ber_4, MXF_BER_LENGTH);
464 // update HMAC with intpack values
465 HMAC->Update(Data, klv_intpack_size - HMAC_SIZE);
467 // finish & write HMAC
469 HMAC->GetHMACValue(p);
471 assert(p + HMAC_SIZE == Data + klv_intpack_size);
478 ASDCP::IntegrityPack::TestValues(const ASDCP::FrameBuffer& FB, byte_t* AssetID,
479 ui32_t sequence, HMACContext* HMAC)
481 ASDCP_TEST_NULL(AssetID);
482 ASDCP_TEST_NULL(HMAC);
484 // find the start of the intpack
485 byte_t* p = (byte_t*)FB.RoData() + ( FB.Size() - klv_intpack_size );
487 // test the AssetID length
488 if ( ! Kumu::read_test_BER(&p, UUIDlen) )
489 return RESULT_HMACFAIL;
492 if ( memcmp(p, AssetID, UUIDlen) != 0 )
494 DefaultLogSink().Error("IntegrityPack failure: AssetID mismatch.\n");
495 return RESULT_HMACFAIL;
499 // test the sequence length
500 if ( ! Kumu::read_test_BER(&p, sizeof(ui64_t)) )
501 return RESULT_HMACFAIL;
503 ui32_t test_sequence = (ui32_t)KM_i64_BE(Kumu::cp2i<ui64_t>(p));
505 // test the sequence value
506 if ( test_sequence != sequence )
508 DefaultLogSink().Error("IntegrityPack failure: sequence is %u, expecting %u.\n", test_sequence, sequence);
509 return RESULT_HMACFAIL;
514 // test the HMAC length
515 if ( ! Kumu::read_test_BER(&p, HMAC_SIZE) )
516 return RESULT_HMACFAIL;
520 HMAC->Update(FB.RoData(), FB.Size() - HMAC_SIZE);
523 return HMAC->TestHMACValue(p);
527 // end AS_DCP_MXF.cpp