Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle ASiC-S XAdES signatures #543

Merged
merged 2 commits into from
Sep 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
149 changes: 44 additions & 105 deletions src/ASiC_S.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,12 @@
#include "ASiC_S.h"

#include "SignatureTST.h"
#include "crypto/Digest.h"
#include "SignatureXAdES_LTA.h"
#include "util/File.h"
#include "util/log.h"
#include "util/ZipSerialize.h"

#include <algorithm>
#include <fstream>
#include <sstream>

using namespace digidoc;
Expand All @@ -37,37 +36,58 @@ using namespace std;
* Initialize ASiCS container.
*/
ASiC_S::ASiC_S(): ASiContainer(MIMETYPE_ASIC_S)
{
}
{}

/**
* Opens ASiC-S container from a file
*/
ASiC_S::ASiC_S(const string &path): ASiContainer(MIMETYPE_ASIC_S)
{
auto z = load(path, false, {MIMETYPE_ASIC_S});
loadContainer(*z);
}
auto z = load(path, false, {mediaType()});
static const string_view metaInf = "META-INF/";

void ASiC_S::save(const string & /*path*/)
{
THROW("Not implemented.");
}
for(const string &file: z->list())
{
if(file == "mimetype" ||
(metaInf.size() < file.size() && file.compare(0, metaInf.size(), metaInf) == 0))
{
if(file == "META-INF/timestamp.tst")
{
if(!signatures().empty())
THROW("Can not add signature to ASiC-S container which already contains a signature.");
stringstream data;
z->extract(file, data);
addSignature(make_unique<SignatureTST>(data, this));
}
if(file == "META-INF/signatures.xml")
{
if(!signatures().empty())
THROW("Can not add signature to ASiC-S container which already contains a signature.");
stringstream data;
z->extract(file, data);
addSignature(make_unique<SignatureXAdES_LTA>(data, this, true));
}
continue;
}

void ASiC_S::addDataFile(const string &path, const string &mediaType)
{
if(!dataFiles().empty())
THROW("Can not add document to ASiC-S container which already contains a document.");

ASiContainer::addDataFile(path, mediaType);
const auto directory = File::directory(file);
if(directory.empty() || directory == "/" || directory == "./")
{
if(!dataFiles().empty())
THROW("Can not add document to ASiC-S container which already contains a document.");
addDataFile(dataStream(file, *z), file, "application/octet-stream");
}
}

if(dataFiles().empty())
THROW("ASiC-S container does not contain any data objects.");
if(signatures().empty())
THROW("ASiC-S container does not contain any signatures.");
}

void ASiC_S::addDataFile(unique_ptr<istream> is, const string &fileName, const string &mediaType)
void ASiC_S::save(const string & /*path*/)
{
if(!dataFiles().empty())
THROW("Can not add document to ASiC-S container which already contains a document.");

ASiContainer::addDataFile(move(is), fileName, mediaType);
THROW("Not implemented.");
}

unique_ptr<Container> ASiC_S::createInternal(const string & /*path*/)
Expand All @@ -83,57 +103,11 @@ void ASiC_S::addAdESSignature(istream & /*signature*/)
unique_ptr<Container> ASiC_S::openInternal(const string &path)
{
if (!isContainerSimpleFormat(path))
return nullptr;
return {};
DEBUG("ASiC_S::openInternal(%s)", path.c_str());
return unique_ptr<Container>(new ASiC_S(path));
}

void ASiC_S::extractTimestamp(const ZipSerialize &z)
{
addSignature(make_unique<SignatureTST>(dataStream("META-INF/timestamp.tst", z), this));
}

/**
* Load container (datafile and timestamp).
*
* @param z Zip stream.
* @param list List of files contained in the container.
* @throws IOException exception is thrown if the manifest.xml file parsing failed.
* @throws ContainerException
*/
void ASiC_S::loadContainer(const ZipSerialize &z)
{
DEBUG("ASiC_S::loadFileAndTimestamp()");
const string metaInf = "META-INF/";
const vector<string> &list = z.list();
int files = 0;

for(const string &file: list)
{
if(file == "mimetype" ||
file.substr(0, metaInf.size()) == metaInf)
continue;

const auto directory = File::directory(file);
if(directory.empty() || directory == "/" || directory == "./")
{
if(files > 0)
{
THROW("ASiC-S container contains more than one data objects.");
}
ASiContainer::addDataFile(dataStream(file, z), file, "application/octet-stream");
files++;
}
}

if(files == 0)
{
THROW("ASiC-S container does not contain any data objects.");
}

extractTimestamp(z);
}

