diff options
author | Krzysztof Jackiewicz <k.jackiewicz@samsung.com> | 2018-10-02 14:27:27 +0200 |
---|---|---|
committer | Bartlomiej Grzelewski <b.grzelewski@samsung.com> | 2018-10-05 21:24:36 +0200 |
commit | 3195a9624186ca5b6b681148b192e9ef48cfe872 (patch) | |
tree | 1143161576039ff8877adcea5760ed1d0bdcf579 /tools | |
parent | f0a0b4b6f6f5047df98a75ec999a964ce772b012 (diff) | |
download | key-manager-3195a9624186ca5b6b681148b192e9ef48cfe872.tar.gz key-manager-3195a9624186ca5b6b681148b192e9ef48cfe872.tar.bz2 key-manager-3195a9624186ca5b6b681148b192e9ef48cfe872.zip |
Initial values tool
Add a tool able to create and/or update an initial values xml. It is also
possible to add encrypted ininial values.
Add rpm package for potential use in gbs buildroot during image creation.
Limitations:
- Hardcoded IV & tag length
- Hardcoded Data format
Testing:
dd if=/dev/random of=/tmp/key bs=32 count=1
dd if=/dev/random of=/tmp/data bs=32 count=1
ckm_initial_values -k /tmp/key -d /tmp/data -n name -t Key -s AES -p pass -e
-b hardware -a acc1,acc2,acc3
Change-Id: Id29d0eb58d9dba3e78b3437534cb566046a39877
Diffstat (limited to 'tools')
-rw-r--r-- | tools/CMakeLists.txt | 1 | ||||
-rw-r--r-- | tools/ckm_initial_values/CMakeLists.txt | 49 | ||||
-rw-r--r-- | tools/ckm_initial_values/base64.cpp | 204 | ||||
-rw-r--r-- | tools/ckm_initial_values/base64.h | 65 | ||||
-rw-r--r-- | tools/ckm_initial_values/main.cpp | 468 |
5 files changed, 787 insertions, 0 deletions
diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index c8245596..9d12bb76 100644 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -46,3 +46,4 @@ INSTALL(TARGETS ${CKM_TOOL} WORLD_EXECUTE ) ADD_SUBDIRECTORY(ckm_db_tool) +ADD_SUBDIRECTORY(ckm_initial_values)
\ No newline at end of file diff --git a/tools/ckm_initial_values/CMakeLists.txt b/tools/ckm_initial_values/CMakeLists.txt new file mode 100644 index 00000000..c23f1e62 --- /dev/null +++ b/tools/ckm_initial_values/CMakeLists.txt @@ -0,0 +1,49 @@ +#### This is to allow local build without gbs #### +CMAKE_MINIMUM_REQUIRED(VERSION 2.6) +INCLUDE(FindPkgConfig) +INCLUDE(GNUInstallDirs) + +IF(NOT DEFINED BIN_DIR) + SET(BIN_DIR "${CMAKE_INSTALL_FULL_BINDIR}" CACHE PATH "User executables directory") +ENDIF() + +SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x") +################################################## + + +SET(CKM_INITIAL_VALUES "ckm_initial_values") + +PKG_CHECK_MODULES(CKM_INITIAL_VALUES_DEP + REQUIRED + openssl + libxml-2.0 + ) + +INCLUDE_DIRECTORIES( + ${CKM_INITIAL_VALUES_DEP_INCLUDE_DIRS} + ) + +SET(CKM_INITIAL_VALUES_SOURCES + ${CMAKE_CURRENT_SOURCE_DIR}/main.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/base64.cpp + ) + +LINK_DIRECTORIES(${CKM_INITIAL_VALUES_DEP_LIBRARY_DIRS}) + +ADD_EXECUTABLE(${CKM_INITIAL_VALUES} ${CKM_INITIAL_VALUES_SOURCES}) + +TARGET_LINK_LIBRARIES(${CKM_INITIAL_VALUES} + ${CKM_INITIAL_VALUES_DEP_LIBRARIES} + ) + +INSTALL(TARGETS ${CKM_INITIAL_VALUES} + DESTINATION ${BIN_DIR} + PERMISSIONS OWNER_READ + OWNER_WRITE + OWNER_EXECUTE + GROUP_READ + GROUP_EXECUTE + WORLD_READ + WORLD_EXECUTE + ) + diff --git a/tools/ckm_initial_values/base64.cpp b/tools/ckm_initial_values/base64.cpp new file mode 100644 index 00000000..aa1520a0 --- /dev/null +++ b/tools/ckm_initial_values/base64.cpp @@ -0,0 +1,204 @@ +/* + * Copyright (c) 2011 Samsung Electronics Co., Ltd All Rights Reserved + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include <algorithm> +#include <memory> + +#include <string.h> +#include <openssl/bio.h> +#include <openssl/evp.h> +#include <openssl/buffer.h> + +#include "base64.h" + +namespace CKM { + +Base64Encoder::Base64Encoder() : + m_b64(0), + m_bmem(0), + m_finalized(false) +{ +} + +void Base64Encoder::append(const RawBuffer &data) +{ + if (m_finalized) { + throw std::logic_error("Already finalized"); + } + + if (!m_b64) + reset(); + + BIO_write(m_b64, data.data(), data.size()); +} + +void Base64Encoder::finalize() +{ + if (m_finalized) { + throw std::logic_error("Already finalized."); + } + + m_finalized = true; + (void)BIO_flush(m_b64); +} + +RawBuffer Base64Encoder::get() +{ + if (!m_finalized) { + throw std::logic_error("Not finalized"); + } + + BUF_MEM *bptr = nullptr; + BIO_get_mem_ptr(m_b64, &bptr); + + if (!bptr) { + throw std::logic_error("Bio internal error"); + } + + if (bptr->length > 0) + return RawBuffer(bptr->data, bptr->data + bptr->length); + + return RawBuffer(); +} + +void Base64Encoder::reset() +{ + m_finalized = false; + BIO_free_all(m_b64); + m_b64 = BIO_new(BIO_f_base64()); + m_bmem = BIO_new(BIO_s_mem()); + + if (!m_b64 || !m_bmem) { + throw std::logic_error("Error during allocation memory in BIO"); + } + + BIO_set_flags(m_b64, BIO_FLAGS_BASE64_NO_NL); + m_b64 = BIO_push(m_b64, m_bmem); +} + +Base64Encoder::~Base64Encoder() +{ + BIO_free_all(m_b64); +} + +Base64Decoder::Base64Decoder() : + m_finalized(false) +{ +} + +void Base64Decoder::append(const RawBuffer &data) +{ + if (m_finalized) { + throw std::logic_error("Already finalized."); + } + + std::copy(data.begin(), data.end(), std::back_inserter(m_input)); +} + +static bool whiteCharacter(char a) +{ + return a == '\n'; +} + +bool Base64Decoder::finalize() +{ + if (m_finalized) { + throw std::logic_error("Already finalized."); + } + + m_finalized = true; + + m_input.erase(std::remove_if(m_input.begin(), + m_input.end(), + whiteCharacter), + m_input.end()); + + for (size_t i = 0; i < m_input.size(); ++i) { + if (isalnum(m_input[i]) + || m_input[i] == '+' + || m_input[i] == '/' + || m_input[i] == '=') + continue; + + return false; + } + + BIO *b64, *bmem; + size_t len = m_input.size(); + + RawBuffer buffer(len); + + if (!buffer.data()) { + throw std::logic_error("Error in malloc."); + } + + memset(buffer.data(), 0, buffer.size()); + b64 = BIO_new(BIO_f_base64()); + + if (!b64) { + throw std::logic_error("Couldn't create BIO object."); + } + + BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL); + RawBuffer tmp(m_input); + m_input.clear(); + + bmem = BIO_new_mem_buf(tmp.data(), len); + + if (!bmem) { + BIO_free(b64); + throw std::logic_error("Internal error in BIO"); + } + + bmem = BIO_push(b64, bmem); + + if (!bmem) { + BIO_free(b64); + throw std::logic_error("Internal error in BIO"); + } + + int readlen = BIO_read(bmem, buffer.data(), buffer.size()); + m_output.clear(); + + bool status = true; + + if (readlen > 0) { + buffer.resize(readlen); + m_output = std::move(buffer); + } else { + status = false; + } + + BIO_free_all(bmem); + return status; +} + +RawBuffer Base64Decoder::get() const +{ + if (!m_finalized) { + throw std::logic_error("Not finalized"); + } + + return m_output; +} + +void Base64Decoder::reset() +{ + m_finalized = false; + m_input.clear(); + m_output.clear(); +} + +} // namespace CKM diff --git a/tools/ckm_initial_values/base64.h b/tools/ckm_initial_values/base64.h new file mode 100644 index 00000000..c9085bf8 --- /dev/null +++ b/tools/ckm_initial_values/base64.h @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2011 Samsung Electronics Co., Ltd All Rights Reserved + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef _BASE64_H_ +#define _BASE64_H_ + +#include <string> +#include <vector> + +typedef std::vector<unsigned char> RawBuffer; + +struct bio_st; +typedef bio_st BIO; + +namespace CKM { + +class Base64Encoder { +public: + Base64Encoder(); + void append(const RawBuffer &data); + void finalize(); + RawBuffer get(); + void reset(); + ~Base64Encoder(); + +private: + BIO *m_b64; + BIO *m_bmem; + bool m_finalized; +}; + +class Base64Decoder { +public: + Base64Decoder(); + void append(const RawBuffer &data); + + /* + * Function will return false when BIO_read fails + * (for example: when string was not in base64 format). + */ + bool finalize(); + RawBuffer get() const; + void reset(); + ~Base64Decoder() {} + +private: + RawBuffer m_input; + RawBuffer m_output; + bool m_finalized; +}; +} // namespace CKM + +#endif diff --git a/tools/ckm_initial_values/main.cpp b/tools/ckm_initial_values/main.cpp new file mode 100644 index 00000000..4e9e0a10 --- /dev/null +++ b/tools/ckm_initial_values/main.cpp @@ -0,0 +1,468 @@ +/* + * Copyright (c) 2018 Samsung Electronics Co., Ltd All Rights Reserved + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ +/* + * @file main.cpp + * @author Krzysztof Jackiewicz (k.jackiewicz@samsung.com) + * @version 1.0 + * @brief + */ + +#include <cstdio> + +#include <unistd.h> +#include <getopt.h> + +#include <cstdlib> + +#include <iostream> +#include <memory> +#include <fstream> +#include <vector> +#include <set> +#include <string> +#include <unordered_map> + +#include <openssl/evp.h> +#include <openssl/rand.h> + +#include <libxml/tree.h> +#include <libxml/parser.h> + +#include "base64.h" + +typedef std::vector<unsigned char> Buffer; +typedef std::istreambuf_iterator<char> InputIterator; + +const size_t DEFAULT_TAG_LEN = 16; +const size_t DEFAULT_IV_LEN = 16; + +const struct option OPTS[] = { + {"xml", required_argument, 0, 'x'}, + {"key", required_argument, 0, 'k'}, + {"name", required_argument, 0, 'n'}, + {"data", required_argument, 0, 'd'}, + {"type", required_argument, 0, 't'}, + {"subtype", required_argument, 0, 's'}, + {"password", required_argument, 0, 'p'}, + {"exportable", no_argument, 0, 'e'}, + {"accessors", required_argument, 0, 'a'}, + {"backend", required_argument, 0, 'b'}, + {"help", no_argument, 0, 'h'}, + {0, 0, 0, 0 } +}; + +const std::string KEY = "Key"; +const std::string DATA = "Data"; +const std::string CERT = "Cert"; +const std::set<std::string> TYPES = { KEY, DATA, CERT }; +const std::set<std::string> SUBTYPES = { "RSA_PRV", "RSA_PUB", + "DSA_PRV", "DSA_PUB", + "ECDSA_PRV", "ECDSA_PUB", + "AES" +}; +const std::set<std::string> BACKENDS = { "software", "hardware" }; + +struct FormatTag { + const std::string plain; + const std::string encrypted; +}; + +const std::unordered_map<std::string, FormatTag> FORMAT = { + { KEY, {"DER", "EncryptedDER"}}, + { DATA, {"Base64", "EncryptedBinary"}}, + { CERT, {"DER", "EncryptedDER"}}, +}; + +std::string base64(const Buffer& data) +{ + try { + CKM::Base64Encoder encoder; + encoder.append(data); + encoder.finalize(); + auto result = encoder.get(); + return std::string(result.begin(), result.end()); + } catch (std::exception &e) { + std::cerr << "Error: " << e.what() << std::endl; + } + return std::string(); +} + +struct InitialValue { + InitialValue() {} + + std::string name, type, subType, data, iv, tag, password, format, backend, exportable; + std::set<std::string> accessors; +}; + +void usage() +{ + std::cout << std::endl << + "Usage: ckm_initial_values <options>" << std::endl << + std::endl << + "Mandatory options:" << std::endl << + " -d|--data <dataFile> Path to file containing initial value to be added. Supported" << std::endl << + " formats:" << std::endl << + " - Key:" << std::endl << + " - raw binary (symmetric keys)" << std::endl << + " - DER (asymmetric keys)" << std::endl << + " - Data: raw binary" << std::endl << + " - Cert: DER" << std::endl << + " -n|--name <name> Name, under which the initial value will be saved." << std::endl << + " -t|--type <type> Initial value type. One of: Key, Data, Cert." << std::endl << + " -s|--subtype <subtype> Initial value subtype. For 'Key' type allowed values are:" << std::endl << + " RSA_PRV, RSA_PUB, DSA_PRV, DSA_PUB, ECDSA_PRV, ECDSA_PUB, AES." << std::endl << + " For other types this option should not be used." << std::endl << + std::endl << + "Optional:" << std::endl << + " -x|--xml <xmlFile> Path to XML file that should be modified. If not provided output" << std::endl << + " will be printed to stdout." << std::endl << + " -k|--key <keyFile> Path to file containing AES key in binary form used for initial" << std::endl << + " value encryption." << std::endl << + " -p|--password <password> Password used to encrypt the initial value." << std::endl << + " -e|--exportable If present the stored value can be later extracted via" << std::endl << + " key-manager API." << std::endl << + " -a|--accessors <accessor1>[,<accessor2>[,...]]" << std::endl << + " A list of key-manager clients allowed to access given initial" << std::endl << + " value separated by commas." << std::endl << + " -b|--backend <backend> A key-manager's backed to use when saving the initial values." << std::endl; +} + +Buffer readFile(const std::string& file) +{ + if (file.empty()) + return Buffer(); + + std::ifstream stream(file); + if (!stream.good()) { + std::cerr << "Invalid file " << file << std::endl; + ::exit(EXIT_FAILURE); + } + + Buffer data(InputIterator(stream), { }); + return data; +} + + +bool encrypt(const Buffer& data, const Buffer& key, Buffer& output, Buffer& iv, Buffer& tag) +{ + OPENSSL_init(); + + iv.resize(DEFAULT_IV_LEN); + + std::ifstream stream("/dev/urandom"); + if (!stream.good()) { + std::cerr << "Can't open /dev/urandom" << std::endl; + return false; + } + // FIXIT + stream.read(reinterpret_cast<char*>(iv.data()), DEFAULT_IV_LEN); + + std::unique_ptr<EVP_CIPHER_CTX, void (*)(EVP_CIPHER_CTX *)> ctx(EVP_CIPHER_CTX_new(), + EVP_CIPHER_CTX_free); + + if (!ctx) { + std::cerr << "EVP_CIPHER_CTX_new() failed" << std::endl; + return false; + } + + if (1 != EVP_EncryptInit_ex(ctx.get(), EVP_aes_256_gcm(), NULL, NULL, NULL)) { + std::cerr << "EVP_EncryptInit_ex() failed" << std::endl; + return false; + } + + if (1 != EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_GCM_SET_IVLEN, iv.size(), NULL)) { + std::cerr << "EVP_CIPHER_CTX_ctrl() failed" << std::endl; + return false; + } + + if (1 != EVP_EncryptInit_ex(ctx.get(), NULL, NULL, key.data(), iv.data())) { + std::cerr << "EVP_EncryptInit_ex() failed" << std::endl; + return false; + } + + int outputLen = 0; + output.resize(data.size() + EVP_CIPHER_CTX_block_size(ctx.get())); + int written = 0; + + if (1 != EVP_EncryptUpdate(ctx.get(), output.data(), &written, data.data(), data.size())) { + std::cerr << "EVP_EncryptUpdate() failed" << std::endl; + return false; + } + outputLen += written; + + if (1 != EVP_EncryptFinal_ex(ctx.get(), &output.data()[written], &written)) { + std::cerr << "EVP_EncryptFinal_ex() failed" << std::endl; + return false; + } + outputLen += written; + output.resize(outputLen); + + tag.resize(DEFAULT_TAG_LEN); + if (1 != EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_GCM_GET_TAG, DEFAULT_TAG_LEN, tag.data())) { + std::cerr << "EVP_CIPHER_CTX_ctrl() failed" << std::endl; + return false; + } + + return true; +} + +struct ScopedXmlLib { + ScopedXmlLib() : doc(NULL), commit(false) { + xmlInitParser(); + LIBXML_TEST_VERSION; + xmlKeepBlanksDefault(0); + } + + ~ScopedXmlLib() { + xmlFreeDoc(doc); + xmlCleanupParser(); + } + + xmlDocPtr doc; + bool commit; +}; + +xmlNodePtr addChild(xmlNodePtr parent, const std::string& name) +{ + auto childNode = xmlNewNode(NULL, BAD_CAST name.c_str()); + if (childNode == NULL) { + std::cerr << "xmlNewNode() failed for " << name << std::endl; + return NULL; + } + + if (xmlAddChild(parent, childNode) == NULL) { + std::cerr << "xmlAddChild() failed" << std::endl; + xmlFreeNode(childNode); + return NULL; + } + + return childNode; +} + +bool addProperty(xmlNodePtr element, const std::string& name, const std::string& value) +{ + if (value.empty()) + return true; + + if (xmlNewProp(element, BAD_CAST name.c_str(), BAD_CAST value.c_str()) == NULL) { + std::cerr << "xmlNewProp() failed for " << name << std::endl; + return false; + } + return true; +} + +bool addInitialValue(const std::string& xmlFile, const InitialValue& val) +{ + ScopedXmlLib lib; + + lib.doc = xmlReadFile(xmlFile.c_str(), NULL, XML_PARSE_NOWARNING); + if (lib.doc == NULL) { + lib.doc = xmlNewDoc(BAD_CAST "1.0"); + if (lib.doc == NULL) { + std::cerr << "xmlNewDoc() failed" << std::endl; + return false; + } + } + + // root node + auto rootNode = xmlDocGetRootElement(lib.doc); + if (rootNode == NULL) { + rootNode = xmlNewNode(NULL, BAD_CAST "InitialValues"); + if (rootNode == NULL) { + std::cerr << "xmlNewNode() failed" << std::endl; + return false; + } + + xmlDocSetRootElement(lib.doc, rootNode); + + if (!addProperty(rootNode, "version", "2")) + return false; + } + + // value node + auto valNode = addChild(rootNode, val.type); + if (valNode == NULL) + return false; + + if (!addProperty(valNode, "name", val.name)) + return false; + + if (!addProperty(valNode, "type", val.subType)) + return false; + + if (!addProperty(valNode, "password", val.password)) + return false; + + if (!addProperty(valNode, "exportable", val.exportable)) + return false; + + if (!addProperty(valNode, "backend", val.backend)) + return false; + + // data node + auto dataNode = xmlNewTextChild(valNode, + NULL, + BAD_CAST val.format.c_str(), + BAD_CAST val.data.c_str()); + if (dataNode == NULL) + return false; + + if (!addProperty(dataNode, "IV", val.iv)) + return false; + + if (!addProperty(dataNode, "tag", val.tag)) + return false; + + // accessor nodes + for (auto& accessor : val.accessors) { + auto accNode = addChild(valNode, "Permission"); + if (accNode == NULL) + return false; + + if (!addProperty(accNode, "accessor", accessor)) + return false; + } + + if (0 >= xmlSaveFormatFile(xmlFile.empty() ? "-" : xmlFile.c_str(), lib.doc, 1)) { + std::cerr << "xmlSaveFile() failed" << std::endl; + return false; + } + return true; +} + +int main(int argc, char* argv[]) +{ + std::string xmlFile, keyFile, dataFile; + Buffer key, data, iv, tag; + InitialValue val; + + int idx = 0; + int c; + while ((c = ::getopt_long(argc, argv, "x:k:n:d:t:s:p:ea:b:h", OPTS, &idx)) != -1) { + switch (c) { + case 'x': + xmlFile = optarg; + break; + case 'k': + keyFile = optarg; + break; + case 'n': + val.name = optarg; + break; + case 'd': + dataFile = optarg; + break; + case 't': + val.type = optarg; + break; + case 's': + val.subType = optarg; + break; + case 'p': + val.password = optarg; + break; + case 'e': + val.exportable = "true"; + break; + case 'a': + { + std::string tmp = optarg; + size_t pos = 0; + size_t found = 0; + while ((found = tmp.find(',', pos)) != std::string::npos) { + if (found != pos) + val.accessors.insert(tmp.substr(pos, found - pos)); + pos = found + 1; + } + if (pos < tmp.size()) + val.accessors.insert(tmp.substr(pos)); + break; + } + case 'b': + val.backend = optarg; + break; + case 'h': + usage(); + return EXIT_SUCCESS; + case '?': + case ':': + default: + usage(); + return EXIT_FAILURE; + } + } + + if (val.name.empty() || dataFile.empty() || val.type.empty()) { + usage(); + return EXIT_FAILURE; + } + + if (TYPES.find(val.type) == TYPES.end()) { + usage(); + return EXIT_FAILURE; + } + + if (val.type == KEY && (val.subType.empty() || SUBTYPES.find(val.subType) == SUBTYPES.end())) { + usage(); + return EXIT_FAILURE; + } + + if (!val.backend.empty() && BACKENDS.find(val.backend) == BACKENDS.end()) { + usage(); + return EXIT_FAILURE; + } + + data = readFile(dataFile); + if (data.empty()) { + std::cerr << "Empty data file " << dataFile << std::endl; + return EXIT_FAILURE; + } + + key = readFile(keyFile); + + val.format = FORMAT.at(val.type).plain; + if (!keyFile.empty()) { + if (key.size() != 32) { + std::cerr << "Invalid key size " << std::endl; + return EXIT_FAILURE; + } + + Buffer output; + if (!encrypt(data, key, output, iv, tag)) + return EXIT_FAILURE; + + val.data = base64(output); + if (val.data.empty()) + return EXIT_FAILURE; + + val.iv = base64(iv); + if (val.iv.empty()) + return EXIT_FAILURE; + + val.tag = base64(tag); + if (val.tag.empty()) + return EXIT_FAILURE; + + val.format = FORMAT.at(val.type).encrypted; + } else { + val.data = base64(data); + } + + if (!addInitialValue(xmlFile, val)) + return EXIT_FAILURE; + + return EXIT_SUCCESS; +} |