1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253
|
// OpenVPN -- An application to securely tunnel IP networks
// over a single port, with support for SSL/TLS-based
// session authentication and key exchange,
// packet encryption, packet authentication, and
// packet compression.
//
// Copyright (C) 2012- OpenVPN Inc.
//
// SPDX-License-Identifier: MPL-2.0 OR AGPL-3.0-only WITH openvpn3-openssl-exception
//
//
//
// Generic functions for extracting X.509 Certificate info from
// OpenSSL X509 objects
#pragma once
#include <cstring>
#include <string>
#include <vector>
#include <openssl/ssl.h>
#include <openssl/bio.h>
#include <openssl/x509v3.h>
#include <openssl/x509.h>
#include "openvpn/common/hexstr.hpp"
#include "openvpn/common/uniqueptr.hpp"
namespace openvpn::OpenSSLPKI {
/**
* Retrieve the complete X.509 Certificate Subject field
*
* OpenSSL supports two ways of representing the subject line. The old
* format is deprecated, but there might be code expecting this old format.
* The old format looks like this:
*
* /C=KG/ST=NA/O=OpenVPN-TEST/CN=Test-Server/emailAddress=me@myhost.mydomain
*
* The new format is UTF-8 compliant and has a different formatting scheme:
*
* C=KG, ST=NA, O=OpenVPN-TEST, CN=Test-Server,
*emailAddress=me@myhost.mydomain
*
*
* @param cert Pointer to a native OpenSSL X509 object containing the
* certificate
* @param new_format (optional, default: false) Which format to use,
* true indicates the new format
*
* @return Returns a std::string containing the complete certificate subject.
* If it was not possible to retrieve the subject, and empty string
* is returned.
*/
static inline std::string x509_get_subject(::X509 *cert, bool new_format = false)
{
if (!new_format)
{
unique_ptr_del<char> subject(
X509_NAME_oneline(X509_get_subject_name(cert), nullptr, 0),
[](char *p)
{ OPENSSL_free(p); });
if (subject)
return std::string(subject.get());
else
return std::string("");
}
unique_ptr_del<BIO> subject_bio(BIO_new(BIO_s_mem()),
[](BIO *p)
{ BIO_free(p); });
if (subject_bio == nullptr)
{
return std::string("");
}
X509_NAME_print_ex(subject_bio.get(),
X509_get_subject_name(cert),
0,
XN_FLAG_SEP_CPLUS_SPC
| XN_FLAG_FN_SN
| ASN1_STRFLGS_UTF8_CONVERT
| ASN1_STRFLGS_ESC_CTRL);
if (BIO_eof(subject_bio.get()))
{
return std::string("");
}
BUF_MEM *subject_mem = nullptr;
BIO_get_mem_ptr(subject_bio.get(), &subject_mem);
return std::string(subject_mem->data,
subject_mem->data + subject_mem->length);
}
static inline std::string X509_get_pem_encoding(::X509 *cert)
{
char *data;
BIO *bio = BIO_new(BIO_s_mem());
/* Even though PEM_write_bio_X509 should not modify the argument the official API does not have a const argument */
PEM_write_bio_X509(bio, cert);
size_t len = BIO_get_mem_data(bio, &data);
std::string certpem{data, len};
BIO_free(bio);
return certpem;
}
/**
* Retrives the algorithm used to sign a X509 certificate
* @param cert OpenSSL certificate
* @return
*/
static inline std::string x509_get_signature_algorithm(const ::X509 *cert)
{
int nid = X509_get_signature_nid(cert);
const char *sig = OBJ_nid2sn(nid);
if (sig)
{
return sig;
}
else
return "(error getting signature algorithm)";
}
/**
* Retrieves a specific portion of the X.509 Certificate subject field
*
* @param cert Pointer to a native OpenSSL X509 object containing the
* certificate
* @param nid Subject name ID to retrieve. See openssl/obj_mac.h for
* list of valid NID_* references.
*
* @return Returns the contents of the extracted field on success. The
* resulting string may be empty if the extraction failed or the field
* is empty.
*/
static inline std::string x509_get_field(::X509 *cert, const int nid)
{
static const char nullc = '\0';
std::string ret;
X509_NAME *x509_name = X509_get_subject_name(cert);
int i = X509_NAME_get_index_by_NID(x509_name, nid, -1);
if (i >= 0)
{
X509_NAME_ENTRY *ent = X509_NAME_get_entry(x509_name, i);
if (ent)
{
ASN1_STRING *val = X509_NAME_ENTRY_get_data(ent);
unsigned char *buf;
buf = (unsigned char *)1; // bug in OpenSSL 0.9.6b ASN1_STRING_to_UTF8
// requires this workaround
const int len = ASN1_STRING_to_UTF8(&buf, val);
if (len > 0)
{
if (std::strlen((char *)buf) == static_cast<unsigned int>(len))
ret = (char *)buf;
OPENSSL_free(buf);
}
}
}
else
{
i = X509_get_ext_by_NID(cert, nid, -1);
if (i >= 0)
{
X509_EXTENSION *ext = X509_get_ext(cert, i);
if (ext)
{
BIO *bio = BIO_new(BIO_s_mem());
if (bio)
{
if (X509V3_EXT_print(bio, ext, 0, 0))
{
if (BIO_write(bio, &nullc, 1) == 1)
{
char *str;
const long len = BIO_get_mem_data(bio, &str);
if (std::strlen(str) == static_cast<size_t>(len))
ret = str;
}
}
BIO_free(bio);
}
}
}
}
return ret;
}
/**
* Retrieves the X.509 certificate serial number
*
* @param cert Pointer to a native OpenSSL X509 object containing the
* certificate
*
* @return Returns the numeric representation of the certificate serial number
* as a std::string.
*/
static inline std::string x509_get_serial(::X509 *cert)
{
const ASN1_INTEGER *asn1_i = X509_get_serialNumber(cert);
BIGNUM *bignum = ASN1_INTEGER_to_BN(asn1_i, NULL);
char *openssl_serial = BN_bn2dec(bignum);
BN_free(bignum);
if (openssl_serial)
{
const std::string ret = openssl_serial;
OPENSSL_free(openssl_serial);
return ret;
}
return std::string();
}
/**
* Retrieves the X.509 certificate serial number as hexadecimal
*
* @param cert Pointer to a native OpenSSL X509 object containing the
* certificate
*
* @return Returns the hexadecimal representation of the certificate
* serial number as a std::string.
*/
static inline std::string x509_get_serial_hex(::X509 *cert)
{
const ASN1_INTEGER *asn1_i = X509_get_serialNumber(cert);
return render_hex_sep(asn1_i->data, asn1_i->length, ':', false);
}
/**
* Retrieves the X.509 certificate SHA256 fingerprint as binary
*
* @return Returns a uint8_t std:vector containing the binary representation
* of the certificate's SHA256 fingerprint.
*/
static inline std::size_t x509_fingerprint_size()
{
return EVP_MD_size(EVP_sha256());
}
static inline std::vector<uint8_t> x509_get_fingerprint(const ::X509 *cert)
{
std::vector<uint8_t> fingerprint;
fingerprint.resize(x509_fingerprint_size());
if (::X509_digest(cert, EVP_sha256(), fingerprint.data(), NULL) != 1)
throw OpenSSLException("OpenSSL error while calling X509_digest()");
return fingerprint;
}
} // namespace openvpn::OpenSSLPKI
|