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)
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 ( ASDCP_SUCCESS(WavHeader.ReadFromBuffer(p, read_count, &data_offset)) )
239 switch ( WavHeader.samplespersec )
241 case 48000: type = ESS_PCM_24b_48k; break;
242 case 96000: type = ESS_PCM_24b_96k; break;
244 return RESULT_FORMAT;
247 else if ( ASDCP_SUCCESS(AIFFHeader.ReadFromBuffer(p, read_count, &data_offset)) )
249 type = ESS_PCM_24b_48k;
251 else if ( Kumu::StringIsXML((const char*)p, FB.Size()) )
253 type = ESS_TIMED_TEXT;
257 else if ( Kumu::PathIsDirectory(filename) )
259 char next_file[Kumu::MaxFilePath];
260 Kumu::DirScanner Scanner;
261 Result_t result = Scanner.Open(filename);
263 if ( ASDCP_SUCCESS(result) )
265 while ( ASDCP_SUCCESS(Scanner.GetNext(next_file)) )
267 if ( next_file[0] == '.' ) // no hidden files or internal links
270 std::string Str(filename);
273 result = Reader.OpenRead(Str.c_str());
275 if ( ASDCP_SUCCESS(result) )
277 result = Reader.Read(FB.Data(), FB.Capacity(), &read_count);
281 if ( ASDCP_SUCCESS(result) )
283 if ( memcmp(FB.RoData(), ASDCP::JP2K::Magic, sizeof(ASDCP::JP2K::Magic)) == 0 )
284 type = ESS_JPEG_2000;
286 else if ( ASDCP_SUCCESS(WavHeader.ReadFromBuffer(FB.RoData(), read_count, &data_offset)) )
287 type = ESS_PCM_24b_48k;
300 ASDCP::EncryptFrameBuffer(const ASDCP::FrameBuffer& FBin, ASDCP::FrameBuffer& FBout, AESEncContext* Ctx)
302 ASDCP_TEST_NULL(Ctx);
306 Result_t result = FBout.Capacity(calc_esv_length(FBin.Size(), FBin.PlaintextOffset()));
309 byte_t* p = FBout.Data();
311 // write the IV to the frame buffer
316 // encrypt the check value to the frame buffer
317 if ( ASDCP_SUCCESS(result) )
319 result = Ctx->EncryptBlock(ESV_CheckValue, p, CBC_BLOCK_SIZE);
323 // write optional plaintext region
324 if ( FBin.PlaintextOffset() > 0 )
326 assert(FBin.PlaintextOffset() <= FBin.Size());
327 memcpy(p, FBin.RoData(), FBin.PlaintextOffset());
328 p += FBin.PlaintextOffset();
331 ui32_t ct_size = FBin.Size() - FBin.PlaintextOffset();
332 ui32_t diff = ct_size % CBC_BLOCK_SIZE;
333 ui32_t block_size = ct_size - diff;
334 assert((block_size % CBC_BLOCK_SIZE) == 0);
336 // encrypt the ciphertext region essence data
337 if ( ASDCP_SUCCESS(result) )
339 result = Ctx->EncryptBlock(FBin.RoData() + FBin.PlaintextOffset(), p, block_size);
343 // construct and encrypt the padding
344 if ( ASDCP_SUCCESS(result) )
346 byte_t the_last_block[CBC_BLOCK_SIZE];
349 memcpy(the_last_block, FBin.RoData() + FBin.PlaintextOffset() + block_size, diff);
351 for (ui32_t i = 0; diff < CBC_BLOCK_SIZE; diff++, i++ )
352 the_last_block[diff] = i;
354 result = Ctx->EncryptBlock(the_last_block, p, CBC_BLOCK_SIZE);
357 if ( ASDCP_SUCCESS(result) )
358 FBout.Size(calc_esv_length(FBin.Size(), FBin.PlaintextOffset()));
365 ASDCP::DecryptFrameBuffer(const ASDCP::FrameBuffer& FBin, ASDCP::FrameBuffer& FBout, AESDecContext* Ctx)
367 ASDCP_TEST_NULL(Ctx);
368 assert(FBout.Capacity() >= FBin.SourceLength());
370 ui32_t ct_size = FBin.SourceLength() - FBin.PlaintextOffset();
371 ui32_t diff = ct_size % CBC_BLOCK_SIZE;
372 ui32_t block_size = ct_size - diff;
374 assert((block_size % CBC_BLOCK_SIZE) == 0);
376 const byte_t* buf = FBin.RoData();
380 buf += CBC_BLOCK_SIZE;
382 // decrypt and test check value
383 byte_t CheckValue[CBC_BLOCK_SIZE];
384 Result_t result = Ctx->DecryptBlock(buf, CheckValue, CBC_BLOCK_SIZE);
385 buf += CBC_BLOCK_SIZE;
387 if ( memcmp(CheckValue, ESV_CheckValue, CBC_BLOCK_SIZE) != 0 )
388 return RESULT_CHECKFAIL;
390 // copy plaintext region
391 if ( FBin.PlaintextOffset() > 0 )
393 memcpy(FBout.Data(), buf, FBin.PlaintextOffset());
394 buf += FBin.PlaintextOffset();
397 // decrypt all but last block
398 if ( ASDCP_SUCCESS(result) )
400 result = Ctx->DecryptBlock(buf, FBout.Data() + FBin.PlaintextOffset(), block_size);
404 // decrypt last block
405 if ( ASDCP_SUCCESS(result) )
407 byte_t the_last_block[CBC_BLOCK_SIZE];
408 result = Ctx->DecryptBlock(buf, the_last_block, CBC_BLOCK_SIZE);
410 if ( the_last_block[diff] != 0 )
412 DefaultLogSink().Error("Unexpected non-zero padding value.\n");
413 return RESULT_FORMAT;
417 memcpy(FBout.Data() + FBin.PlaintextOffset() + block_size, the_last_block, diff);
420 if ( ASDCP_SUCCESS(result) )
421 FBout.Size(FBin.SourceLength());
429 ASDCP::IntegrityPack::CalcValues(const ASDCP::FrameBuffer& FB, byte_t* AssetID,
430 ui32_t sequence, HMACContext* HMAC)
432 ASDCP_TEST_NULL(AssetID);
433 ASDCP_TEST_NULL(HMAC);
437 static byte_t ber_4[MXF_BER_LENGTH] = {0x83, 0, 0, 0};
439 // update HMAC with essence data
440 HMAC->Update(FB.RoData(), FB.Size());
442 // track file ID length
443 memcpy(p, ber_4, MXF_BER_LENGTH);
448 memcpy(p, AssetID, UUIDlen);
452 memcpy(p, ber_4, MXF_BER_LENGTH);
453 *(p+3) = sizeof(ui64_t);
457 Kumu::i2p<ui64_t>(KM_i64_BE(sequence), p);
461 memcpy(p, ber_4, MXF_BER_LENGTH);
465 // update HMAC with intpack values
466 HMAC->Update(Data, klv_intpack_size - HMAC_SIZE);
468 // finish & write HMAC
470 HMAC->GetHMACValue(p);
472 assert(p + HMAC_SIZE == Data + klv_intpack_size);
479 ASDCP::IntegrityPack::TestValues(const ASDCP::FrameBuffer& FB, byte_t* AssetID,
480 ui32_t sequence, HMACContext* HMAC)
482 ASDCP_TEST_NULL(AssetID);
483 ASDCP_TEST_NULL(HMAC);
485 // find the start of the intpack
486 byte_t* p = (byte_t*)FB.RoData() + ( FB.Size() - klv_intpack_size );
488 // test the AssetID length
489 if ( ! Kumu::read_test_BER(&p, UUIDlen) )
490 return RESULT_HMACFAIL;
493 if ( memcmp(p, AssetID, UUIDlen) != 0 )
495 DefaultLogSink().Error("IntegrityPack failure: AssetID mismatch.\n");
496 return RESULT_HMACFAIL;
500 // test the sequence length
501 if ( ! Kumu::read_test_BER(&p, sizeof(ui64_t)) )
502 return RESULT_HMACFAIL;
504 ui32_t test_sequence = (ui32_t)KM_i64_BE(Kumu::cp2i<ui64_t>(p));
506 // test the sequence value
507 if ( test_sequence != sequence )
509 DefaultLogSink().Error("IntegrityPack failure: sequence is %u, expecting %u.\n", test_sequence, sequence);
510 return RESULT_HMACFAIL;
515 // test the HMAC length
516 if ( ! Kumu::read_test_BER(&p, HMAC_SIZE) )
517 return RESULT_HMACFAIL;
521 HMAC->Update(FB.RoData(), FB.Size() - HMAC_SIZE);
524 return HMAC->TestHMACValue(p);
528 // end AS_DCP_MXF.cpp