Initial values tool 94/190494/16
authorKrzysztof Jackiewicz <k.jackiewicz@samsung.com>
Tue, 2 Oct 2018 12:27:27 +0000 (14:27 +0200)
committerBartlomiej Grzelewski <b.grzelewski@samsung.com>
Fri, 5 Oct 2018 19:24:36 +0000 (21:24 +0200)
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

LICENSE.BSD-2-Clause [new file with mode: 0644]
packaging/key-manager.spec
tools/CMakeLists.txt
tools/ckm_initial_values/CMakeLists.txt [new file with mode: 0644]
tools/ckm_initial_values/base64.cpp [new file with mode: 0644]
tools/ckm_initial_values/base64.h [new file with mode: 0644]
tools/ckm_initial_values/main.cpp [new file with mode: 0644]

diff --git a/LICENSE.BSD-2-Clause b/LICENSE.BSD-2-Clause
new file mode 100644 (file)
index 0000000..03f5f58
--- /dev/null
@@ -0,0 +1,24 @@
+Copyright (c) 2014, STMicroelectronics International N.V.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+this list of conditions and the following disclaimer in the documentation
+and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
index 1a3dd0b..4daa444 100644 (file)
@@ -125,6 +125,18 @@ Requires(postun): %{sbin_dir}/ldconfig
 CKM login/password module to PAM. Used to monitor user login/logout
 and password change events from PAM
 
+%package -n key-manager-initial-values
+Summary:    CKM initial values tool
+Group:      Security/Libraries
+License:    Apache-2.0 and BSD-2-Clause
+BuildRequires: cmake
+BuildRequires: pkgconfig(openssl)
+BuildRequires: pkgconfig(libxml-2.0)
+Requires(post): %{sbin_dir}/ldconfig
+Requires(postun): %{sbin_dir}/ldconfig
+
+%description -n key-manager-initial-values
+Includes ckm_initial_values tool for initial values XML generation
 
 %prep
 %setup -q
@@ -322,3 +334,8 @@ fi
 %{bin_dir}/ckm_db_merge
 %{bin_dir}/ckm_generate_db
 %test_dir
+
+%files -n key-manager-initial-values
+%license LICENSE
+%license LICENSE.BSD-2-Clause
+%{bin_dir}/ckm_initial_values
index c824559..9d12bb7 100644 (file)
@@ -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 (file)
index 0000000..c23f1e6
--- /dev/null
@@ -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 (file)
index 0000000..aa1520a
--- /dev/null
@@ -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 (file)
index 0000000..c9085bf
--- /dev/null
@@ -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 (file)
index 0000000..4e9e0a1
--- /dev/null
@@ -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;
+}