Skip to content

Commit

Permalink
Handle ASiC-S XAdES signatures (#543)
Browse files Browse the repository at this point in the history
* Handle ASiC-S XAdES signatures

IB-7593

Signed-off-by: Raul Metsma <[email protected]>

* Fix crash of certain subject/issuer names

IB-7593

Signed-off-by: Raul Metsma <[email protected]>

---------

Signed-off-by: Raul Metsma <[email protected]>
  • Loading branch information
metsma authored Sep 15, 2023
1 parent e66b16a commit 95c7c68
Show file tree
Hide file tree
Showing 10 changed files with 128 additions and 206 deletions.
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

0 comments on commit 95c7c68

Please sign in to comment.