libdigidocpp
BDoc.cpp
Go to the documentation of this file.
1 /*
2  * libdigidocpp
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2.1 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17  *
18  */
19 
20 #include "BDoc.h"
21 
22 #include "log.h"
23 #include "Conf.h"
24 #include "Document.h"
25 #include "SignatureTM.h"
26 #include "crypto/Digest.h"
27 #include "util/File.h"
28 #include "io/ZipSerialize.h"
29 #include "xml/OpenDocument_manifest.hxx"
30 
31 #include <fstream>
32 #include <set>
33 
34 namespace digidoc
35 {
37 {
38 public:
39  BDocPrivate(): version("1.0") {}
40 
41  static const std::string MIMETYPE_PREFIX;
42  static const std::string MANIFEST_NAMESPACE;
43 
44  std::string version;
45  std::vector<Document> documents;
46  std::vector<Signature*> signatures;
47  std::auto_ptr<digidoc::ISerialize> serializer;
48 };
49 }
50 
54 const std::string digidoc::BDocPrivate::MIMETYPE_PREFIX = "application/vnd.bdoc-";
55 
59 const std::string digidoc::BDocPrivate::MANIFEST_NAMESPACE = "urn:oasis:names:tc:opendocument:xmlns:manifest:1.0";
60 
65  : d(new BDocPrivate)
66 {
67 }
68 
73 digidoc::BDoc::BDoc(const std::string &path) throw(IOException, BDocException)
74  : d(new BDocPrivate)
75 {
76  std::auto_ptr<ISerialize> serializer(new ZipSerialize(path));
77  readFrom(serializer);
78 }
79 
87 digidoc::BDoc::BDoc(std::auto_ptr<ISerialize> serializer) throw(IOException, BDocException)
88  : d(new BDocPrivate)
89 {
90  readFrom(serializer);
91 }
92 
97 {
98  // Release all signatures.
99  while(!d->signatures.empty())
100  {
101  delete (d->signatures.back());
102  d->signatures.pop_back();
103  }
104  delete d;
105 }
106 
116 void digidoc::BDoc::readFrom(std::auto_ptr<ISerialize> serializer) throw(IOException, BDocException)
117 {
118  DEBUG("BDoc::readFrom(serializer = %p '%s')", serializer.get(), serializer->getPath().c_str());
119 
120  // Set container initializer.
121  d->serializer = serializer;
122 
123  // Extract container to temporary directory.
124  std::string tmpPath = d->serializer->extract();
125 
126  // Read and check mimetype.
127  readMimetype(tmpPath);
128 
129  // Parse manifest, load documents and signatures, also check container file listing.
130  parseManifestAndLoadFiles(tmpPath);
131 }
132 
142 {
143  // Check that serializer is provided.
144  if(!d->serializer.get())
145  THROW_BDOCEXCEPTION("You can not use save() method if you didn't open a container with readFrom() method. Use saveTo() method in case of new a BDoc container.");
146 
147  // Check that at least one document is in container.
148  if( d->documents.empty() )
149  THROW_BDOCEXCEPTION("Can not save, BDoc container is empty.");
150 
151  // Create new container.
152  d->serializer->create();
153 
154  // Create mimetype file and add it to container.
155  d->serializer->addFile("mimetype", createMimetype());
156 
157  // Create manifest file and add it to container.
158  // NB! Don't change the order of signatures in list after manifest is created,
159  // otherwise manifest has incorrect signature mimetypes.
160  d->serializer->addFile("META-INF/manifest.xml", createManifest());
161 
162  // Add all documents to container.
163  for(std::vector<Document>::const_iterator iter = d->documents.begin(); iter != d->documents.end(); iter++)
164  {
165  d->serializer->addFile(iter->getFileName(), iter->getFilePath());
166  std::cout << "added file " << iter->getFileName() << " to documents." << std::endl;
167  }
168 
169  // Add all signatures to container.
170  unsigned int i = 0;
171  for(std::vector<Signature*>::const_iterator iter = d->signatures.begin(); iter != d->signatures.end(); iter++)
172  {
173  d->serializer->addFile(Log::format("META-INF/signature%u.xml", i++), (*iter)->saveToXml());
174  }
175 
176  // Save container.
177  d->serializer->save();
178 }
179 
189 void digidoc::BDoc::saveTo(const std::string &path) throw(IOException, BDocException)
190 {
191  d->serializer.reset(new digidoc::ZipSerialize(path));
192  save();
193 }
194 
205 {
206  if(!d->signatures.empty())
207  THROW_BDOCEXCEPTION("Can not add document to container which has signatures, remove all signatures before adding new document.");
208 
209  // Check that document file exists.
210  if(!util::File::fileExists(document.getFilePath()))
211  THROW_BDOCEXCEPTION("Document file '%s' does not exist.", document.getFilePath().c_str());
212 
213  // Check if document with same filename exists.
214  for(std::vector<Document>::const_iterator iter = d->documents.begin(); iter != d->documents.end(); iter++)
215  {
216  if(document.getFileName().compare(iter->getFileName()) == 0)
217  THROW_BDOCEXCEPTION("Document with same file name '%s' already exists '%s'.", document.getFileName().c_str(), iter->getFileName().c_str());
218  }
219 
220  d->documents.push_back(document);
221 }
222 
231 {
232  if(id >= d->documents.size())
233  THROW_BDOCEXCEPTION("Incorrect document id %u, there are only %u documents in container.", id, d->documents.size());
234  return d->documents[id];
235 }
236 
245 void digidoc::BDoc::removeDocument(unsigned int id) throw(BDocException)
246 {
247  if(!d->signatures.empty())
248  THROW_BDOCEXCEPTION("Can not remove document from container which has signatures, remove all signatures before removing document.");
249 
250  if(d->documents.size() > id)
251  d->documents.erase(d->documents.begin() + id);
252  else
253  THROW_BDOCEXCEPTION("Incorrect document id %u, there are only %u documents in container.", id, d->documents.size());
254 }
255 
259 unsigned int digidoc::BDoc::documentCount() const
260 {
261  return (unsigned int)d->documents.size();
262 }
263 
268 {
269  return BDocType;
270 }
271 
278 void digidoc::BDoc::addSignature(const std::vector<unsigned char> &signature) throw(BDocException)
279 {
280  addSignature(signature, TM);
281 }
282 
289 void digidoc::BDoc::addSignature(const std::vector<unsigned char> &signature, Type profile) throw(BDocException)
290 {
291  if(signature.empty())
292  THROW_BDOCEXCEPTION("Empty signature object, can not add signature.");
293  if(d->documents.empty())
294  THROW_BDOCEXCEPTION("No documents in container, can not add signature.");
295 
296  std::string fileName = util::File::tempFileName();
297  std::ofstream ofs(util::File::encodeName(fileName).c_str());
298  ofs << "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\" ?>\n";
299  for(std::vector<unsigned char>::const_iterator i = signature.begin(); i != signature.end(); ++i)
300  ofs << *i;
301  ofs.close();
302  try
303  {
304  switch(profile)
305  {
306  case TM:
307  d->signatures.push_back(new SignatureTM(fileName, *this));
308  break;
309  case BES:
310  d->signatures.push_back(new SignatureBES(fileName, *this));
311  break;
312  }
313  }
314  catch(const Exception &)
315  {
316  THROW_BDOCEXCEPTION("Failed to add signature.");
317  }
318 }
319 
327 {
328  if(!signature)
329  THROW_BDOCEXCEPTION("Empty signature object, can not add signature.");
330  if(d->documents.empty())
331  THROW_BDOCEXCEPTION("No documents in container, can not add signature.");
332  d->signatures.push_back(signature);
333 }
334 
343 {
344  if( id >= d->signatures.size() )
345  THROW_BDOCEXCEPTION("Incorrect signature id %u, there are only %u signatures in container.", id, d->signatures.size());
346  return d->signatures[id];
347 }
348 
355 void digidoc::BDoc::removeSignature(unsigned int id) throw(BDocException)
356 {
357  if(d->signatures.size() > id)
358  {
359  std::vector<Signature*>::iterator it = (d->signatures.begin() + id);
360  delete *it;
361  d->signatures.erase(it);
362  }
363  else
364  THROW_BDOCEXCEPTION("Incorrect signature id %u, there are only %u signatures in container.", id, d->signatures.size());
365 }
366 
370 unsigned int digidoc::BDoc::signatureCount() const
371 {
372  return (unsigned int)d->signatures.size();
373 }
374 
380 void digidoc::BDoc::setVersion(const std::string& version)
381 {
382  d->version = version;
383 }
384 
388 std::string digidoc::BDoc::getMimeType() const
389 {
390  return BDocPrivate::MIMETYPE_PREFIX + d->version;
391 }
392 
400 {
401  DEBUG("BDoc::createMimetype()");
402 
403  // Create mimetype file.
404  std::string fileName = util::File::tempFileName();
405  std::ofstream ofs(util::File::encodeName(fileName).c_str());
406  ofs << getMimeType();
407  ofs.close();
408 
409  if(ofs.fail())
410  THROW_IOEXCEPTION("Failed to create mimetype file to '%s'.", fileName.c_str());
411 
412  return fileName;
413 }
414 
426 {
427  DEBUG("digidoc::BDoc::createManifest()");
428 
429  std::string fileName = util::File::tempFileName();
430 
431  try
432  {
433  manifest::Manifest manifest;
434 
435  // Add container file entry with mimetype.
436  manifest.file_entry().push_back(manifest::File_entry("/", getMimeType()));
437 
438  // Add documents with mimetypes.
439  for(std::vector<Document>::const_iterator iter = d->documents.begin(); iter != d->documents.end(); iter++)
440  {
441  manifest::File_entry fileEntry(iter->getFileName(), iter->getMediaType());
442  manifest.file_entry().push_back(fileEntry);
443  }
444 
445  // Add signatures with mimetypes.
446  unsigned int i = 0;
447  for(std::vector<Signature*>::const_iterator iter = d->signatures.begin(); iter != d->signatures.end(); iter++)
448  {
449  manifest::File_entry fileEntry(Log::format("META-INF/signature%u.xml", i++), (*iter)->getMediaType());
450  manifest.file_entry().push_back(fileEntry);
451  }
452 
453  // Serialize XML to file.
454  xml_schema::NamespaceInfomap map;
455  map["manifest"].name = BDocPrivate::MANIFEST_NAMESPACE;
456  DEBUG("Serializing manifest XML to '%s'", fileName.c_str());
457  // all XML data must be in UTF-8
458  std::ofstream ofs(digidoc::util::File::encodeName(fileName).c_str());
459  manifest::manifest(ofs, manifest, map, "", xml_schema::Flags::dont_initialize);
460  ofs.close();
461 
462  if(ofs.fail())
463  THROW_IOEXCEPTION("Failed to create manifest XML file to '%s'.", fileName.c_str());
464  }
465  catch(const xml_schema::Exception& e)
466  {
467  std::ostringstream oss;
468  oss << e;
469  THROW_IOEXCEPTION("Failed to create manifest XML file. Error: %s", oss.str().c_str());
470  }
471 
472  return fileName;
473 }
474 
482 void digidoc::BDoc::readMimetype(const std::string &path) throw(IOException, BDocException)
483 {
484  DEBUG("BDoc::readMimetype(path = '%s')", path.c_str());
485  // Read mimetype from file.
486  std::string fileName = util::File::path(path, "mimetype");
487  std::ifstream ifs(util::File::encodeName(fileName).c_str(), std::ios::binary);
488 
489  unsigned char bom[] = { 0, 0, 0 };
490  ifs.read((char*)bom, sizeof(bom));
491  // Contains UTF-16 BOM
492  if((bom[0] == 0xFF && bom[1] == 0xEF) ||
493  (bom[0] == 0xEF && bom[1] == 0xFF))
494  THROW_IOEXCEPTION("Mimetype file must be UTF-8 format.", fileName.c_str());
495  // does not contain UTF-8 BOM reset pos
496  if(!(bom[0] == 0xEF && bom[1] == 0xBB && bom[2] == 0xBF))
497  ifs.seekg(0, std::ios::beg);
498 
499  std::string mimetype;
500  ifs >> mimetype;
501  ifs.close();
502 
503  if(ifs.fail())
504  THROW_IOEXCEPTION("Failed to read mimetype file from '%s'.", fileName.c_str());
505 
506  // Check that mimetype version is correct.
507  DEBUG("mimetype = '%s'", mimetype.c_str());
508  if(getMimeType() != mimetype)
509  THROW_BDOCEXCEPTION("Incorrect mimetype '%s', expecting '%s'", mimetype.c_str(), getMimeType().c_str());
510 }
511 
524 {
525  DEBUG("BDoc::readManifest(path = '%s')", path.c_str());
526 
527  try
528  {
529  // Parse manifest file.
530  std::string fileName = util::File::path(path, "META-INF/manifest.xml");
531  xml_schema::Properties properties;
532  properties.schema_location(BDocPrivate::MANIFEST_NAMESPACE, Conf::getInstance()->getManifestXsdPath());
533  std::auto_ptr<manifest::Manifest> manifest(manifest::manifest(fileName, xml_schema::Flags::dont_initialize, properties));
534 
535  // Extract and validate file list from manifest.
536  std::set<std::string> manifestFiles;
537  bool mimetypeChecked = false;
538  for(manifest::Manifest::File_entrySequence::const_iterator iter = manifest->file_entry().begin();
539  iter != manifest->file_entry().end(); iter++)
540  {
541  DEBUG("full_path = '%s', media_type = '%s'", iter->full_path().c_str(), iter->media_type().c_str());
542  // Check container mimetype.
543  if(std::string("/").compare(iter->full_path()) == 0)
544  {
545  if(mimetypeChecked)
546  THROW_BDOCEXCEPTION("Manifest has more than one container media type defined.");
547 
548  if(getMimeType().compare(iter->media_type()) != 0)
549  {
550  THROW_BDOCEXCEPTION("Manifest has incorrect BDCO container media type defined '%s', expecting '%s'.", iter->media_type().c_str(), getMimeType().c_str());
551  }
552 
553  DEBUG("BDOC mimetype OK");
554  mimetypeChecked = true;
555  continue;
556  }
557 
558  // Check that file reference is not defined already and add relative file reference to set.
559  if(manifestFiles.find(iter->full_path()) != manifestFiles.end())
560  THROW_BDOCEXCEPTION("Manifest multiple entries defined for file '%s'.", iter->media_type().c_str());
561 
562 #ifdef __APPLE__
563  // Apple decomposes always utf-8 chars on filesystem, make sure that we compare paths with composed form
564  manifestFiles.insert(digidoc::util::File::decodeName(iter->full_path()));
565 #else
566  manifestFiles.insert(iter->full_path());
567 #endif
568 
569  // Add document to documents list.
570  if(iter->full_path().find_first_of("/") == std::string::npos)
571  {
572  if(!util::File::fileExists(util::File::path(path, iter->full_path())))
573  {
574  THROW_BDOCEXCEPTION("File described in manifest '%s' does not exist in BDOC container.", iter->full_path().c_str());
575  }
576  d->documents.push_back(Document(util::File::fileName(iter->full_path()), util::File::path(path, iter->full_path()), iter->media_type()));
577  continue;
578  }
579 
580  // Add signature to signatures list.
581  DEBUG("%s :: %u", iter->full_path().c_str(), iter->full_path().find_first_of("META-INF/"));
582  std::string signatureFileName = (iter->full_path().substr((iter->full_path().find('/'))+1));
583 
584  if(iter->full_path().find_first_of("META-INF/") == 0)
585  {
586  DEBUG("signature filename :: '%s'", signatureFileName.c_str());
587 
588  if(signatureFileName.find_first_of("/") != std::string::npos)
589  THROW_BDOCEXCEPTION("Unexpected file described in manifest '%s'.", iter->full_path().c_str());
590 
591  if(!util::File::fileExists(util::File::path(util::File::path(path, "META-INF"), signatureFileName)))
592  THROW_BDOCEXCEPTION("File described in manifest '%s' does not exist in BDOC container.", iter->full_path().c_str());
593 
594  std::string signaturePath = util::File::path(util::File::path(path, "META-INF"), signatureFileName);
595 
596  try
597  {
598  if(SignatureBES::MEDIA_TYPE == iter->media_type())
599  d->signatures.push_back(new SignatureBES(signaturePath, *this));
600  else if(SignatureTM::MEDIA_TYPE == iter->media_type())
601  d->signatures.push_back(new SignatureTM(signaturePath, *this));
602  else
603  THROW_BDOCEXCEPTION("Unknown signature media type '%s'.", iter->media_type().c_str());
604  }
605  catch(const SignatureException& e)
606  {
607  THROW_BDOCEXCEPTION_CAUSE(e, "Failed to parse signature '%s', type '%s'.", signaturePath.c_str(), iter->media_type().c_str());
608  }
609 
610  continue;
611  }
612 
613  // Found unexpected file description in manifest.
614  THROW_BDOCEXCEPTION("Unexpected file description found in container manifest.");
615  }
616 
617  if(!mimetypeChecked)
618  THROW_BDOCEXCEPTION("Manifest file does not have BDOC media type described.");
619 
620  // Check that there are no unexpected files in container.
621  std::vector<std::string> containerFiles = util::File::listFiles(path, true, true, true);
622  for(std::vector<std::string>::const_iterator iter = containerFiles.begin(); iter != containerFiles.end(); iter++)
623  {
624 
625  std::string containerFile = *iter;
626  if(std::string("mimetype").compare(containerFile) == 0
627  || std::string("META-INF/manifest.xml").compare(containerFile) == 0)
628  continue;
629 
630  std::replace(containerFile.begin(), containerFile.end(), '\\', '/');
631  if(manifestFiles.find(containerFile) == manifestFiles.end())
632  THROW_BDOCEXCEPTION("File '%s' found in BDOC container is not described in manifest.", containerFile.c_str());
633  }
634  }
635  catch(const xml_schema::Exception& e)
636  {
637  std::ostringstream oss;
638  oss << e;
639  THROW_IOEXCEPTION("Failed to parse manifest XML: %s", oss.str().c_str());
640  }
641 }
642 
650 {
651  sign(signer, TM);
652 }
653 
661 void digidoc::BDoc::sign(Signer* signer, Type profile) throw(BDocException)
662 {
663  if (!signer)
664  THROW_BDOCEXCEPTION("Null pointer in digidoc::BDoc::sign");
665 
666  DEBUG("sign(signer = %p, profile=%d)", signer, profile);
667 
668  // Create signature by type.
669  Signature* signature = NULL;
670  switch(profile)
671  {
672  case BES:
673  signature = new SignatureBES(newSignatureId(), *this);
674  break;
675  case TM:
676  signature = new SignatureTM(newSignatureId(), *this);
677  break;
678  default:
679  THROW_BDOCEXCEPTION("Unknown signature profile: %d", profile);
680  break;
681  }
682 
683  try
684  {
685  // Finalize the signature by calculating signature.
686  signature->sign(signer);
687  }
688  catch(const SignatureException& e)
689  {
690  delete signature;
691  THROW_BDOCEXCEPTION_CAUSE(e, "Failed to sign BDOC container.");
692  }
693  catch(const SignException& e)
694  {
695  delete signature;
696  THROW_BDOCEXCEPTION_CAUSE(e, "Failed to sign BDOC container.");
697  }
698 
699  // Add the created signature to the signatures list.
700  addSignature(signature);
701 }