Skip to content
7 changes: 5 additions & 2 deletions player/src/common/defines.h
Original file line number Diff line number Diff line change
Expand Up @@ -81,11 +81,14 @@
//! Folder for certificates
#define CERTIFICATES_FOLDER "KnownCerts"

//! Folder for PKCS12 keys
#define KEYSTORE_FOLDER "KeyStore"

//! Binary format filter
#define BINARY_FORMAT QObject::tr("Binary format (*.der)")
#define CERT_FORMAT_EXTENSIONS QObject::tr("Binary format (*.der;*.cer;*.crt;*.pem)")

//! Binary filter.
#define BINARY_FILTER "*.der"
#define CERT_FILE_EXTENSIONS "*.der;*.cer;*.crt;*.pfx;*.pem"

//! Binary extention
#define BINARY_EXT ".der"
Expand Down
6 changes: 6 additions & 0 deletions player/src/common/segmentInfo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
************************************************************************************/

#include "segmentInfo.h"
#include "certificateStorage.h"

SegmentInfo::SegmentInfo(QString file_name /*= QString()*/)
: m_segment_number(0)
Expand Down Expand Up @@ -57,6 +58,11 @@ void SegmentInfo::read(MediaHeaderBox*box)
if (m_videoTimescale == 0) m_videoTimescale = box->getTimeScale();
}

void SegmentInfo::read(ProtectionSystemSpecificHeaderBox* box)
{
m_key = CertificateStorage(KEYSTORE_FOLDER).decryptKey(box);
}

