Aktualizr
C++ SOTA Client
All Classes Namespaces Files Functions Variables Enumerations Enumerator Pages
p11engine.cc
1 #include "p11engine.h"
2 
3 #include <array>
4 #include <utility>
5 #include <vector>
6 
7 #include <libp11.h>
8 #include <openssl/evp.h>
9 #include <openssl/pem.h>
10 #include <boost/algorithm/hex.hpp>
11 #include <boost/filesystem.hpp>
12 #include <boost/scoped_array.hpp>
13 
14 #include "crypto/crypto.h"
15 #include "utilities/config_utils.h"
16 #include "utilities/utils.h"
17 
18 P11Engine* P11EngineGuard::instance = nullptr;
19 int P11EngineGuard::ref_counter = 0;
20 
21 P11ContextWrapper::P11ContextWrapper(const boost::filesystem::path& module) {
22  if (module.empty()) {
23  ctx = nullptr;
24  return;
25  }
26  // never returns NULL
27  ctx = PKCS11_CTX_new();
28  if (PKCS11_CTX_load(ctx, module.c_str()) != 0) {
29  PKCS11_CTX_free(ctx);
30  LOG_ERROR << "Couldn't load PKCS11 module " << module.string() << ": "
31  << ERR_error_string(ERR_get_error(), nullptr);
32  throw std::runtime_error("PKCS11 error");
33  }
34 }
35 
36 P11ContextWrapper::~P11ContextWrapper() {
37  if (ctx != nullptr) {
38  PKCS11_CTX_unload(ctx);
39  PKCS11_CTX_free(ctx);
40  }
41 }
42 
43 P11SlotsWrapper::P11SlotsWrapper(PKCS11_ctx_st* ctx_in) {
44  ctx = ctx_in;
45  if (ctx == nullptr) {
46  wslots_ = nullptr;
47  nslots = 0;
48  return;
49  }
50  if (PKCS11_enumerate_slots(ctx, &wslots_, &nslots) != 0) {
51  LOG_ERROR << "Couldn't enumerate slots"
52  << ": " << ERR_error_string(ERR_get_error(), nullptr);
53  throw std::runtime_error("PKCS11 error");
54  }
55 }
56 
57 P11SlotsWrapper::~P11SlotsWrapper() {
58  if ((wslots_ != nullptr) && (nslots != 0U)) {
59  PKCS11_release_all_slots(ctx, wslots_, nslots);
60  }
61 }
62 
63 P11Engine::P11Engine(P11Config config) : config_(std::move(config)), ctx_(config_.module), slots_(ctx_.get()) {
64  if (config_.module.empty()) {
65  return;
66  }
67 
68  PKCS11_SLOT* slot = PKCS11_find_token(ctx_.get(), slots_.get_slots(), slots_.get_nslots());
69  if ((slot == nullptr) || (slot->token == nullptr)) {
70  throw std::runtime_error("Couldn't find pkcs11 token");
71  }
72 
73  LOG_DEBUG << "Slot manufacturer......: " << slot->manufacturer;
74  LOG_DEBUG << "Slot description.......: " << slot->description;
75  LOG_DEBUG << "Slot token label.......: " << slot->token->label;
76  LOG_DEBUG << "Slot token manufacturer: " << slot->token->manufacturer;
77  LOG_DEBUG << "Slot token model.......: " << slot->token->model;
78  LOG_DEBUG << "Slot token serialnr....: " << slot->token->serialnr;
79 
80  uri_prefix_ = std::string("pkcs11:serial=") + slot->token->serialnr + ";pin-value=" + config_.pass + ";id=%";
81 
82  ENGINE_load_builtin_engines();
83  ENGINE* engine = ENGINE_by_id("dynamic");
84 
85  if (engine == nullptr) {
86  throw std::runtime_error("SSL pkcs11 engine initialization failed");
87  }
88 
89  try {
90  const boost::filesystem::path pkcs11Path = findPkcsLibrary();
91  LOG_INFO << "Loading PKCS#11 engine library: " << pkcs11Path.string();
92  if (ENGINE_ctrl_cmd_string(engine, "SO_PATH", pkcs11Path.c_str(), 0) == 0) {
93  throw std::runtime_error(std::string("P11 engine command failed: SO_PATH ") + pkcs11Path.string());
94  }
95 
96  if (ENGINE_ctrl_cmd_string(engine, "ID", "pkcs11", 0) == 0) {
97  throw std::runtime_error("P11 engine command failed: ID pksc11");
98  }
99 
100  if (ENGINE_ctrl_cmd_string(engine, "LIST_ADD", "1", 0) == 0) {
101  throw std::runtime_error("P11 engine command failed: LIST_ADD 1");
102  }
103 
104  if (ENGINE_ctrl_cmd_string(engine, "LOAD", nullptr, 0) == 0) {
105  throw std::runtime_error("P11 engine command failed: LOAD");
106  }
107 
108  if (ENGINE_ctrl_cmd_string(engine, "MODULE_PATH", config_.module.c_str(), 0) == 0) {
109  throw std::runtime_error(std::string("P11 engine command failed: MODULE_PATH ") + config_.module.string());
110  }
111 
112  if (ENGINE_ctrl_cmd_string(engine, "PIN", config_.pass.c_str(), 0) == 0) {
113  throw std::runtime_error(std::string("P11 engine command failed: PIN"));
114  }
115 
116  if (ENGINE_init(engine) == 0) {
117  throw std::runtime_error("P11 engine initialization failed");
118  }
119  } catch (const std::runtime_error& exc) {
120  // Note: treat these in a special case, as ENGINE_finish cannot be called on
121  // an engine which has not been fully initialized
122  ENGINE_free(engine);
123  ENGINE_cleanup(); // for openssl < 1.1
124  throw;
125  }
126 
127  ssl_engine_ = engine;
128 }
129 
130 boost::filesystem::path P11Engine::findPkcsLibrary() {
131  static const boost::filesystem::path engine_path = PKCS11_ENGINE_PATH;
132 
133  if (!boost::filesystem::exists(engine_path)) {
134  LOG_ERROR << "PKCS11 engine not available (" << engine_path << ")";
135  return "";
136  }
137 
138  return engine_path;
139 }
140 
141 PKCS11_SLOT* P11Engine::findTokenSlot() const {
142  PKCS11_SLOT* slot = PKCS11_find_token(ctx_.get(), slots_.get_slots(), slots_.get_nslots());
143  if ((slot == nullptr) || (slot->token == nullptr)) {
144  LOG_ERROR << "Couldn't find a token";
145  return nullptr;
146  }
147  int rv;
148  PKCS11_is_logged_in(slot, 1, &rv);
149  if (rv == 0) {
150  if (PKCS11_open_session(slot, 1) != 0) {
151  LOG_ERROR << "Error creating rw session in to the slot: " << ERR_error_string(ERR_get_error(), nullptr);
152  }
153 
154  if (PKCS11_login(slot, 0, config_.pass.c_str()) != 0) {
155  LOG_ERROR << "Error logging in to the token: " << ERR_error_string(ERR_get_error(), nullptr);
156  return nullptr;
157  }
158  }
159  return slot;
160 }
161 
162 bool P11Engine::readUptanePublicKey(std::string* key_out) {
163  if (config_.module.empty()) {
164  return false;
165  }
166  if ((config_.uptane_key_id.length() % 2) != 0U) {
167  return false; // id is a hex string
168  }
169 
170  PKCS11_SLOT* slot = findTokenSlot();
171  if (slot == nullptr) {
172  return false;
173  }
174 
175  PKCS11_KEY* keys;
176  unsigned int nkeys;
177  int rc = PKCS11_enumerate_public_keys(slot->token, &keys, &nkeys);
178  if (rc < 0) {
179  LOG_ERROR << "Error enumerating public keys in PKCS11 device: " << ERR_error_string(ERR_get_error(), nullptr);
180  return false;
181  }
182  PKCS11_KEY* key = nullptr;
183  {
184  std::vector<unsigned char> id_hex;
185  boost::algorithm::unhex(config_.uptane_key_id, std::back_inserter(id_hex));
186 
187  for (unsigned int i = 0; i < nkeys; i++) {
188  // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
189  if ((keys[i].id_len == config_.uptane_key_id.length() / 2) &&
190  // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
191  (memcmp(keys[i].id, id_hex.data(), config_.uptane_key_id.length() / 2) == 0)) {
192  // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
193  key = &keys[i];
194  break;
195  }
196  }
197  }
198  if (key == nullptr) {
199  LOG_ERROR << "Requested public key was not found";
200  return false;
201  }
202  StructGuard<EVP_PKEY> evp_key(PKCS11_get_public_key(key), EVP_PKEY_free);
203  StructGuard<BIO> mem(BIO_new(BIO_s_mem()), BIO_vfree);
204  PEM_write_bio_PUBKEY(mem.get(), evp_key.get());
205 
206  char* pem_key = nullptr;
207  // NOLINTNEXTLINE(google-runtime-int,cppcoreguidelines-pro-type-cstyle-cast)
208  long length = BIO_get_mem_data(mem.get(), &pem_key);
209  key_out->assign(pem_key, static_cast<size_t>(length));
210 
211  return true;
212 }
213 
214 bool P11Engine::generateUptaneKeyPair() {
215  PKCS11_SLOT* slot = findTokenSlot();
216  if (slot == nullptr) {
217  return false;
218  }
219 
220  std::vector<unsigned char> id_hex;
221  boost::algorithm::unhex(config_.uptane_key_id, std::back_inserter(id_hex));
222 
223  // Manually generate a key and store it on the HSM
224  // Note that libp11 has a dedicated function marked as deprecated, it
225  // worked the same way in version <= 0.4.7 but tries to generate the
226  // RSA key directly on the HSM from 0.4.8. As it would not work reliably
227  // with openssl 1.1, we reimplemented it here.
228  StructGuard<EVP_PKEY> pkey = Crypto::generateRSAKeyPairEVP(KeyType::kRSA2048);
229  if (pkey == nullptr) {
230  LOG_ERROR << "Error generating keypair on the device:" << ERR_error_string(ERR_get_error(), nullptr);
231  return false;
232  }
233 
234  if (PKCS11_store_private_key(slot->token, pkey.get(), nullptr, id_hex.data(), id_hex.size()) != 0) {
235  LOG_ERROR << "Could not store private key on the token";
236  return false;
237  }
238  if (PKCS11_store_public_key(slot->token, pkey.get(), nullptr, id_hex.data(), id_hex.size()) != 0) {
239  LOG_ERROR << "Could not store public key on the token";
240  return false;
241  }
242 
243  return true;
244 }
245 
246 bool P11Engine::readTlsCert(std::string* cert_out) const {
247  const std::string& id = config_.tls_clientcert_id;
248 
249  if (config_.module.empty()) {
250  return false;
251  }
252  if ((id.length() % 2) != 0U) {
253  return false; // id is a hex string
254  }
255 
256  PKCS11_SLOT* slot = findTokenSlot();
257  if (slot == nullptr) {
258  return false;
259  }
260 
261  PKCS11_CERT* certs;
262  unsigned int ncerts;
263  int rc = PKCS11_enumerate_certs(slot->token, &certs, &ncerts);
264  if (rc < 0) {
265  LOG_ERROR << "Error enumerating certificates in PKCS11 device: " << ERR_error_string(ERR_get_error(), nullptr);
266  return false;
267  }
268 
269  PKCS11_CERT* cert = nullptr;
270  {
271  std::vector<unsigned char> id_hex;
272  boost::algorithm::unhex(id, std::back_inserter(id_hex));
273 
274  for (unsigned int i = 0; i < ncerts; i++) {
275  // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
276  if ((certs[i].id_len == id.length() / 2) && (memcmp(certs[i].id, id_hex.data(), id.length() / 2) == 0)) {
277  // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
278  cert = &certs[i];
279  break;
280  }
281  }
282  }
283  if (cert == nullptr) {
284  LOG_ERROR << "Requested certificate was not found";
285  return false;
286  }
287  StructGuard<BIO> mem(BIO_new(BIO_s_mem()), BIO_vfree);
288  PEM_write_bio_X509(mem.get(), cert->x509);
289 
290  char* pem_key = nullptr;
291  // NOLINTNEXTLINE(google-runtime-int,cppcoreguidelines-pro-type-cstyle-cast)
292  long length = BIO_get_mem_data(mem.get(), &pem_key);
293  cert_out->assign(pem_key, static_cast<size_t>(length));
294 
295  return true;
296 }
P11Engine
Definition: p11engine.h:43
P11Config
Definition: config.h:28