dodo  0.0.1
A C++ library to create containerized Linux services
x509cert.hpp
Go to the documentation of this file.
1 /*
2  * This file is part of the dodo library (https://github.com/jmspit/dodo).
3  * Copyright (c) 2019 Jan-Marten Spit.
4  *
5  * This program is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation, version 3.
8  *
9  * This program is distributed in the hope that it will be useful, but
10  * WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12  * General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program. If not, see <http://www.gnu.org/licenses/>.
16  */
17 
18 /**
19  * @file x509cert.hpp
20  * Defines the dodo::network::SSLSocket class.
21  */
22 
23 #ifndef network_x509cert_hpp
24 #define network_x509cert_hpp
25 
26 #include <list>
27 #include <map>
28 #include <openssl/ssl.h>
29 #include <openssl/x509v3.h>
30 #include <string>
31 
32 namespace dodo::network {
33 
34  /**
35  * Interface common to X509 documents.
36  *
37  * See @ref developer_networking for details on the role of this class.
38  */
39  class X509Common {
40  public:
41 
42  /**
43  * The SubjectAltName type.
44  */
45  enum class SANType {
46  stDNS = GEN_DNS, /**< A DNS name such as myhost.mydomain.org */
47  stURI = GEN_URI, /**< An URI */
48  stEMAIL = GEN_EMAIL, /**< An email address */
49  stIP = GEN_IPADD /**< An IPv4 or IPv6 address */
50  };
51 
52  /**
53  * Convert the SANType name to a string.
54  * @param san_type The SANType to convert.
55  * @return the string representation.
56  */
57  static std::string SANTypeAsString( const SANType& san_type ) {
58  switch( san_type ) {
59  case SANType::stDNS : return "DNS";
60  case SANType::stURI : return "URI";
61  case SANType::stEMAIL : return "EMAIL";
62  case SANType::stIP : return "IP";
63  }
64  return "?";
65  }
66 
67  /**
68  * Subject AltName record.
69  */
70  struct SAN {
71  /**
72  * The type.
73  */
75 
76  /**
77  * The name.
78  */
79  std::string san_name;
80  };
81 
82 
83  /**
84  * Attributes that together constitute a X509 identity.
85  */
86  struct Identity {
87 
88  Identity() : countryCode(""), state(""), locality(""), organization(""), organizationUnit(""),
89  commonName(""),email("") {}
90 
91  /**
92  * A two-character country code, for example NL for The Netherlands.
93  */
94  std::string countryCode;
95 
96  /**
97  * The State or Province name.
98  */
99  std::string state;
100 
101  /**
102  * The locality name (city, town).
103  */
104  std::string locality;
105 
106  /**
107  * The organization name.
108  */
109  std::string organization;
110 
111  /**
112  * The organizational unit name.
113  */
114  std::string organizationUnit;
115 
116  /**
117  * The common name.
118  */
119  std::string commonName;
120 
121  /**
122  * The email address.
123  */
124  std::string email;
125 
126  /**
127  * The businessCategory
128  */
129  std::string businessCategory;
130 
131  /**
132  * The jurisdiction country code.
133  */
134  std::string jurisdictionC;
135 
136  /**
137  * The jurisdiction state.
138  */
139  std::string jurisdictionST;
140 
141  /**
142  * A cert serial number.
143  */
144  std::string serialNumber;
145 
146  /**
147  * The street address.
148  */
149  std::string street;
150 
151  /**
152  * The postal code.
153  */
154  std::string postalCode;
155 
156  /**
157  * Other key-value pairs in the identity.
158  */
159  std::map<std::string,std::string> other;
160  };
161 
162  /**
163  * Enumeration of X509 document types.
164  */
165  enum class X509Type {
166  Unknown, /**< Unknown PEM document */
167  PrivateKey, /**< Private key PEM document (possibly encrypted). */
168  PublicKey, /**< Public key PEM document */
169  CertificateSigningRequest, /**< CSR PEM document */
170  Certificate /**< Certificate PEM document */
171  };
172 
173  /**
174  * Detects a X509 document type from a PEM file.
175  * The PEM is not checked on validity, and a result other than X509Type::Unknown does not imply the document
176  * is well formed and valid. Note that both private key and encrypted private key PEM files are identified as
177  * X509Type::PrivateKey.
178  * @param file The file name to be content-checked.
179  * @param tag Receives the PEM tag (eg 'CERTIFICATE','PRIVATE KEY',..).
180  * @return the X509Type.
181  */
182  static X509Type detectX509Type( const std::string file, std::string &tag );
183 
184  protected:
185  /**
186  * Parse a subject or issuer string into an Identity.*
187  * @param src The identity string
188  * @return the Identity.
189  */
190  static Identity parseIdentity( const std::string src );
191 
192  private:
193  /** Never construct, interface class. */
194  X509Common() = delete;
195  /** Never destruct, interface class. */
196  ~X509Common() = delete;
197  };
198 
199  /**
200  * X509 Certificate signing request (CSR) interface. Note that this is an interface class, it does not
201  * manage ownership of X509_REQ structures.
202  *
203  * See @ref developer_networking for details on the role of this class.
204  */
206  public:
207 
208  /**
209  * Load a certificate signing request (CSR) from a PEM file. The X509_REQ object pointed to gets owned by
210  * the caller and must be freed with free( X509_REQ* cert ). Note that the call will fail if the file is
211  * not a CSR, even if it is a valid PEM file - such as a certificate or a private key in
212  * PEM format.
213  *
214  * @param file The filename to load from.
215  * @throw common::Exception when the openSSL BIO fails to create.
216  * @throw common::Exception when the file cannot be read.
217  * @throw common::Exception when the file is not a valid PEM file.
218  * @return Pointer to your X509_REQ.
219  */
220  static X509_REQ* loadPEM( const std::string file );
221 
222  /**
223  * Free / clean an X509 object.
224  * @param cert The X509_REQ object to free.
225  */
226  static void free( X509_REQ* cert ) { X509_REQ_free( cert ); }
227 
228  /**
229  * Get the CSR subject identity.
230  * @param cert The source CSR / X509_REQ.
231  * @return the CSR subject identity.
232  */
233  static X509Common::Identity getSubject( const X509_REQ *cert );
234 
235  /**
236  * Get the certificate fingerprint (a hash on the public key modulus) in string format,
237  * multiple hexadecimal bytes values separated by a colon.
238  * `openssl list -digest-algorithms` shows a full list of hash (digest) names. Stick to newer hash algorithms
239  * from the SHA-3 family.
240  *
241  * @throws common::Exception if the digest name is invalid.
242  *
243  * @see https://en.wikipedia.org/wiki/Secure_Hash_Algorithms
244  *
245  * @param cert A pointer to the X509 certificate.
246  * @param hashname The name of the hash algorithm to use. Defaults to 'shake256'. Names are case-insensitive.
247  * @return A string representation of the fingerprint.
248  */
249  static std::string getFingerPrint( const X509_REQ *cert, const std::string hashname = "shake256" );
250 
251 
252  private:
253  /** Never construct, interface class. */
255  /** Never destruct, interface class. */
257  };
258 
259  /**
260  * X509 public key certificate (PKC) interface. Note that this is an interface class, it does not
261  * manage ownership of X509 structures.
262  *
263  * See @ref developer_networking for details on the role of this class.
264  */
265  class X509Certificate : public X509Common {
266  public:
267 
268  /**
269  * Load a public key certificate (aka 'certificate') from a PEM file. The X509 object pointed to gets owned by
270  * the caller and must be freed when done with free( X509* cert ). Note that the call will fail if the file is
271  * not a public key certificate, even though it is a valid PEM file. Also note that the call will return ony the
272  * first certificate if the PEM file contains multiple certificates.
273  * @param file The PEM file to load from.
274  * @throw common::Exception when the openSSL BIO fails to create.
275  * @throw common::Exception when the file cannot be read.
276  * @throw common::Exception when the file is not a valid PEM file.
277  * @return Pointer to the X509 document.
278  */
279  static X509* loadPEM( const std::string file );
280 
281  /**
282  * Free / clean an X509 object.
283  * @param cert A pointer to the X509 certificate.
284  */
285  static void free( X509* cert ) { X509_free( cert ); }
286 
287  /**
288  * Get the certificate issuer.
289  * @param cert A pointer to the X509 certificate.
290  * @return the issuer string.
291  */
292  static X509Common::Identity getIssuer( const X509 *cert );
293 
294  /**
295  * Get the certificate serial number as concatenated hex bytes. Note that the serial number is only supposed to be unique
296  * among certificates signed by a single CA. To truly identify certificates, use getFingerPrint().
297  * @param cert The source PKC / X509.
298  * @return the serial string.
299  */
300  static std::string getSerial( const X509 *cert );
301 
302  /**
303  * Get the certificate subject identity.
304  * @param cert A pointer to the X509 certificate.
305  * @return the subject identity.
306  */
307  static X509Common::Identity getSubject( const X509 *cert );
308 
309  /**
310  * Get the SAN (subject alternate name) list for the certificate, which may be empty.
311  * @param cert A pointer to the X509 certificate.
312  * @return A list of SAN.
313  */
314  static std::list<X509Common::SAN> getSubjectAltNames( const X509* cert );
315 
316  /**
317  * Get the certificate fingerprint (a hash on the public key modulus) in string format,
318  * multiple hexadecimal bytes values separated by a colon.
319  * `openssl list -digest-algorithms` shows a full list of hash (digest) names. Stick to newer hash algorithms
320  * from the SHA-3 family.
321  *
322  * @throws common::Exception if the digest name is invalid.
323  *
324  * @see https://en.wikipedia.org/wiki/Secure_Hash_Algorithms
325  *
326  * @param cert A pointer to the X509 certificate.
327  * @param hashname The name of the hash algorithm to use. Defaults to 'shake256'. Names are case-insensitive.
328  * @return A string representation of the fingerprint.
329  */
330  static std::string getFingerPrint( const X509 *cert, const std::string hashname = "shake256" );
331 
332  /**
333  * Verify a peer name against this certificate's CN and SubjectAltnames.
334  *
335  * The name is always matched against the CN, regardless of the X509Common::SANType.
336  * The name is matched against SubjectAltNames that match the X509Common::SANType.
337  *
338  * @param cert A pointer to the X509 certificate.
339  * @param san The SAN structure to compare against.
340  * @param wildcards If true, allow wildcards.
341  * @return true if the name matches.
342  */
343  static bool verifySAN( const X509 *cert, const SAN &san, bool wildcards = false );
344 
345  private:
346 
347  /**
348  * Verify a peer name matches a SAN.
349  * @param peer The name of the peer.
350  * @param san The SubjectAltname of the peer.
351  * @param wildcards If true, allow wildcards.
352  * @return true when the name matches.
353  */
354  static bool verifyName( const std::string &peer, const std::string &san, bool wildcards = false );
355 
356  /**
357  * Verify a peer IP matches a SAN of type stIP. The strings are converted to IP addresses, both must be valid IP
358  * addresses and they must be equal ( Address::operator==( const Address &) ).
359  * @param peer The ipv4 or ipv6 of the peer (as a string).
360  * @param san The ipv4 or ipv6 SubjectAltname of the peer (as a string).
361  * @return true when the IP matches.
362  */
363  static bool verifyIP( const std::string &peer, const std::string &san );
364 
365  /** Never construct, interface class. */
366  X509Certificate() = delete;
367  /** Never destruct, interface class. */
368  ~X509Certificate() = delete;
369  };
370 
371 }
372 
373 #endif
dodo::network::X509Common
Interface common to X509 documents.
Definition: x509cert.hpp:39
dodo::network::X509CertificateSigningRequest::free
static void free(X509_REQ *cert)
Free / clean an X509 object.
Definition: x509cert.hpp:226
dodo::network::X509Common::Identity::state
std::string state
The State or Province name.
Definition: x509cert.hpp:99
dodo::network::X509Common::SAN
Subject AltName record.
Definition: x509cert.hpp:70
dodo::network::X509Common::Identity
Attributes that together constitute a X509 identity.
Definition: x509cert.hpp:86
dodo::network::X509Common::Identity::serialNumber
std::string serialNumber
A cert serial number.
Definition: x509cert.hpp:144
dodo::network::X509Common::detectX509Type
static X509Type detectX509Type(const std::string file, std::string &tag)
Detects a X509 document type from a PEM file.
Definition: x509cert.cpp:41
dodo::network::X509Common::SANTypeAsString
static std::string SANTypeAsString(const SANType &san_type)
Convert the SANType name to a string.
Definition: x509cert.hpp:57
dodo::network::X509Certificate::free
static void free(X509 *cert)
Free / clean an X509 object.
Definition: x509cert.hpp:285
dodo::network::X509Common::X509Type::Certificate
@ Certificate
Certificate PEM document.
dodo::network::X509Certificate::getIssuer
static X509Common::Identity getIssuer(const X509 *cert)
Get the certificate issuer.
Definition: x509cert.cpp:163
dodo::network::X509CertificateSigningRequest
X509 Certificate signing request (CSR) interface.
Definition: x509cert.hpp:205
dodo::network::X509CertificateSigningRequest::loadPEM
static X509_REQ * loadPEM(const std::string file)
Load a certificate signing request (CSR) from a PEM file.
Definition: x509cert.cpp:92
dodo::network::X509Common::~X509Common
~X509Common()=delete
Never destruct, interface class.
dodo::network::X509Certificate::loadPEM
static X509 * loadPEM(const std::string file)
Load a public key certificate (aka 'certificate') from a PEM file.
Definition: x509cert.cpp:143
dodo::network::X509Common::X509Type::Unknown
@ Unknown
Unknown PEM document.
dodo::network::X509Certificate::verifySAN
static bool verifySAN(const X509 *cert, const SAN &san, bool wildcards=false)
Verify a peer name against this certificate's CN and SubjectAltnames.
Definition: x509cert.cpp:278
dodo::network::X509Certificate::X509Certificate
X509Certificate()=delete
Never construct, interface class.
dodo::network::X509Common::Identity::organizationUnit
std::string organizationUnit
The organizational unit name.
Definition: x509cert.hpp:114
dodo::network::X509Common::SANType::stDNS
@ stDNS
A DNS name such as myhost.mydomain.org.
dodo::network::X509Common::Identity::street
std::string street
The street address.
Definition: x509cert.hpp:149
dodo::network::X509CertificateSigningRequest::X509CertificateSigningRequest
X509CertificateSigningRequest()=delete
Never construct, interface class.
dodo::network::X509Common::Identity::businessCategory
std::string businessCategory
The businessCategory.
Definition: x509cert.hpp:129
dodo::network::X509Certificate::getFingerPrint
static std::string getFingerPrint(const X509 *cert, const std::string hashname="shake256")
Get the certificate fingerprint (a hash on the public key modulus) in string format,...
Definition: x509cert.cpp:230
dodo::network::X509Common::SANType::stIP
@ stIP
An IPv4 or IPv6 address.
dodo::network::X509Common::parseIdentity
static Identity parseIdentity(const std::string src)
Parse a subject or issuer string into an Identity.
Definition: x509cert.cpp:61
dodo::network::X509Common::Identity::countryCode
std::string countryCode
A two-character country code, for example NL for The Netherlands.
Definition: x509cert.hpp:94
dodo::network::X509Common::X509Type::PublicKey
@ PublicKey
Public key PEM document.
dodo::network::X509Common::Identity::jurisdictionC
std::string jurisdictionC
The jurisdiction country code.
Definition: x509cert.hpp:134
dodo::network::X509Common::Identity::postalCode
std::string postalCode
The postal code.
Definition: x509cert.hpp:154
dodo::network::X509Common::X509Common
X509Common()=delete
Never construct, interface class.
dodo::network::X509Certificate::getSubject
static X509Common::Identity getSubject(const X509 *cert)
Get the certificate subject identity.
Definition: x509cert.cpp:182
dodo::network::X509Certificate
X509 public key certificate (PKC) interface.
Definition: x509cert.hpp:265
dodo::network::X509Common::Identity::jurisdictionST
std::string jurisdictionST
The jurisdiction state.
Definition: x509cert.hpp:139
dodo::network::X509Certificate::verifyIP
static bool verifyIP(const std::string &peer, const std::string &san)
Verify a peer IP matches a SAN of type stIP.
Definition: x509cert.cpp:269
dodo::network::X509Common::SANType
SANType
The SubjectAltName type.
Definition: x509cert.hpp:45
dodo::network::X509Common::SAN::san_type
X509Common::SANType san_type
The type.
Definition: x509cert.hpp:74
dodo::network::X509Common::SANType::stURI
@ stURI
An URI.
dodo::network::X509Common::Identity::organization
std::string organization
The organization name.
Definition: x509cert.hpp:109
dodo::network::X509Common::Identity::other
std::map< std::string, std::string > other
Other key-value pairs in the identity.
Definition: x509cert.hpp:159
dodo::network
Interface for network communication.
Definition: address.hpp:37
dodo::network::X509Common::SANType::stEMAIL
@ stEMAIL
An email address.
dodo::network::X509Certificate::getSubjectAltNames
static std::list< X509Common::SAN > getSubjectAltNames(const X509 *cert)
Get the SAN (subject alternate name) list for the certificate, which may be empty.
Definition: x509cert.cpp:192
dodo::network::X509Common::Identity::locality
std::string locality
The locality name (city, town).
Definition: x509cert.hpp:104
dodo::network::X509Certificate::~X509Certificate
~X509Certificate()=delete
Never destruct, interface class.
dodo::network::X509Common::SAN::san_name
std::string san_name
The name.
Definition: x509cert.hpp:79
dodo::network::X509CertificateSigningRequest::~X509CertificateSigningRequest
~X509CertificateSigningRequest()=delete
Never destruct, interface class.
dodo::network::X509CertificateSigningRequest::getFingerPrint
static std::string getFingerPrint(const X509_REQ *cert, const std::string hashname="shake256")
Get the certificate fingerprint (a hash on the public key modulus) in string format,...
Definition: x509cert.cpp:121
dodo::network::X509Certificate::getSerial
static std::string getSerial(const X509 *cert)
Get the certificate serial number as concatenated hex bytes.
Definition: x509cert.cpp:172
dodo::network::X509Common::X509Type::CertificateSigningRequest
@ CertificateSigningRequest
CSR PEM document.
dodo::network::X509CertificateSigningRequest::getSubject
static X509Common::Identity getSubject(const X509_REQ *cert)
Get the CSR subject identity.
Definition: x509cert.cpp:112
dodo::network::X509Common::Identity::commonName
std::string commonName
The common name.
Definition: x509cert.hpp:119
dodo::network::X509Common::Identity::email
std::string email
The email address.
Definition: x509cert.hpp:124
dodo::network::X509Common::X509Type::PrivateKey
@ PrivateKey
Private key PEM document (possibly encrypted).
dodo::network::X509Common::X509Type
X509Type
Enumeration of X509 document types.
Definition: x509cert.hpp:165
dodo::network::X509Certificate::verifyName
static bool verifyName(const std::string &peer, const std::string &san, bool wildcards=false)
Verify a peer name matches a SAN.
Definition: x509cert.cpp:248