Result_t ASDCP::IntegrityPack::TestValues(const ASDCP::FrameBuffer& FB, const byte_t* AssetID, ui32_t sequence, HMACContext* HMAC) { ASDCP_TEST_NULL(AssetID); ASDCP_TEST_NULL(HMAC); // find the start of the intpack byte_t* p = (byte_t*)FB.RoData() + ( FB.Size() - klv_intpack_size ); // test the AssetID length if ( ! Kumu::read_test_BER(&p, UUIDlen) ) return RESULT_HMACFAIL; // test the AssetID if ( memcmp(p, AssetID, UUIDlen) != 0 ) { DefaultLogSink().Error("IntegrityPack failure: AssetID mismatch.\n"); return RESULT_HMACFAIL; } p += UUIDlen; // test the sequence length if ( ! Kumu::read_test_BER(&p, sizeof(ui64_t)) ) return RESULT_HMACFAIL; ui32_t test_sequence = (ui32_t)KM_i64_BE(Kumu::cp2i<ui64_t>(p)); // test the sequence value if ( test_sequence != sequence ) { DefaultLogSink().Error("IntegrityPack failure: sequence is %u, expecting %u.\n", test_sequence, sequence); return RESULT_HMACFAIL; } p += sizeof(ui64_t); // test the HMAC length if ( ! Kumu::read_test_BER(&p, HMAC_SIZE) ) return RESULT_HMACFAIL; // test the HMAC HMAC->Reset(); HMAC->Update(FB.RoData(), FB.Size() - HMAC_SIZE); HMAC->Finalize(); return HMAC->TestHMACValue(p); }
Result_t AS_02::MXF::AS02IndexWriterVBR::WriteToFile(Kumu::FileWriter& Writer) { assert(m_Dict); ASDCP::FrameBuffer index_body_buffer; ui32_t index_body_size = m_PacketList->m_List.size() * MaxIndexSegmentSize; // segment-count * max-segment-size Result_t result = index_body_buffer.Capacity(index_body_size); ui64_t start_position = 0; if ( m_CurrentSegment != 0 ) { m_CurrentSegment->IndexDuration = m_CurrentSegment->IndexEntryArray.size(); start_position = m_CurrentSegment->IndexStartPosition + m_CurrentSegment->IndexDuration; m_CurrentSegment = 0; } std::list<InterchangeObject*>::iterator pl_i = m_PacketList->m_List.begin(); for ( ; pl_i != m_PacketList->m_List.end() && KM_SUCCESS(result); pl_i++ ) { InterchangeObject* object = *pl_i; object->m_Lookup = m_Lookup; ASDCP::FrameBuffer WriteWrapper; WriteWrapper.SetData(index_body_buffer.Data() + index_body_buffer.Size(), index_body_buffer.Capacity() - index_body_buffer.Size()); result = object->WriteToBuffer(WriteWrapper); index_body_buffer.Size(index_body_buffer.Size() + WriteWrapper.Size()); delete *pl_i; *pl_i = 0; } m_PacketList->m_List.clear(); if ( KM_SUCCESS(result) ) { IndexByteCount = index_body_buffer.Size(); UL body_ul(m_Dict->ul(MDD_ClosedCompleteBodyPartition)); result = Partition::WriteToFile(Writer, body_ul); } if ( KM_SUCCESS(result) ) { ui32_t write_count = 0; result = Writer.Write(index_body_buffer.RoData(), index_body_buffer.Size(), &write_count); assert(write_count == index_body_buffer.Size()); } if ( KM_SUCCESS(result) ) { m_CurrentSegment = new IndexTableSegment(m_Dict); assert(m_CurrentSegment); AddChildObject(m_CurrentSegment); m_CurrentSegment->DeltaEntryArray.push_back(IndexTableSegment::DeltaEntry()); m_CurrentSegment->IndexEditRate = m_EditRate; m_CurrentSegment->IndexStartPosition = start_position; } return result; }
static bool string_is_xml(const ASDCP::FrameBuffer& buffer) { std::string ns_prefix, type_name, namespace_name; Kumu::AttributeList doc_attr_list; return GetXMLDocType(buffer.RoData(), buffer.Size(), ns_prefix, type_name, namespace_name, doc_attr_list); }
ASDCP::Result_t ASDCP::KLVPacket::WriteKLToBuffer(ASDCP::FrameBuffer& Buffer, const UL& label, ui32_t length) { assert(label.HasValue()); if ( Buffer.Size() + kl_length > Buffer.Capacity() ) { DefaultLogSink().Error("Small write buffer\n"); return RESULT_FAIL; } memcpy(Buffer.Data() + Buffer.Size(), label.Value(), label.Size()); if ( ! Kumu::write_BER(Buffer.Data() + Buffer.Size() + SMPTE_UL_LENGTH, length, MXF_BER_LENGTH) ) return RESULT_FAIL; Buffer.Size(Buffer.Size() + kl_length); return RESULT_OK; }
Result_t AS_02::h__AS02WriterClip::WriteClipBlock(const ASDCP::FrameBuffer& FrameBuf) { if ( m_ClipStart == 0 ) { DefaultLogSink().Error("Cannot write clip block, no clip open.\n"); return RESULT_STATE; } return m_File.Write(FrameBuf.RoData(), FrameBuf.Size()); }
Result_t ASDCP::IntegrityPack::CalcValues(const ASDCP::FrameBuffer& FB, const byte_t* AssetID, ui32_t sequence, HMACContext* HMAC) { ASDCP_TEST_NULL(AssetID); ASDCP_TEST_NULL(HMAC); byte_t* p = Data; HMAC->Reset(); static byte_t ber_4[MXF_BER_LENGTH] = {0x83, 0, 0, 0}; // update HMAC with essence data HMAC->Update(FB.RoData(), FB.Size()); // track file ID length memcpy(p, ber_4, MXF_BER_LENGTH); *(p+3) = UUIDlen;; p += MXF_BER_LENGTH; // track file ID memcpy(p, AssetID, UUIDlen); p += UUIDlen; // sequence length memcpy(p, ber_4, MXF_BER_LENGTH); *(p+3) = sizeof(ui64_t); p += MXF_BER_LENGTH; // sequence number Kumu::i2p<ui64_t>(KM_i64_BE(sequence), p); p += sizeof(ui64_t); // HMAC length memcpy(p, ber_4, MXF_BER_LENGTH); *(p+3) = HMAC_SIZE; p += MXF_BER_LENGTH; // update HMAC with intpack values HMAC->Update(Data, klv_intpack_size - HMAC_SIZE); // finish & write HMAC HMAC->Finalize(); HMAC->GetHMACValue(p); assert(p + HMAC_SIZE == Data + klv_intpack_size); return RESULT_OK; }
Result_t AS_02::MXF::AS02IndexWriterCBR::WriteToFile(Kumu::FileWriter& Writer) { assert(m_Dict); ASDCP::FrameBuffer index_body_buffer; ui32_t index_body_size = MaxIndexSegmentSize; // segment-count * max-segment-size Result_t result = index_body_buffer.Capacity(index_body_size); m_CurrentSegment = new IndexTableSegment(m_Dict); assert(m_CurrentSegment); m_CurrentSegment->m_Lookup = m_Lookup; m_CurrentSegment->IndexEditRate = m_EditRate; m_CurrentSegment->IndexStartPosition = 0; m_CurrentSegment->IndexDuration = m_Duration; m_CurrentSegment->EditUnitByteCount = m_SampleSize; AddChildObject(m_CurrentSegment); ASDCP::FrameBuffer WriteWrapper; WriteWrapper.SetData(index_body_buffer.Data() + index_body_buffer.Size(), index_body_buffer.Capacity() - index_body_buffer.Size()); result = m_CurrentSegment->WriteToBuffer(WriteWrapper); index_body_buffer.Size(index_body_buffer.Size() + WriteWrapper.Size()); delete m_CurrentSegment; m_CurrentSegment = 0; m_PacketList->m_List.clear(); if ( KM_SUCCESS(result) ) { IndexByteCount = index_body_buffer.Size(); UL body_ul(m_Dict->ul(MDD_ClosedCompleteBodyPartition)); result = Partition::WriteToFile(Writer, body_ul); } if ( KM_SUCCESS(result) ) { ui32_t write_count = 0; result = Writer.Write(index_body_buffer.RoData(), index_body_buffer.Size(), &write_count); assert(write_count == index_body_buffer.Size()); } return result; }
// base subroutine for reading a KLV packet, assumes file position is at the first byte of the packet Result_t ASDCP::Read_EKLV_Packet(Kumu::FileReader& File, const ASDCP::Dictionary& Dict, const ASDCP::WriterInfo& Info, Kumu::fpos_t& LastPosition, ASDCP::FrameBuffer& CtFrameBuf, ui32_t FrameNum, ui32_t SequenceNum, ASDCP::FrameBuffer& FrameBuf, const byte_t* EssenceUL, AESDecContext* Ctx, HMACContext* HMAC) { KLReader Reader; Result_t result = Reader.ReadKLFromFile(File); if ( KM_FAILURE(result) ) return result; UL Key(Reader.Key()); ui64_t PacketLength = Reader.Length(); LastPosition = LastPosition + Reader.KLLength() + PacketLength; if ( Key.MatchIgnoreStream(Dict.ul(MDD_CryptEssence)) ) // ignore the stream numbers { if ( ! Info.EncryptedEssence ) { DefaultLogSink().Error("EKLV packet found, no Cryptographic Context in header.\n"); return RESULT_FORMAT; } // read encrypted triplet value into internal buffer assert(PacketLength <= 0xFFFFFFFFL); CtFrameBuf.Capacity((ui32_t) PacketLength); ui32_t read_count; result = File.Read(CtFrameBuf.Data(), (ui32_t) PacketLength, &read_count); if ( ASDCP_FAILURE(result) ) return result; if ( read_count != PacketLength ) { DefaultLogSink().Error("read length is smaller than EKLV packet length.\n"); return RESULT_FORMAT; } CtFrameBuf.Size((ui32_t) PacketLength); // should be const but mxflib::ReadBER is not byte_t* ess_p = CtFrameBuf.Data(); // read context ID length if ( ! Kumu::read_test_BER(&ess_p, UUIDlen) ) return RESULT_FORMAT; // test the context ID if ( memcmp(ess_p, Info.ContextID, UUIDlen) != 0 ) { DefaultLogSink().Error("Packet's Cryptographic Context ID does not match the header.\n"); return RESULT_FORMAT; } ess_p += UUIDlen; // read PlaintextOffset length if ( ! Kumu::read_test_BER(&ess_p, sizeof(ui64_t)) ) return RESULT_FORMAT; ui32_t PlaintextOffset = (ui32_t)KM_i64_BE(Kumu::cp2i<ui64_t>(ess_p)); ess_p += sizeof(ui64_t); // read essence UL length if ( ! Kumu::read_test_BER(&ess_p, SMPTE_UL_LENGTH) ) return RESULT_FORMAT; // test essence UL if ( ! UL(ess_p).MatchIgnoreStream(EssenceUL) ) // ignore the stream number { char strbuf[IntBufferLen]; const MDDEntry* Entry = Dict.FindUL(Key.Value()); if ( Entry == 0 ) { DefaultLogSink().Warn("Unexpected Essence UL found: %s.\n", Key.EncodeString(strbuf, IntBufferLen)); } else { DefaultLogSink().Warn("Unexpected Essence UL found: %s.\n", Entry->name); } return RESULT_FORMAT; } ess_p += SMPTE_UL_LENGTH; // read SourceLength length if ( ! Kumu::read_test_BER(&ess_p, sizeof(ui64_t)) ) return RESULT_FORMAT; ui32_t SourceLength = (ui32_t)KM_i64_BE(Kumu::cp2i<ui64_t>(ess_p)); ess_p += sizeof(ui64_t); assert(SourceLength); if ( FrameBuf.Capacity() < SourceLength ) { DefaultLogSink().Error("FrameBuf.Capacity: %u SourceLength: %u\n", FrameBuf.Capacity(), SourceLength); return RESULT_SMALLBUF; } ui32_t esv_length = calc_esv_length(SourceLength, PlaintextOffset); // read ESV length if ( ! Kumu::read_test_BER(&ess_p, esv_length) ) { DefaultLogSink().Error("read_test_BER did not return %u\n", esv_length); return RESULT_FORMAT; } ui32_t tmp_len = esv_length + (Info.UsesHMAC ? klv_intpack_size : 0); if ( PacketLength < tmp_len ) { DefaultLogSink().Error("Frame length is larger than EKLV packet length.\n"); return RESULT_FORMAT; } if ( Ctx ) { // wrap the pointer and length as a FrameBuffer for use by // DecryptFrameBuffer() and TestValues() FrameBuffer TmpWrapper; TmpWrapper.SetData(ess_p, tmp_len); TmpWrapper.Size(tmp_len); TmpWrapper.SourceLength(SourceLength); TmpWrapper.PlaintextOffset(PlaintextOffset); result = DecryptFrameBuffer(TmpWrapper, FrameBuf, Ctx); FrameBuf.FrameNumber(FrameNum); // detect and test integrity pack if ( ASDCP_SUCCESS(result) && Info.UsesHMAC && HMAC ) { IntegrityPack IntPack; result = IntPack.TestValues(TmpWrapper, Info.AssetUUID, SequenceNum, HMAC); } } else // return ciphertext to caller { if ( FrameBuf.Capacity() < tmp_len ) { char intbuf[IntBufferLen]; DefaultLogSink().Error("FrameBuf.Capacity: %u FrameLength: %s\n", FrameBuf.Capacity(), ui64sz(PacketLength, intbuf)); return RESULT_SMALLBUF; } memcpy(FrameBuf.Data(), ess_p, tmp_len); FrameBuf.Size(tmp_len); FrameBuf.FrameNumber(FrameNum); FrameBuf.SourceLength(SourceLength); FrameBuf.PlaintextOffset(PlaintextOffset); } } else if ( Key.MatchIgnoreStream(EssenceUL) ) // ignore the stream number { // read plaintext frame if ( FrameBuf.Capacity() < PacketLength ) { char intbuf[IntBufferLen]; DefaultLogSink().Error("FrameBuf.Capacity: %u FrameLength: %s\n", FrameBuf.Capacity(), ui64sz(PacketLength, intbuf)); return RESULT_SMALLBUF; } // read the data into the supplied buffer ui32_t read_count; assert(PacketLength <= 0xFFFFFFFFL); result = File.Read(FrameBuf.Data(), (ui32_t) PacketLength, &read_count); if ( ASDCP_FAILURE(result) ) return result; if ( read_count != PacketLength ) { char intbuf1[IntBufferLen]; char intbuf2[IntBufferLen]; DefaultLogSink().Error("read_count: %s != FrameLength: %s\n", ui64sz(read_count, intbuf1), ui64sz(PacketLength, intbuf2) ); return RESULT_READFAIL; } FrameBuf.FrameNumber(FrameNum); FrameBuf.Size(read_count); } else { char strbuf[IntBufferLen]; const MDDEntry* Entry = Dict.FindUL(Key.Value()); if ( Entry == 0 ) { DefaultLogSink().Warn("Unexpected Essence UL found: %s.\n", Key.EncodeString(strbuf, IntBufferLen)); } else { DefaultLogSink().Warn("Unexpected Essence UL found: %s.\n", Entry->name); } return RESULT_FORMAT; } return result; }
Result_t ASDCP::DecryptFrameBuffer(const ASDCP::FrameBuffer& FBin, ASDCP::FrameBuffer& FBout, AESDecContext* Ctx) { ASDCP_TEST_NULL(Ctx); assert(FBout.Capacity() >= FBin.SourceLength()); ui32_t ct_size = FBin.SourceLength() - FBin.PlaintextOffset(); ui32_t diff = ct_size % CBC_BLOCK_SIZE; ui32_t block_size = ct_size - diff; assert(block_size); assert((block_size % CBC_BLOCK_SIZE) == 0); const byte_t* buf = FBin.RoData(); // get ivec Ctx->SetIVec(buf); buf += CBC_BLOCK_SIZE; // decrypt and test check value byte_t CheckValue[CBC_BLOCK_SIZE]; Result_t result = Ctx->DecryptBlock(buf, CheckValue, CBC_BLOCK_SIZE); buf += CBC_BLOCK_SIZE; if ( memcmp(CheckValue, ESV_CheckValue, CBC_BLOCK_SIZE) != 0 ) return RESULT_CHECKFAIL; // copy plaintext region if ( FBin.PlaintextOffset() > 0 ) { memcpy(FBout.Data(), buf, FBin.PlaintextOffset()); buf += FBin.PlaintextOffset(); } // decrypt all but last block if ( ASDCP_SUCCESS(result) ) { result = Ctx->DecryptBlock(buf, FBout.Data() + FBin.PlaintextOffset(), block_size); buf += block_size; } // decrypt last block if ( ASDCP_SUCCESS(result) ) { byte_t the_last_block[CBC_BLOCK_SIZE]; result = Ctx->DecryptBlock(buf, the_last_block, CBC_BLOCK_SIZE); if ( the_last_block[diff] != 0 ) { DefaultLogSink().Error("Unexpected non-zero padding value.\n"); return RESULT_FORMAT; } if ( diff > 0 ) memcpy(FBout.Data() + FBin.PlaintextOffset() + block_size, the_last_block, diff); } if ( ASDCP_SUCCESS(result) ) FBout.Size(FBin.SourceLength()); return result; }
Result_t ASDCP::EncryptFrameBuffer(const ASDCP::FrameBuffer& FBin, ASDCP::FrameBuffer& FBout, AESEncContext* Ctx) { ASDCP_TEST_NULL(Ctx); FBout.Size(0); // size the buffer Result_t result = FBout.Capacity(calc_esv_length(FBin.Size(), FBin.PlaintextOffset())); // write the IV byte_t* p = FBout.Data(); // write the IV to the frame buffer Ctx->GetIVec(p); p += CBC_BLOCK_SIZE; // encrypt the check value to the frame buffer if ( ASDCP_SUCCESS(result) ) { result = Ctx->EncryptBlock(ESV_CheckValue, p, CBC_BLOCK_SIZE); p += CBC_BLOCK_SIZE; } // write optional plaintext region if ( FBin.PlaintextOffset() > 0 ) { assert(FBin.PlaintextOffset() <= FBin.Size()); memcpy(p, FBin.RoData(), FBin.PlaintextOffset()); p += FBin.PlaintextOffset(); } ui32_t ct_size = FBin.Size() - FBin.PlaintextOffset(); ui32_t diff = ct_size % CBC_BLOCK_SIZE; ui32_t block_size = ct_size - diff; assert((block_size % CBC_BLOCK_SIZE) == 0); // encrypt the ciphertext region essence data if ( ASDCP_SUCCESS(result) ) { result = Ctx->EncryptBlock(FBin.RoData() + FBin.PlaintextOffset(), p, block_size); p += block_size; } // construct and encrypt the padding if ( ASDCP_SUCCESS(result) ) { byte_t the_last_block[CBC_BLOCK_SIZE]; if ( diff > 0 ) memcpy(the_last_block, FBin.RoData() + FBin.PlaintextOffset() + block_size, diff); for (ui32_t i = 0; diff < CBC_BLOCK_SIZE; diff++, i++ ) the_last_block[diff] = i; result = Ctx->EncryptBlock(the_last_block, p, CBC_BLOCK_SIZE); } if ( ASDCP_SUCCESS(result) ) FBout.Size(calc_esv_length(FBin.Size(), FBin.PlaintextOffset())); return result; }
ASDCP::Result_t ASDCP::RawEssenceType(const std::string& filename, EssenceType_t& type) { type = ESS_UNKNOWN; ASDCP::FrameBuffer FB; Kumu::FileReader Reader; ASDCP::Wav::SimpleWaveHeader WavHeader; ASDCP::RF64::SimpleRF64Header RF64Header; ASDCP::AIFF::SimpleAIFFHeader AIFFHeader; Kumu::XMLElement TmpElement("Tmp"); ui32_t data_offset; ui32_t read_count; Result_t result = FB.Capacity(Wav::MaxWavHeader); // using Wav max because everything else is much smaller if ( Kumu::PathIsFile(filename) ) { result = Reader.OpenRead(filename); if ( ASDCP_SUCCESS(result) ) { result = Reader.Read(FB.Data(), FB.Capacity(), &read_count); Reader.Close(); } if ( ASDCP_SUCCESS(result) ) { const byte_t* p = FB.RoData(); FB.Size(read_count); ui32_t i = 0; while ( p[i] == 0 ) i++; if ( i > 1 && p[i] == 1 && (p[i+1] == ASDCP::MPEG2::SEQ_START || p[i+1] == ASDCP::MPEG2::PIC_START) ) { type = ESS_MPEG2_VES; } else if ( memcmp(FB.RoData(), ASDCP::JP2K::Magic, sizeof(ASDCP::JP2K::Magic)) == 0 ) { type = ESS_JPEG_2000; } else if ( ASDCP_SUCCESS(WavHeader.ReadFromBuffer(FB.RoData(), read_count, &data_offset)) ) { switch ( WavHeader.samplespersec ) { case 48000: type = ESS_PCM_24b_48k; break; case 96000: type = ESS_PCM_24b_96k; break; default: return RESULT_FORMAT; } } else if ( ASDCP_SUCCESS(RF64Header.ReadFromBuffer(FB.RoData(), read_count, &data_offset)) ) { switch ( RF64Header.samplespersec ) { case 48000: type = ESS_PCM_24b_48k; break; case 96000: type = ESS_PCM_24b_96k; break; default: return RESULT_FORMAT; } } else if ( ASDCP_SUCCESS(AIFFHeader.ReadFromBuffer(FB.RoData(), read_count, &data_offset)) ) { type = ESS_PCM_24b_48k; } else if ( string_is_xml(FB) ) { type = ESS_TIMED_TEXT; } else if ( ASDCP::ATMOS::IsDolbyAtmos(filename) ) { type = ESS_DCDATA_DOLBY_ATMOS; } } } else if ( Kumu::PathIsDirectory(filename) ) { char next_file[Kumu::MaxFilePath]; Kumu::DirScanner Scanner; Result_t result = Scanner.Open(filename); if ( ASDCP_SUCCESS(result) ) { while ( ASDCP_SUCCESS(Scanner.GetNext(next_file)) ) { if ( next_file[0] == '.' ) // no hidden files or internal links continue; result = Reader.OpenRead(Kumu::PathJoin(filename, next_file)); if ( ASDCP_SUCCESS(result) ) { result = Reader.Read(FB.Data(), FB.Capacity(), &read_count); Reader.Close(); } if ( ASDCP_SUCCESS(result) ) { if ( memcmp(FB.RoData(), ASDCP::JP2K::Magic, sizeof(ASDCP::JP2K::Magic)) == 0 ) { type = ESS_JPEG_2000; } else if ( ASDCP_SUCCESS(WavHeader.ReadFromBuffer(FB.RoData(), read_count, &data_offset)) ) { switch ( WavHeader.samplespersec ) { case 48000: type = ESS_PCM_24b_48k; break; case 96000: type = ESS_PCM_24b_96k; break; default: return RESULT_FORMAT; } } else if ( ASDCP_SUCCESS(RF64Header.ReadFromBuffer(FB.RoData(), read_count, &data_offset)) ) { switch ( RF64Header.samplespersec ) { case 48000: type = ESS_PCM_24b_48k; break; case 96000: type = ESS_PCM_24b_96k; break; default: return RESULT_FORMAT; } } else if ( ASDCP::ATMOS::IsDolbyAtmos(Kumu::PathJoin(filename, next_file)) ) { type = ESS_DCDATA_DOLBY_ATMOS; } else { type = ESS_DCDATA_UNKNOWN; } } break; } } } return result; }