void SegmentInfo::readAfIdentificationBox(AFIdentificationBox *box)
{
m_duration = box->getDuration() / m_videoTimescale;
Expand Down
3 changes: 3 additions & 0 deletions player/src/common/segmentInfo.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
#include "correctstarttimebox.hpp"
#include "helpers/optional.hpp"
#include "templateTableBoxes.hpp"
#include "protectionSystemSpecificHeaderBox.hpp"

/**
* Class that descibes one MP4 file.
Expand All @@ -72,6 +73,7 @@ class SegmentInfo
void read(CompositionOffsetBox* box);
void read(TrackRunBox* box);
void read(MediaHeaderBox* box);
void read(ProtectionSystemSpecificHeaderBox* box);

//! Returns if a fragment is a Surveillance file.
bool isSurveillanceFragment() const;
Expand Down Expand Up @@ -154,6 +156,7 @@ class SegmentInfo
public:
uint32_t m_currentParserTrackId; ///< track id currently beingparsed
uint32_t m_defaultSampleDuration; ///< default sample duration of last tfhd read in order to pass to trun
HexArray m_key; ///< optional key for decrypting
};

typedef QList<SegmentInfo> SegmentList;
Expand Down
2 changes: 2 additions & 0 deletions player/src/parser/boxFactory.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
#include "trackHeaderBox.hpp"
#include "trackRunBox.hpp"
#include "correctstarttimebox.hpp"
#include "protectionSystemSpecificHeaderBox.hpp"

BoxFactory::BoxFactory()
{
Expand Down Expand Up @@ -134,6 +135,7 @@ BoxFactory::BoxFactory()
registerCreator<TrackHeaderBox>();
registerCreator<TrackRunBox>();
registerCreator<CorrectStartTimeBox>();
registerCreator<ProtectionSystemSpecificHeaderBox>();
}

BoxFactory & BoxFactory::instance()
Expand Down
169 changes: 164 additions & 5 deletions player/src/parser/certificateStorage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,21 @@
#include "certificateStorage.h"

#include <QDir>
#include <qinputdialog.h>
#include <qmessagebox.h>

#include "defines.h"

CertificateStorage::CertificateStorage()
#include <openssl/hpke.h>
#include <openssl/pkcs12.h>
#include <openssl/x509.h>
#include <openssl/err.h>
#include <map>
#include <string>

CertificateStorage::CertificateStorage(const char *folder)
{
m_folder = folder;
update();
}

Expand All @@ -45,9 +55,10 @@ QString CertificateStorage::getCertificateFolder()
{
QString certificates_folder;
#ifdef _WIN32
certificates_folder = QDir::homePath() + WINP_APP_DATA_ROAMING + COMPANY_NAME + "/" + PRODUCT_NAME + "/" + CERTIFICATES_FOLDER;
#else
certificates_folder = QDir::homePath() + "/." + PRODUCT_NAME + "/" + CERTIFICATES_FOLDER;
certificates_folder = QDir::homePath() + WINP_APP_DATA_ROAMING + COMPANY_NAME + "/" + PRODUCT_NAME + "/" + m_folder;
#endif //WIN32
#ifdef UNIX
certificates_folder = QDir::homePath() + "/." + PRODUCT_NAME + "/" + m_folder;
#endif //UNIX

//create it if needed
Expand All @@ -61,7 +72,8 @@ void CertificateStorage::update()
{
m_files.clear();
QDir certificate_dir(getCertificateFolder());
m_files = certificate_dir.entryInfoList(QStringList(BINARY_FILTER), QDir::Files | QDir::Readable, QDir::Name);
auto ext = QString(CERT_FILE_EXTENSIONS).split(';');
m_files = certificate_dir.entryInfoList(ext, QDir::Files | QDir::Readable, QDir::Name);
}

void CertificateStorage::removeCertificate(int index)
Expand Down Expand Up @@ -93,3 +105,150 @@ bool CertificateStorage::isCertificateKnown(const QByteArray& binary_certificate
}
return false;
}

HexArray CertificateStorage::decryptKey(ProtectionSystemSpecificHeaderBox* box)
{
HexArray thumbPrint = box->getCertThumbprint();
HexArray resp;
//
// Lookup cached hashes
// Note: will fail when a file is replaced and encrypted by different key
//
std::map<std::string, bool> files;
auto hexthumb = QByteArray(thumbPrint).toHex().toStdString().c_str();
auto hashes = getCertificateFolder() + "/hashes.txt";
std::string fname;
if (FILE* fd = fopen(hashes.toStdString().c_str(), "r")) {
char line[1024] = {};
while (fgets(line, sizeof(line), fd)) {
auto del = strchr(line, ' ');
size_t hlen = del - line, len = strlen(line);
if (len > 0 && line[len - 1] == '\n') line[len - 1] = 0; // remove trailing newline
files[del + 1] = true;
if (hlen < sizeof(line) && hlen == thumbPrint.size() * 2 && !memcmp(line, hexthumb, hlen)) {
fname = del + 1;
}
}
fclose(fd);
}

EVP_PKEY* pkey = 0;
PKCS12* p12 = 0;
if (fname.empty()) { // update hashes if not found
//
// If unkown cert hash search for a new pkcs12 file
//
if (FILE* fd = fopen(hashes.toStdString().c_str(), "a+")) {
for (auto cIter = m_files.constBegin(); cIter != m_files.constEnd(); ++cIter)
{
if (files.find(cIter->fileName().toStdString()) == files.end()) {
if (auto p12_file = fopen(cIter->absoluteFilePath().toStdString().c_str(), "rb"))
{
if (p12) PKCS12_free(p12);
p12 = d2i_PKCS12_fp(p12_file, 0);
fclose(p12_file);

bool ok{};
QString text = QInputDialog::getText(0, "Password for " + cIter->fileName(),
"Password:", QLineEdit::Normal, "", &ok);

EVP_PKEY* pk = 0;
X509* cert = 0;
if (!PKCS12_parse(p12, text.toStdString().c_str(), &pk, &cert, 0)) {
char buf[256];
fprintf(stdout, "Error parsing PKCS#12 file: %s\n", ERR_error_string(ERR_get_error(), buf));
}
else {
if (cert) {
const EVP_MD* md = EVP_sha256();
uint8_t sig[64] = {};
uint32_t siglen = sizeof(sig);
if (X509_digest(cert, md, sig, &siglen)) {
const char* thumb = QByteArray((char*)sig, siglen).toHex().toStdString().c_str();
fprintf(fd, "%s %s\n", thumb, cIter->fileName().toStdString().c_str());
if (siglen == thumbPrint.size() && !memcmp(thumb, thumbPrint.data(), siglen)) {
fname = cIter->fileName().toStdString();
pkey = pk; // keep key for decryption
pk = 0;
}
}
X509_free(cert);
}
if (pk) EVP_PKEY_free(pk);
}
fclose(p12_file);
}
}
}
fclose(fd);
}
}

if (pkey == 0) {
auto pfx = (getCertificateFolder() + QString("/") + QString(fname.c_str())).toStdString();
if (auto p12_file = fopen(pfx.c_str(), "rb"))
{
auto p12 = d2i_PKCS12_fp(p12_file, 0);

bool ok{};
QString text = QInputDialog::getText(0, "Password for " + QString(fname.c_str()),
"Password:", QLineEdit::Normal, "", &ok);
X509* cert = 0;
if (!PKCS12_parse(p12, text.toStdString().c_str(), &pkey, &cert, 0) || pkey == 0) {
QMessageBox::warning(0, "Decryption", "Error accessing private key.\n Check password.");
return resp;
}
fclose(p12_file);
}
}
if (pkey == 0) {
QMessageBox::warning(0, "Decryption", QString("Error accessing key ") + QString(fname.c_str()));
return resp;
}

auto encryptedKey = box->getEncryptedKey();
switch (box->getEncryptionVersion()) {
case 1: {
char buf[256];
auto ctx = EVP_PKEY_CTX_new(pkey, NULL);
EVP_PKEY_decrypt_init(ctx);
EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_OAEP_PADDING);
if (EVP_PKEY_CTX_set_rsa_oaep_md(ctx, EVP_sha256()) <= 0)
fprintf(stdout, "Error parsing PKCS#12 file: %s\n", ERR_error_string(ERR_get_error(), buf));

uint8_t buffer[1024] = {};
size_t bufsiz = sizeof(buffer);
EVP_PKEY_decrypt(ctx, NULL, &bufsiz, encryptedKey.data(), encryptedKey.size());

if (bufsiz <= sizeof(buffer) && EVP_PKEY_decrypt(ctx, buffer, &bufsiz, encryptedKey.data(), encryptedKey.size())) {
resp = HexArray(buffer, bufsiz);
}
else {
fprintf(stdout, "Error parsing PKCS#12 file: %s\n", ERR_error_string(ERR_get_error(), buf));
}
if (p12) PKCS12_free(p12);
EVP_PKEY_CTX_free(ctx);
if (pkey) EVP_PKEY_free(pkey);
break;
}
case 2: {
OSSL_HPKE_SUITE suite = { box->getHPKE_KEM(), box->getHPKE_KDF(), box->getHPKE_AEAD() };
OSSL_HPKE_CTX* ctx = OSSL_HPKE_CTX_new(OSSL_HPKE_MODE_BASE, suite, OSSL_HPKE_ROLE_RECEIVER, NULL, NULL);

uint8_t buffer[256] = {};
size_t bufsiz = sizeof(buffer);
if (ctx) {
OSSL_HPKE_decap(ctx, box->getSharedSecret().data(), box->getSharedSecret().size(), pkey, box->getInfo().data(), box->getInfo().size());
if (OSSL_HPKE_open(ctx, buffer, &bufsiz, NULL, 0, encryptedKey.data(), encryptedKey.size())) {
resp = HexArray(buffer, bufsiz);
}
}
OSSL_HPKE_CTX_free(ctx);
break;
}
default:
QMessageBox::warning(0, "Decryption", QString("Encryption version not supported"));
break;
}
return resp;
}
10 changes: 8 additions & 2 deletions player/src/parser/certificateStorage.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,17 +29,19 @@
#define CERTIFICATESTORAGE_H

#include <QFileInfo>
#include "helpers/hexarray.hpp"
#include "ProtectionSystemSpecificHeaderBox.hpp"

//! Certificate storage.
class CertificateStorage
{
public:
CertificateStorage();
CertificateStorage(const char *folder);

~CertificateStorage();

//! Get folders with certificate.
static QString getCertificateFolder();
QString getCertificateFolder();

//! Update certificate storage.
void update();
Expand All @@ -53,9 +55,13 @@ class CertificateStorage
//! Check if certificate known.
bool isCertificateKnown(const QByteArray& binary_certificate);

//! Try to load pkcs12 file and decrypt the key
HexArray decryptKey(ProtectionSystemSpecificHeaderBox* box);

private:
//! List of files
QFileInfoList m_files;
QString m_folder;
};

#endif // CERTIFICATESTORAGE_H
4 changes: 3 additions & 1 deletion player/src/parser/consistencyChecker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
#include "trackHeaderBox.hpp"
#include "trackRunBox.hpp"
#include "correctstarttimebox.hpp"
#include "protectionSystemSpecificHeaderBox.hpp"

ConsistencyChecker::ConsistencyChecker()
: m_current_box(nullptr)
Expand Down Expand Up @@ -89,7 +90,8 @@ ConsistencyChecker::ConsistencyChecker()
this->zeroOrOneCheck<MetaBox>();
this->zeroOrOneCheck<MovieExtendsBox>();
this->zeroOrOneCheck<UserDataBox>();
});
this->zeroOrOneCheck<ProtectionSystemSpecificHeaderBox>();
});

registerChecker<TrackBox>( [this] () {
this->exactlyOneCheck<MediaBox>();
Expand Down
42 changes: 42 additions & 0 deletions player/src/parser/helpers/hexarray.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/************************************************************************************
* Copyright (c) 2013 ONVIF.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of ONVIF nor the names of its contributors may be
* used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL ONVIF BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
************************************************************************************/

#ifndef HELPERS_HEXARRAY_H
#define HELPERS_HEXARRAY_H

#include <vector>
//! Union describing uint24_t data type.
class HexArray : public std::vector<uint8_t>
{
public:
HexArray() {}
HexArray(uint8_t *data, size_t size) : std::vector<uint8_t>(data, data + size) {}

};


#endif // HELPERS_UINT24_H
7 changes: 7 additions & 0 deletions player/src/parser/helpers/property.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
#include "endian.hpp"
#include "uint24.hpp"
#include "optional.hpp"
#include "hexarray.hpp"

//! Helper class, allowing to build trees of string values. Used for exporting box properties in human readable format. Uses curiosly recurring template pattern.
class Property CC_CXX11_FINAL
Expand Down Expand Up @@ -114,6 +115,12 @@ class Property CC_CXX11_FINAL
m_string = (QString)value;
}

//! Converts HexArray to strings.
inline void convert(HexArray value)
{
m_string = QByteArray((char*)value.data(), value.size()).toHex();
}

//! Converts DateTime values to strings.
inline void convert(QDateTime value)
{
Expand Down
Loading