Signature* ASiC_S::prepareSignature(Signer * /*signer*/)
{
THROW("Not implemented.");
Expand All @@ -144,31 +118,6 @@ Signature *ASiC_S::sign(Signer * /*signer*/)
THROW("Not implemented.");
}


bool ASiC_S::isTimestampedASiC_S(const vector<string> &list)
{
DEBUG("isTimestampedASiC_S()");
bool isASiCS = false;

auto dataFiles = 0;
auto hasTimestamp = false;

// container has only one file in root folder and has a timestamp
for(const string &file: list)
{
const auto directory = File::directory(file);
if(directory.empty() || directory == "/" || directory == "./")
dataFiles++;
if(file == "META-INF/timestamp.tst")
hasTimestamp = true;
}

isASiCS = hasTimestamp && (dataFiles == 1);

DEBUG("ASiCS Container: %s", isASiCS ? "yes" : "no");
return isASiCS;
}

/**
* Detect ASiC format based on file extentions, mimetype or zip contents.<br/>
* Container format is simple (ASiC-S) or extended (ASiC-E).
Expand All @@ -185,26 +134,16 @@ bool ASiC_S::isContainerSimpleFormat(const string &path)
return false;
if(extension == ASICS_EXTENSION || extension == ASICS_EXTENSION_ABBR)
return true;

DEBUG("Check if ASiC/zip containter");
try
{
ZipSerialize z(path, false);
vector<string> list = z.list();
if(find(list.begin(), list.end(), "mimetype") != list.end())
{
stringstream iss;
z.extract("mimetype", iss);
if(readMimetype(iss) == MIMETYPE_ASIC_S)
return true;
}
if(isTimestampedASiC_S(list))
return true;
return !list.empty() && list.front() == "mimetype" && readMimetype(z) == MIMETYPE_ASIC_S;
}
catch(const Exception &)
{
// Ignore the exception: not ASiC/zip document
}

return false;
}
9 changes: 1 addition & 8 deletions src/ASiC_S.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,6 @@ namespace digidoc
public:
void save(const std::string &path = {}) override;

void addDataFile(const std::string &path, const std::string &mediaType) override;
void addDataFile(std::unique_ptr<std::istream> is, const std::string &fileName, const std::string &mediaType) override;

void addAdESSignature(std::istream &sigdata) override;
Signature* prepareSignature(Signer *signer) override;
Signature* sign(Signer* signer) override;
Expand All @@ -50,11 +47,7 @@ namespace digidoc
ASiC_S();
ASiC_S(const std::string &path);
DISABLE_COPY(ASiC_S);

void extractTimestamp(const ZipSerialize &z);
void loadContainer(const ZipSerialize &z);


static bool isContainerSimpleFormat(const std::string &path);
static bool isTimestampedASiC_S(const std::vector<std::string> &list);
};
}
46 changes: 23 additions & 23 deletions src/ASiContainer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -79,29 +79,27 @@ ASiContainer::ASiContainer(const string &mimetype)
unique_ptr<ZipSerialize> ASiContainer::load(const string &path, bool mimetypeRequired, const set<string> &supported)
{
DEBUG("ASiContainer::ASiContainer(path = '%s')", path.c_str());
unique_ptr<ZipSerialize> z = make_unique<ZipSerialize>(d->path = path, false);
auto z = make_unique<ZipSerialize>(d->path = path, false);

vector<string> list = z->list();
if(list.empty())
THROW("Failed to parse container");

for(const string &file: list)
d->properties[file] = z->properties(file);

if(mimetypeRequired && list[0] != "mimetype")
if(mimetypeRequired && list.front() != "mimetype")
THROW("required mimetype not found");

// ETSI TS 102 918: mimetype has to be the first in the archive
if(list.front() == "mimetype")
{
stringstream data;
z->extract(list.front(), data);
d->mimetype = readMimetype(data);
d->mimetype = readMimetype(*z);
DEBUG("mimetype = '%s'", d->mimetype.c_str());
if(supported.find(d->mimetype) == supported.cend())
THROW("Incorrect mimetype '%s'", d->mimetype.c_str());
}

for(const string &file: list)
d->properties[file] = z->properties(file);

return z;
}

Expand Down Expand Up @@ -154,7 +152,7 @@ unique_ptr<iostream> ASiContainer::dataStream(const string &path, const ZipSeria
{
unique_ptr<iostream> data;
if(d->properties[path].size > MAX_MEM_FILE)
data = make_unique<fstream>(File::encodeName(File::tempFileName()).c_str(), fstream::in|fstream::out|fstream::binary|fstream::trunc);
data = make_unique<fstream>(File::encodeName(File::tempFileName()), fstream::in|fstream::out|fstream::binary|fstream::trunc);
else
data = make_unique<stringstream>();
z.extract(path, *data);
Expand All @@ -180,28 +178,28 @@ void ASiContainer::addDataFile(const string &path, const string &mediaType)

ZipSerialize::Properties prop { appInfo(), File::modifiedTime(path), File::fileSize(path) };
bool useTempFile = prop.size > MAX_MEM_FILE;
zproperty(File::fileName(path), move(prop));
zproperty(File::fileName(path), std::move(prop));
unique_ptr<istream> is;
if(useTempFile)
{
is = make_unique<ifstream>(File::encodeName(path).c_str(), ifstream::binary);
is = make_unique<ifstream>(File::encodeName(path), ifstream::binary);
}
else
{
stringstream *data = new stringstream;
if(ifstream file(File::encodeName(path).c_str(), ifstream::binary); file)
auto data = make_unique<stringstream>();
if(ifstream file{File::encodeName(path), ifstream::binary})
*data << file.rdbuf();
is.reset(data);
is = std::move(data);
}
addDataFilePrivate(move(is), fileName, mediaType);
addDataFilePrivate(std::move(is), fileName, mediaType);
}

void ASiContainer::addDataFile(unique_ptr<istream> is, const string &fileName, const string &mediaType)
{
addDataFileChecks(fileName, mediaType);
if(fileName.find_last_of("/\\") != string::npos)
THROW("Document file '%s' cannot contain directory path.", fileName.c_str());
addDataFilePrivate(move(is), fileName, mediaType);
addDataFilePrivate(std::move(is), fileName, mediaType);
}

void ASiContainer::addDataFileChecks(const string &fileName, const string &mediaType)
Expand All @@ -218,7 +216,7 @@ void ASiContainer::addDataFileChecks(const string &fileName, const string &media

void ASiContainer::addDataFilePrivate(unique_ptr<istream> is, const string &fileName, const string &mediaType)
{
d->documents.push_back(new DataFilePrivate(move(is), fileName, mediaType));
d->documents.push_back(new DataFilePrivate(std::move(is), fileName, mediaType));
}

/**
Expand All @@ -235,7 +233,7 @@ void ASiContainer::removeDataFile(unsigned int id)
THROW("Can not remove document from container which has signatures, remove all signatures before removing document.");
if(id >= d->documents.size())
THROW("Incorrect document id %u, there are only %zu documents in container.", id, dataFiles().size());
vector<DataFile*>::const_iterator it = (d->documents.cbegin() + id);
auto it = d->documents.cbegin() + id;
delete *it;
d->documents.erase(it);
}
Expand All @@ -256,7 +254,7 @@ void ASiContainer::removeSignature(unsigned int id)
{
if(id >= d->signatures.size())
THROW("Incorrect signature id %u, there are only %zu signatures in container.", id, d->signatures.size());
vector<Signature*>::const_iterator it = (d->signatures.cbegin() + id);
auto it = d->signatures.cbegin() + id;
delete *it;
d->signatures.erase(it);
}
Expand All @@ -280,15 +278,14 @@ string ASiContainer::zpath() const

ZipSerialize::Properties ASiContainer::zproperty(const string &file) const
{
map<string, ZipSerialize::Properties>::const_iterator i = d->properties.find(file);
if(i != d->properties.cend())
if(auto i = d->properties.find(file); i != d->properties.cend())
return i->second;
return d->properties[file] = { appInfo(), time(nullptr), 0 };
}

void ASiContainer::zproperty(const string &file, ZipSerialize::Properties &&prop)
{
d->properties[file] = move(prop);
d->properties[file] = std::move(prop);
}

/**
Expand All @@ -298,9 +295,12 @@ void ASiContainer::zproperty(const string &file, ZipSerialize::Properties &&prop
* @throws IOException exception is thrown if there was error reading mimetype file from disk.
* @throws ContainerException exception is thrown if the parsed mimetype is incorrect.
*/
string ASiContainer::readMimetype(istream &is)
string ASiContainer::readMimetype(const ZipSerialize &z)
{
DEBUG("ASiContainer::readMimetype()");
stringstream is;
z.extract("mimetype", is);

array<unsigned char,3> bom{};
is.read((char*)bom.data(), bom.size());
// Contains UTF-16 BOM
Expand Down
4 changes: 2 additions & 2 deletions src/ASiContainer.h
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,8 @@ namespace digidoc
ZipSerialize::Properties zproperty(const std::string &file) const;
void zproperty(const std::string &file, ZipSerialize::Properties &&prop);

static std::string readMimetype(std::istream &path);
static std::string readMimetype(const ZipSerialize &z);

private:
DISABLE_COPY(ASiContainer);

Expand Down
Loading