/** * Adds document to the container. Documents can be removed from container only * after all signatures are removed. * * @param document a document, which is added to the container. * @throws BDocException exception is thrown if the document path is incorrect or document * with same file name already exists. Also no document can be added if the * container already has one or more signatures. */ void digidoc::BDoc::addDocument(const Document& document) throw(BDocException) { if(signatures.size() == 0) { // Check that document file exists. if(!util::File::fileExists(document.getPath())) { THROW_BDOCEXCEPTION("Document file '%s' does not exist.", document.getPath().c_str()); } // Check if document with same filename exists. std::string documentFileName = util::File::fileName(document.getPath()); for(std::vector<Document>::const_iterator iter = documents.begin(); iter != documents.end(); iter++) { if(documentFileName.compare(util::File::fileName(iter->getPath())) == 0) { THROW_BDOCEXCEPTION("Document with same file name '%s' already exists '%s'.", document.getPath().c_str(), iter->getPath().c_str()); } } documents.push_back(document); } else { THROW_BDOCEXCEPTION("Can not add document to container which has signatures, remove all signatures before adding new document."); } }
/** * Adds signature to the container. * * @param signature signature, which is added to the container. * @throws BDocException throws exception if there are no documents in container. */ void digidoc::BDoc::addSignature(const std::vector<unsigned char> &signature, Type profile) throw(BDocException) { if(signature.empty()) THROW_BDOCEXCEPTION("Empty signature object, can not add signature."); if(d->documents.empty()) THROW_BDOCEXCEPTION("No documents in container, can not add signature."); std::string fileName = util::File::tempFileName(); std::ofstream ofs(util::File::encodeName(fileName).c_str()); ofs << "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\" ?>\n"; for(std::vector<unsigned char>::const_iterator i = signature.begin(); i != signature.end(); ++i) ofs << *i; ofs.close(); try { switch(profile) { case TM: d->signatures.push_back(new SignatureTM(fileName, *this)); break; case BES: d->signatures.push_back(new SignatureBES(fileName, *this)); break; } } catch(const Exception &) { THROW_BDOCEXCEPTION("Failed to add signature."); } }
/** * Adds signature to the container. * * @param signature signature, which is added to the container. * @throws BDocException throws exception if there are no documents in container. */ void digidoc::BDoc::addSignature(Signature* signature) throw(BDocException) { if(!signature) THROW_BDOCEXCEPTION("Empty signature object, can not add signature."); if(d->documents.empty()) THROW_BDOCEXCEPTION("No documents in container, can not add signature."); d->signatures.push_back(signature); }
/** * Removes document from container by document id. Documents can be * removed from container only after all signatures are removed. * * @param id document's id, which will be removed. * @throws BDocException throws exception if the document id is incorrect or there are * one or more signatures. */ void digidoc::BDoc::removeDocument(unsigned int id) throw(BDocException) { if(!d->signatures.empty()) THROW_BDOCEXCEPTION("Can not remove document from container which has signatures, remove all signatures before removing document."); if(d->documents.size() > id) d->documents.erase(d->documents.begin() + id); else THROW_BDOCEXCEPTION("Incorrect document id %u, there are only %u documents in container.", id, d->documents.size()); }
digidoc::BDoc::BDoc(const std::string &path) throw(IOException, BDocException) : d(new BDocPrivate) { THROW_BDOCEXCEPTION("BDOC 1 container is unsupported."); std::auto_ptr<ISerialize> serializer(new ZipSerialize(path)); readFrom(serializer); }
/** * Reads and parses container mimetype. Checks that the mimetype is supported. * * @param path path to container directory. * @throws IOException exception is thrown if there was error reading mimetype file from disk. * @throws BDocException exception is thrown if the parsed mimetype is incorrect. */ void digidoc::BDoc::readMimetype(const std::string &path) throw(IOException, BDocException) { DEBUG("BDoc::readMimetype(path = '%s')", path.c_str()); // Read mimetype from file. std::string fileName = util::File::path(path, "mimetype"); std::ifstream ifs(util::File::encodeName(fileName).c_str(), std::ios::binary); unsigned char bom[] = { 0, 0, 0 }; ifs.read((char*)bom, sizeof(bom)); // Contains UTF-16 BOM if((bom[0] == 0xFF && bom[1] == 0xEF) || (bom[0] == 0xEF && bom[1] == 0xFF)) THROW_IOEXCEPTION("Mimetype file must be UTF-8 format.", fileName.c_str()); // does not contain UTF-8 BOM reset pos if(!(bom[0] == 0xEF && bom[1] == 0xBB && bom[2] == 0xBF)) ifs.seekg(0, std::ios::beg); std::string mimetype; ifs >> mimetype; ifs.close(); if(ifs.fail()) THROW_IOEXCEPTION("Failed to read mimetype file from '%s'.", fileName.c_str()); // Check that mimetype version is correct. DEBUG("mimetype = '%s'", mimetype.c_str()); if(getMimeType() != mimetype) THROW_BDOCEXCEPTION("Incorrect mimetype '%s', expecting '%s'", mimetype.c_str(), getMimeType().c_str()); }
/** * Returns signature referenced by signature id. * * @param id signature id. * @return returns signature referenced by signature id. * @throws BDocException throws exception if the signature id is incorrect. */ const digidoc::Signature* digidoc::BDoc::getSignature(unsigned int id) const throw(BDocException) { if( id >= signatures.size() ) { THROW_BDOCEXCEPTION("Incorrect signature id %u, there are only %u signatures in container.", id, signatures.size()); } return signatures[id]; }
/** * Returns document referenced by document id. * * @param id document id. * @return returns document referenced by document id. * @throws BDocException throws exception if the document id is incorrect. */ digidoc::Document digidoc::BDoc::getDocument(unsigned int id) const throw(BDocException) { if( id >= documents.size() ) { THROW_BDOCEXCEPTION("Incorrect document id %u, there are only %u documents in container.", id, documents.size()); } return documents[id]; }
/** * Removes signature from container by signature id. * * @param id signature's id, which will be removed. * @throws BDocException throws exception if the signature id is incorrect. */ void digidoc::BDoc::removeSignature(unsigned int id) throw(BDocException) { if(d->signatures.size() > id) { std::vector<Signature*>::iterator it = (d->signatures.begin() + id); delete *it; d->signatures.erase(it); } else THROW_BDOCEXCEPTION("Incorrect signature id %u, there are only %u signatures in container.", id, d->signatures.size()); }
/** * Saves the container using the <code>serializer</code> implementation provided in * <code>readFrom()</code> method. * * @throws IOException is thrown if there was a failure saving BDOC container. For example added * document does not exist. * @throws BDocException is thrown if BDoc class is not correctly initialized. */ void digidoc::BDoc::save() throw(IOException, BDocException) { // Check that serializer is provided. if(serializer.get() == NULL) { 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."); } // Check that at least one document is in container. if( documents.empty() ) { THROW_BDOCEXCEPTION("Can not save, BDoc container is empty."); } // Create new container. serializer->create(); // Create mimetype file and add it to container. serializer->addFile("mimetype", createMimetype()); // Create manifest file and add it to container. // NB! Don't change the order of signatures in list after manifest is created, // otherwise manifest has incorrect signature mimetypes. serializer->addFile("META-INF/manifest.xml", createManifest()); // Add all documents to container. for(std::vector<Document>::const_iterator iter = documents.begin(); iter != documents.end(); iter++) { serializer->addFile(util::File::fileName(iter->getPath()), iter->getPath()); } // Add all signatures to container. unsigned int i = 0; for(std::vector<Signature*>::const_iterator iter = signatures.begin(); iter != signatures.end(); iter++) { serializer->addFile(util::String::format("META-INF/signature%u.xml", i++), (*iter)->saveToXml()); } // Save container. serializer->save(); }
/** * Signs all documents in container. * * @param signer signer implementation. * @param profile signature profile (e.g. BES, TM). * @throws BDocException exception is throws if signing the BDCO container failed. */ void digidoc::BDoc::sign(Signer* signer, Type profile) throw(BDocException) { if (!signer) THROW_BDOCEXCEPTION("Null pointer in digidoc::BDoc::sign"); DEBUG("sign(signer = %p, profile=%d)", signer, profile); // Create signature by type. Signature* signature = NULL; switch(profile) { case BES: signature = new SignatureBES(newSignatureId(), *this); break; case TM: signature = new SignatureTM(newSignatureId(), *this); break; default: THROW_BDOCEXCEPTION("Unknown signature profile: %d", profile); break; } try { // Finalize the signature by calculating signature. signature->sign(signer); } catch(const SignatureException& e) { delete signature; THROW_BDOCEXCEPTION_CAUSE(e, "Failed to sign BDOC container."); } catch(const SignException& e) { delete signature; THROW_BDOCEXCEPTION_CAUSE(e, "Failed to sign BDOC container."); } // Add the created signature to the signatures list. addSignature(signature); }
/** * Adds signature to the container. * * @param signature signature, which is added to the container. * @throws BDocException throws exception if there are no documents in container. */ void digidoc::BDoc::addSignature(Signature* signature) throw(BDocException) { assert(signature != NULL); // why did you do this? if(!documents.empty()) { signatures.push_back(signature); } else { THROW_BDOCEXCEPTION("No documents in container, can not add signature."); } }
/** * Reads and parses container mimetype. Checks that the mimetype is supported. * * @param path path to container directory. * @throws IOException exception is thrown if there was error reading mimetype file from disk. * @throws BDocException exception is thrown if the parsed mimetype is incorrect. */ void digidoc::BDoc::readMimetype(std::string path) throw(IOException, BDocException) { DEBUG("BDoc::readMimetype(path = '%s')", path.c_str()); // Read mimetype from file. std::string fileName = util::File::path(path, "mimetype"); std::ifstream ifs(util::File::fstreamName(fileName).c_str()); std::string mimetype; ifs >> mimetype; ifs.close(); if(ifs.fail()) { THROW_IOEXCEPTION("Failed to read mimetype file from '%s'.", fileName.c_str()); } // Check that mimetype version is correct. DEBUG("mimetype = '%s'", mimetype.c_str()); if(getMimeType().compare(mimetype) != 0) { THROW_BDOCEXCEPTION("Incorrect mimetype '%s', expecting '%s'", mimetype.c_str(), getMimeType().c_str()); } }
/** * Signs all documents in container. * * @param signer signer implementation. * @param profile signature profile (e.g. BES, TM). * @throws BDocException exception is throws if signing the BDCO container failed. */ void digidoc::BDoc::sign(Signer* signer, Signature::Type profile) throw(BDocException) { if (signer == NULL) { THROW_BDOCEXCEPTION("Null pointer in digidoc::BDoc::sign"); } DEBUG("sign(signer = 0x%X, profile=%d)", (unsigned int)signer, profile); // Create signature by type. Signature* signature = NULL; if(profile == Signature::BES) { signature = new SignatureBES(*this); } else if(profile == Signature::TM) { signature = new SignatureTM(*this); } else if(profile == Signature::MOBILE) { try { signature = new SignatureMobile( signer->signaturePath(), *this); } catch(const Exception& e) { THROW_BDOCEXCEPTION_CAUSE(e, "MobileSignature"); } addSignature( signature ); return; } else { THROW_BDOCEXCEPTION("Unknown signature profile: %d", profile); } // Calculate digests for the documents and add these to the signature reference. for(std::vector<Document>::iterator iter = documents.begin(); iter != documents.end(); iter++) { try { DEBUG("Adding document '%s', '%s' to the signature references.", iter->getPath().c_str(), iter->getMediaType().c_str()); // URIs must encode non-ASCII characters in the format %HH where HH is the hex representation of the character std::string uri = std::string("/") + digidoc::util::String::toUriFormat(digidoc::util::File::fileName(iter->getPath())); std::auto_ptr<Digest> calc = Digest::create(); std::vector<unsigned char> digest = iter->calcDigest(calc.get()); DEBUGMEM("digest", &digest[0], digest.size()); signature->addReference(uri, calc->getUri(), digest); } catch(const Exception& e) { delete signature; THROW_BDOCEXCEPTION_CAUSE(e, "Failed to calculate digests for document '%s'.", iter->getPath().c_str()); } } try { // Finalize the signature by calculating signature. signature->sign(signer); } catch(const SignatureException& e) { delete signature; THROW_BDOCEXCEPTION_CAUSE(e, "Failed to sign BDOC container."); } catch(const SignException& e) { delete signature; THROW_BDOCEXCEPTION_CAUSE(e, "Failed to sign BDOC container."); } // Add the created signature to the signatures list. addSignature(signature); }
/** * Parses manifest file and checks that files described in manifest exist, also * checks that no extra file do exist that are not described in manifest.xml. * * Note: If non-ascii characters are present in XML data, we depend on the LANG variable to be set properly * (see iconv --list for the list of supported encoding values for libiconv). * * @param path directory on disk of the BDOC container. * @throws IOException exception is thrown if the manifest.xml file parsing failed. * @throws BDocException */ void digidoc::BDoc::parseManifestAndLoadFiles(std::string path) throw(IOException, BDocException) { DEBUG("BDoc::readManifest(path = '%s')", path.c_str()); try { // Parse manifest file. std::string fileName = util::File::path(path, "META-INF/manifest.xml"); xml_schema::Properties properties; properties.schema_location(MANIFEST_NAMESPACE, Conf::getInstance()->getManifestXsdPath()); std::auto_ptr<manifest::Manifest> manifest(manifest::manifest(fileName, xml_schema::Flags::dont_initialize, properties)); // Extract and validate file list from manifest. std::set<std::string> manifestFiles; bool mimetypeChecked = false; for(manifest::Manifest::File_entrySequence::const_iterator iter = manifest->file_entry().begin(); iter != manifest->file_entry().end(); iter++) { DEBUG("full_path = '%s', media_type = '%s'", iter->full_path().c_str(), iter->media_type().c_str()); // Check container mimetype. if(std::string("/").compare(iter->full_path()) == 0) { if(mimetypeChecked) { THROW_BDOCEXCEPTION("Manifest has more than one container media type defined."); } if(getMimeType().compare(iter->media_type()) != 0) { THROW_BDOCEXCEPTION("Manifest has incorrect BDCO container media type defined '%s', expecting '%s'.", iter->media_type().c_str(), getMimeType().c_str()); } DEBUG("BDOC mimetype OK"); mimetypeChecked = true; continue; } // Check that file reference is not defined already and add relative file reference to set. if(manifestFiles.find(iter->full_path()) != manifestFiles.end()) { THROW_BDOCEXCEPTION("Manifest multiple entries defined for file '%s'.", iter->media_type().c_str()); } manifestFiles.insert(iter->full_path()); // Add document to documents list. if(iter->full_path().find_first_of("/") == std::string::npos) { if(!util::File::fileExists(util::File::path(path, iter->full_path()))) { THROW_BDOCEXCEPTION("File described in manifest '%s' does not exist in BDOC container.", iter->full_path().c_str()); } documents.push_back(Document(util::File::path(path, iter->full_path()), iter->media_type())); continue; } // Add signature to signatures list. DEBUG("%s :: %u", iter->full_path().c_str(), iter->full_path().find_first_of("META-INF/")); std::string signatureFileName = (iter->full_path().substr((iter->full_path().find('/'))+1)); if(iter->full_path().find_first_of("META-INF/") == 0) { DEBUG("signature filename :: '%s'", signatureFileName.c_str()); if(signatureFileName.find_first_of("/") != std::string::npos) { THROW_BDOCEXCEPTION("Unexpected file described in manifest '%s'.", iter->full_path().c_str()); } if(!util::File::fileExists(util::File::path(util::File::path(path, "META-INF"), signatureFileName))) { THROW_BDOCEXCEPTION("File described in manifest '%s' does not exist in BDOC container.", iter->full_path().c_str()); } std::string signaturePath = util::File::path(util::File::path(path, "META-INF"), signatureFileName); try { if(SignatureBES::MEDIA_TYPE == iter->media_type()) { signatures.push_back(new SignatureBES(signaturePath, *this)); } else if(SignatureTM::MEDIA_TYPE == iter->media_type()) { signatures.push_back(new SignatureTM(signaturePath, *this)); } else { THROW_BDOCEXCEPTION("Unknown signature media type '%s'.", iter->media_type().c_str()); } } catch(const SignatureException& e) { THROW_BDOCEXCEPTION_CAUSE(e, "Failed to parse signature '%s', type '%s'.", signaturePath.c_str(), iter->media_type().c_str()); } continue; } // Found unexpected file description in manifest. THROW_BDOCEXCEPTION("Unexpected file description found in container manifest."); } if(!mimetypeChecked) { THROW_BDOCEXCEPTION("Manifest file does not have BDOC media type described."); } // Check that there are no unexpected files in container. std::vector<std::string> containerFiles = util::File::listFiles(path, true, true, true); for(std::vector<std::string>::const_iterator iter = containerFiles.begin(); iter != containerFiles.end(); iter++) { std::string containerFile = *iter; if(std::string("mimetype").compare(containerFile) == 0 || std::string("META-INF/manifest.xml").compare(containerFile) == 0) { continue; } std::replace(containerFile.begin(), containerFile.end(), '\\', '/'); if(manifestFiles.find(containerFile) == manifestFiles.end()) { THROW_BDOCEXCEPTION("File '%s' found in BDOC container is not described in manifest.", containerFile.c_str()); } } } catch(const xml_schema::Exception& e) { std::ostringstream oss; oss << e; THROW_IOEXCEPTION("Failed to parse manifest XML: %s", oss.str().c_str()); } }
/** * Initialize BDOC container. */ digidoc::BDoc::BDoc() : d(new BDocPrivate) { THROW_BDOCEXCEPTION("BDOC 1 container is unsupported."); }