OpenDDS  Snapshot(2023/04/28-20:55)
Certificate.cpp
Go to the documentation of this file.
1 /*
2  * Distributed under the OpenDDS License.
3  * See: http://www.OpenDDS.org/license.html
4  */
5 
6 #include "Certificate.h"
8 #include "Err.h"
9 #include <algorithm>
10 #include <cstring>
11 #include <fstream>
12 #include <iterator>
13 #include <cerrno>
14 #include <openssl/pem.h>
15 #include <openssl/x509v3.h>
16 #include "../OpenSSL_legacy.h" // Must come after all other OpenSSL includes
17 #include <sstream>
18 
20 
21 namespace OpenDDS {
22 namespace Security {
23 namespace SSL {
24 
25 Certificate::Certificate(const std::string& uri,
26  const std::string& password)
27  : x_(0), original_bytes_(), dsign_algo_("")
28 {
30  if (! load(ex, uri, password)) {
31  ACE_ERROR((LM_WARNING, "(%P|%t) %C\n", ex.message.in()));
32  }
33 }
34 
36  : x_(0), original_bytes_(), dsign_algo_("")
37 {
38  deserialize(src);
39 }
40 
42  : x_(0), original_bytes_(), dsign_algo_("")
43 {
44 }
45 
47  : x_(0), original_bytes_(), dsign_algo_("")
48 {
49  if (0 < other.original_bytes_.length()) {
51  }
52 }
53 
55 {
56  if (x_) {
57  X509_free(x_);
58  }
59 }
60 
62 {
63  if (this != &rhs) {
64  if (rhs.x_ && (0 < rhs.original_bytes_.length())) {
66  }
67 
68  }
69  return *this;
70 }
71 
73  const std::string& uri,
74  const std::string& password)
75 {
76  using namespace CommonUtilities;
77 
78  if (x_) {
79  set_security_error(ex, -1, 0, "SSL::Certificate::load: WARNING: document already loaded");
80  return false;
81  }
82 
83  URI uri_info(uri);
84 
85  switch (uri_info.scheme) {
86  case URI::URI_FILE:
88  x_ = x509_from_pem(original_bytes_, password);
89  break;
90 
91  case URI::URI_DATA:
93  x_ = x509_from_pem(original_bytes_, password);
94  break;
95 
96  case URI::URI_PKCS11:
97  case URI::URI_UNKNOWN:
98  default:
100  "(%P|%t) SSL::Certificate::load: WARNING: Unsupported URI scheme\n"));
101 
102  break;
103  }
104 
105  if (!loaded()) {
106  std::stringstream msg;
107  msg << "SSL::Certificate::load: WARNING: Failed to load document supplied "
108  "with URI '" << uri << "'";
109  set_security_error(ex, -1, 0, msg.str().c_str());
110  return false;
111  }
112 
113  const int err = cache_dsign_algo();
114  if (err) {
115  set_security_error(ex, -1, 0, "SSL::Certificate::load: WARNING: Failed to cache signature algorithm");
116  return false;
117  }
118  return true;
119 }
120 
121 int Certificate::validate(const Certificate& ca, unsigned long int flags) const
122 {
123  if (!x_) {
125  ACE_TEXT("(%P|%t) SSL::Certificate::verify: WARNING, a ")
126  ACE_TEXT("certificate must be loaded before it can be verified\n")), 1);
127  }
128 
129  if (!ca.x_) {
131  ACE_TEXT("(%P|%t) SSL::Certificate::verify: WARNING, passed-in ")
132  ACE_TEXT("CA has not loaded a certificate\n")), 1);
133  }
134 
135  X509_STORE* const certs = X509_STORE_new();
136  if (!certs) {
137  OPENDDS_SSL_LOG_ERR("failed to create X509_STORE");
138  return 1;
139  }
140 
141  X509_STORE_add_cert(certs, ca.x_);
142 
143  X509_STORE_CTX* validation_ctx = X509_STORE_CTX_new();
144  if (!validation_ctx) {
145  X509_STORE_free(certs);
146  return 1;
147  }
148 
149  X509_STORE_CTX_init(validation_ctx, certs, x_, 0);
150  X509_STORE_CTX_set_flags(validation_ctx, flags);
151 
152  int result =
153  X509_verify_cert(validation_ctx) == 1
154  ? X509_V_OK : 1; // X509_V_ERR_UNSPECIFIED is not provided by all OpenSSL versions
155 
156  if (result == 1) {
157  const int err = X509_STORE_CTX_get_error(validation_ctx),
158  depth = X509_STORE_CTX_get_error_depth(validation_ctx);
160  ACE_TEXT("(%P|%t) SSL::Certificate::verify: WARNING '%C' occurred using cert at ")
161  ACE_TEXT("depth '%i', validation failed.\n"),
162  X509_verify_cert_error_string(err), depth));
163  result = err;
164  }
165 
166  X509_STORE_CTX_free(validation_ctx);
167  X509_STORE_free(certs);
168  return result;
169 }
170 
172 {
173 public:
175  : public_key(pkey), md_ctx(0), pkey_ctx(0)
176  {
177  }
178 
180 
181  int operator()(const DDS::OctetSeq& src,
182  const std::vector<const DDS::OctetSeq*>& expected_contents)
183  {
184  if (!public_key) return 1;
185 
186  int pk_id = 0;
187  std::vector<const DDS::OctetSeq*>::const_iterator i, n;
188 
189  md_ctx = EVP_MD_CTX_new();
190  if (!md_ctx) {
191  OPENDDS_SSL_LOG_ERR("EVP_MD_CTX_new failed");
192  return 1;
193  }
194 
195  EVP_MD_CTX_init(md_ctx);
196 
197  if (1 != EVP_DigestVerifyInit(md_ctx, &pkey_ctx, EVP_sha256(), 0,
198  public_key)) {
199  OPENDDS_SSL_LOG_ERR("EVP_DigestVerifyInit failed");
200  return 1;
201  }
202 
203  // Determine which signature type is being verified
204  pk_id = EVP_PKEY_id(public_key);
205 
206  if (pk_id == EVP_PKEY_RSA) {
207  if (1 !=
208  EVP_PKEY_CTX_set_rsa_padding(pkey_ctx, RSA_PKCS1_PSS_PADDING)) {
209  OPENDDS_SSL_LOG_ERR("EVP_PKEY_CTX_set_rsa_padding failed");
210  return 1;
211  }
212 
213  if (1 != EVP_PKEY_CTX_set_rsa_mgf1_md(pkey_ctx, EVP_sha256())) {
214  OPENDDS_SSL_LOG_ERR("EVP_PKEY_CTX_set_rsa_mgf1_md failed");
215  return 1;
216  }
217  }
218 
219  n = expected_contents.end();
220  for (i = expected_contents.begin(); i != n; ++i) {
221  if ((*i)->length() > 0) {
222  if (1 != EVP_DigestVerifyUpdate(md_ctx, (*i)->get_buffer(),
223  (*i)->length())) {
224  OPENDDS_SSL_LOG_ERR("EVP_DigestVerifyUpdate failed");
225  return 1;
226  }
227  }
228  }
229 
230 #ifdef OPENSSL_V_1_0
231  // some versions of OpenSSL take a pointer to non-const
232  unsigned char* buffer = const_cast<unsigned char*>(src.get_buffer());
233 #else
234  const unsigned char* buffer = src.get_buffer();
235 #endif
236  const int err = EVP_DigestVerifyFinal(md_ctx, buffer, src.length());
237  if (0 == err) {
238  return 1; // Verification failed, but no error occurred
239 
240  } else if (1 != err) {
241  OPENDDS_SSL_LOG_ERR("EVP_DigestVerifyFinal failed");
242  return 1;
243  }
244 
245  return 0;
246  }
247 
248 private:
250  EVP_MD_CTX* md_ctx;
251  EVP_PKEY_CTX* pkey_ctx;
252 };
253 
255  const DDS::OctetSeq& src,
256  const std::vector<const DDS::OctetSeq*>& expected_contents) const
257 {
258 #ifdef OPENSSL_V_1_0
259  struct EVP_PKEY_Handle {
260  EVP_PKEY* pkey_;
261  explicit EVP_PKEY_Handle(EVP_PKEY* pkey) : pkey_(pkey) {}
262  operator EVP_PKEY*() { return pkey_; }
263  ~EVP_PKEY_Handle() { EVP_PKEY_free(pkey_); }
264  } pkey(X509_get_pubkey(x_));
265 #else
266  EVP_PKEY* pkey = X509_get0_pubkey(x_);
267 #endif
268  verify_implementation verify(pkey);
269  return verify(src, expected_contents);
270 }
271 
272 int Certificate::subject_name_to_str(std::string& dst,
273  unsigned long flags) const
274 {
275  int result = 1;
276 
277  dst.clear();
278 
279  if (x_) {
280  /* Do not free name! */
281  X509_NAME* name = X509_get_subject_name(x_);
282  if (name) {
283  BIO* buffer = BIO_new(BIO_s_mem());
284  if (buffer) {
285  int len = X509_NAME_print_ex(buffer, name, 0, flags);
286  if (len > 0) {
287  std::vector<char> tmp(len +
288  1); // BIO_gets will add null hence +1
289  len = BIO_gets(buffer, &tmp[0], len + 1);
290  if (len > 0) {
291  std::copy(
292  tmp.begin(),
293  tmp.end() -
294  1, // But... string inserts a null so it's not needed
295  std::back_inserter(dst));
296  result = 0;
297 
298  } else {
299  OPENDDS_SSL_LOG_ERR("failed to write BIO to string");
300  }
301 
302  } else {
303  OPENDDS_SSL_LOG_ERR("failed to read X509_NAME into BIO buffer");
304  }
305 
306  BIO_free(buffer);
307  }
308  }
309  }
310 
311  return result;
312 }
313 
314 int Certificate::subject_name_digest(std::vector<CORBA::Octet>& dst) const
315 {
316  dst.clear();
317 
318  if (!x_) return 1;
319 
320  /* Do not free name! */
321  X509_NAME* name = X509_get_subject_name(x_);
322  if (0 == name) {
323  OPENDDS_SSL_LOG_ERR("X509_get_subject_name failed");
324  return 1;
325  }
326 
327  std::vector<CORBA::Octet> tmp(EVP_MAX_MD_SIZE);
328 
329  unsigned int len = 0;
330  if (1 != X509_NAME_digest(name, EVP_sha256(), &tmp[0], &len)) {
331  OPENDDS_SSL_LOG_ERR("X509_NAME_digest failed");
332  return 1;
333  }
334 
335  dst.insert(dst.begin(), tmp.begin(), tmp.begin() + len);
336 
337  return 0;
338 }
339 
340 const char* Certificate::keypair_algo() const
341 {
342  // This should probably be pulling the information directly from
343  // the certificate.
344  if (std::string("RSASSA-PSS-SHA256") == dsign_algo_) {
345  return "RSA-2048";
346 
347  } else if (std::string("ECDSA-SHA256") == dsign_algo_) {
348  return "EC-prime256v1";
349 
350  } else {
351  return "UNKNOWN";
352  }
353 }
354 
356 {
357 #ifndef OPENSSL_V_3_0
358  cache_dsign_algo_impl() : pkey_(0), rsa_(0), ec_(0) {}
360  {
361  EVP_PKEY_free(pkey_);
362  RSA_free(rsa_);
363  EC_KEY_free(ec_);
364  }
365 #else
366  cache_dsign_algo_impl() : pkey_(0) {}
368  {
369  EVP_PKEY_free(pkey_);
370  }
371 #endif
372 
373  int operator() (X509* cert, std::string& dst)
374  {
375  if (!cert) {
377  "(%P|%t) SSL::Certificate::cache_dsign_algo: WARNING, failed to "
378  "get pubkey from X509 cert\n"));
379  return 1;
380  }
381 
382  pkey_ = X509_get_pubkey(cert);
383  if (!pkey_) {
384  OPENDDS_SSL_LOG_ERR("cache_dsign_algo_impl::operator(): x509_get_pubkey failed");
385  return 1;
386  }
387 
388 #ifndef OPENSSL_V_3_0
389  rsa_ = EVP_PKEY_get1_RSA(pkey_);
390  if (rsa_) {
391  dst = "RSASSA-PSS-SHA256";
392  return 0;
393  }
394 
395  ec_ = EVP_PKEY_get1_EC_KEY(pkey_);
396  if (ec_) {
397  dst = "ECDSA-SHA256";
398  return 0;
399  }
400 #else
401  const int ptype = EVP_PKEY_id (pkey_);
402  if (ptype == EVP_PKEY_RSA || ptype == EVP_PKEY_RSA_PSS) {
403  dst = "RSASSA-PSS-SHA256";
404  return 0;
405  } else if (ptype == EVP_PKEY_EC) {
406  dst = "ECDSA-SHA256";
407  return 0;
408  }
409 #endif
410 
412  "(%P|%t) SSL::Certificate::cache_dsign_algo: WARNING, only RSASSA-PSS-SHA256 or "
413  "ECDSA-SHA256 are currently supported signature/verification algorithms\n"));
414 
415  return 1;
416  }
417 
418 private:
420 #ifndef OPENSSL_V_3_0
421  RSA* rsa_;
422  EC_KEY* ec_;
423 #endif
424 };
425 
427 {
429 }
430 
431 void Certificate::load_cert_bytes(const std::string& path)
432 {
433 #ifdef ACE_ANDROID
434  CORBA::Octet *buffer;
435 
436  char b[1024];
437  FILE* fp = ACE_OS::fopen(path.c_str(), "rb");
438 
439  int n;
440  int i = 0;
441  while (!feof(fp)) {
442  n = ACE_OS::fread(&b, 1, 1024, fp);
443  i += n;
444 
445  original_bytes_.length(i + 1); // +1 for null byte at end of cert
446  buffer = original_bytes_.get_buffer();
447  ACE_OS::memcpy(buffer + i - n, b, n);
448  }
449 
450  ACE_OS::fclose(fp);
451 
452  // To appease the other DDS security implementations which
453  // append a null byte at the end of the cert.
454  buffer[i + 1] = 0u;
455 
456 #else
457  std::ifstream in(path.c_str(), std::ios::binary);
458 
459  if (!in) {
461  "(%P|%t) Certificate::load_cert_bytes:"
462  "WARNING: Failed to load file '%C'; '%m'\n",
463  path.c_str()));
464  return;
465  }
466 
467  const std::ifstream::pos_type begin = in.tellg();
468  in.seekg(0, std::ios::end);
469  const std::ifstream::pos_type end = in.tellg();
470  in.seekg(0, std::ios::beg);
471 
472  original_bytes_.length(static_cast<CORBA::ULong>(end - begin + 1));
473  in.read(reinterpret_cast<char*>(original_bytes_.get_buffer()), end - begin);
474 
475  if (!in) {
477  "(%P|%t) Certificate::load_cert_bytes:"
478  "WARNING: Failed to load file '%C'; '%m'\n",
479  path.c_str()));
480  return;
481  }
482 
483  // To appease the other DDS security implementations which
484  // append a null byte at the end of the cert.
485  original_bytes_[original_bytes_.length() - 1] = 0u;
486 #endif
487 }
488 
489 void Certificate::load_cert_data_bytes(const std::string& data)
490 {
491  // Start at position 1 because path contains a comma in element 0
492  // and that comma is not included in the cert string
493  // copy the full length to get the terminating null
494  original_bytes_.length(static_cast<unsigned int>(data.size()));
495  std::memcpy(original_bytes_.get_buffer(), data.c_str() + 1, data.size());
496 }
497 
498 X509* Certificate::x509_from_pem(const std::string& path,
499  const std::string& password)
500 {
501  X509* result = NULL;
502 
503  BIO* filebuf = BIO_new_file(path.c_str(), "r");
504  if (filebuf) {
505  if (!password.empty()) {
506  result =
507  PEM_read_bio_X509_AUX(filebuf, 0, 0, (void*)password.c_str());
508  if (!result) {
509  OPENDDS_SSL_LOG_ERR("PEM_read_bio_X509_AUX failed");
510  }
511 
512  } else {
513  result = PEM_read_bio_X509_AUX(filebuf, 0, 0, 0);
514  if (!result) {
515  OPENDDS_SSL_LOG_ERR("PEM_read_bio_X509_AUX failed");
516  }
517  }
518 
519  BIO_free(filebuf);
520 
521  } else {
522  std::stringstream errmsg;
523  errmsg << "failed to read file '" << path << "' using BIO_new_file";
524  OPENDDS_SSL_LOG_ERR(errmsg.str().c_str());
525  }
526 
527  return result;
528 }
529 
531  const std::string& password)
532 {
533  X509* result = 0;
534 
535  BIO* filebuf = BIO_new(BIO_s_mem());
536  do {
537  if (filebuf) {
538  if (0 >= BIO_write(filebuf, bytes.get_buffer(), bytes.length())) {
539  OPENDDS_SSL_LOG_ERR("BIO_write failed");
540  break;
541  }
542  if (!password.empty()) {
543  result = PEM_read_bio_X509_AUX(filebuf, 0, 0,
544  (void*)password.c_str());
545  if (!result) {
546  OPENDDS_SSL_LOG_ERR("PEM_read_bio_X509_AUX failed");
547  break;
548  }
549 
550  } else {
551  result = PEM_read_bio_X509_AUX(filebuf, 0, 0, 0);
552  if (!result) {
553  OPENDDS_SSL_LOG_ERR("PEM_read_bio_X509_AUX failed");
554  break;
555  }
556  }
557 
558  } else {
559  std::stringstream errmsg;
560  errmsg << "BIO_new failed";
561  OPENDDS_SSL_LOG_ERR(errmsg.str().c_str());
562  break;
563  }
564 
565  } while (0);
566 
567  BIO_free(filebuf);
568 
569  return result;
570 }
571 
573 {
574  dst = original_bytes_;
575 
576  if (dst.length() == original_bytes_.length()) {
577  return 0;
578  }
579 
580  return 1;
581 }
582 
584 {
585  explicit deserialize_impl(const DDS::OctetSeq& src)
586  : src_(src), buffer_(BIO_new(BIO_s_mem()))
587  {}
588 
590  {
591  BIO_free(buffer_);
592  }
593 
594  int operator() (X509*& dst)
595  {
596  if (dst) {
598  "(%P|%t) SSL::Certificate::deserialize: WARNING, an X509 certificate "
599  "has already been loaded\n"));
600  return 1;
601  }
602 
603  if (0 == src_.length()) {
605  "(%P|%t) SSL::Certificate::deserialize: WARNING, source OctetSeq contains no data"));
606  return 1;
607  }
608 
609  if (!buffer_) {
610  OPENDDS_SSL_LOG_ERR("failed to allocate buffer with BIO_new");
611  return 1;
612  }
613 
614  const int len = BIO_write(buffer_, src_.get_buffer(), src_.length());
615  if (len <= 0) {
616  OPENDDS_SSL_LOG_ERR("failed to write OctetSeq to BIO");
617  return 1;
618  }
619 
620  dst = PEM_read_bio_X509_AUX(buffer_, 0, 0, 0);
621  if (! dst) {
622  OPENDDS_SSL_LOG_ERR("failed to read X509 from BIO");
623  return 1;
624  }
625 
626  return 0;
627  }
628 
629 private:
631  BIO* buffer_;
632 };
633 
635 {
636  int err = deserialize_impl(src)(x_) || cache_dsign_algo();
637  if (!err) {
638  original_bytes_ = src;
639  }
640 
641  return err;
642 }
643 
644 std::ostream& operator<<(std::ostream& lhs, const Certificate& rhs)
645 {
646  if (rhs.x_) {
647  lhs << "Certificate: { is_ca? '"
648  << (X509_check_ca(rhs.x_) ? "yes" : "no") << "'; }";
649 
650  } else {
651  lhs << "NULL";
652  }
653  return lhs;
654 }
655 
656 bool operator==(const Certificate& lhs, const Certificate& rhs)
657 {
658  if (lhs.x_ && rhs.x_) {
659  return (0 == X509_cmp(lhs.x_, rhs.x_)) &&
660  (lhs.original_bytes_ == rhs.original_bytes_);
661  }
662  return (lhs.x_ == rhs.x_) &&
663  (lhs.original_bytes_ == rhs.original_bytes_);
664 }
665 
666 } // namespace SSL
667 } // namespace Security
668 } // namespace OpenDDS
669 
int fclose(FILE *fp)
This URI abstraction is currently naive and only separates the URI scheme on the LHS from the "everyt...
#define ACE_ERROR(X)
int deserialize(const DDS::OctetSeq &src)
EVP_PKEY * pkey_
SequenceBackInsertIterator< Sequence > back_inserter(Sequence &seq)
void * memcpy(void *t, const void *s, size_t len)
bool load(DDS::Security::SecurityException &ex, const std::string &uri, const std::string &password="")
Definition: Certificate.cpp:72
struct evp_pkey_st EVP_PKEY
int verify_signature(const DDS::OctetSeq &src, const std::vector< const DDS::OctetSeq *> &expected_contents) const
FILE * fopen(const char *filename, const char *mode)
size_t fread(void *ptr, size_t size, size_t nelems, FILE *fp)
int validate(const Certificate &ca, unsigned long int flags=0u) const
friend OpenDDS_Security_Export std::ostream & operator<<(std::ostream &, const Certificate &)
deserialize_impl(const DDS::OctetSeq &src)
friend OpenDDS_Security_Export bool operator==(const Certificate &lhs, const Certificate &rhs)
Certificate & operator=(const Certificate &rhs)
Definition: Certificate.cpp:61
LM_WARNING
sequence< octet > OctetSeq
Definition: DdsDcpsCore.idl:64
int subject_name_digest(std::vector< CORBA::Octet > &dst) const
const char *const name
Definition: debug.cpp:60
void load_cert_bytes(const std::string &path)
int operator()(const DDS::OctetSeq &src, const std::vector< const DDS::OctetSeq *> &expected_contents)
ACE_TEXT("TCP_Factory")
#define EVP_MD_CTX_new
#define EVP_MD_CTX_free
DDS::ReturnCode_t copy(DDS::DynamicData_ptr dest, DDS::DynamicData_ptr src)
ACE_CDR::Octet Octet
#define OPENDDS_END_VERSIONED_NAMESPACE_DECL
int subject_name_to_str(std::string &dst, unsigned long flags=XN_FLAG_ONELINE) const
int serialize(DDS::OctetSeq &dst) const
void load_cert_data_bytes(const std::string &data)
#define ACE_ERROR_RETURN(X, Y)
static X509 * x509_from_pem(const std::string &path, const std::string &password="")
bool set_security_error(DDS::Security::SecurityException &ex, int code, int minor_code, const char *message)
The Internal API and Implementation of OpenDDS.
Definition: AddressCache.h:28
struct x509_st X509
#define OPENDDS_SSL_LOG_ERR(MSG)
Definition: Err.h:12