1 | /* |
2 | * Copyright 2017-present Facebook, Inc. |
3 | * |
4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
5 | * you may not use this file except in compliance with the License. |
6 | * You may obtain a copy of the License at |
7 | * |
8 | * http://www.apache.org/licenses/LICENSE-2.0 |
9 | * |
10 | * Unless required by applicable law or agreed to in writing, software |
11 | * distributed under the License is distributed on an "AS IS" BASIS, |
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
13 | * See the License for the specific language governing permissions and |
14 | * limitations under the License. |
15 | */ |
16 | #include <folly/ssl/OpenSSLCertUtils.h> |
17 | |
18 | #include <folly/FileUtil.h> |
19 | #include <folly/ScopeGuard.h> |
20 | #include <folly/String.h> |
21 | #include <folly/ssl/OpenSSLPtrTypes.h> |
22 | |
23 | namespace folly { |
24 | namespace ssl { |
25 | |
26 | namespace { |
27 | std::string getOpenSSLErrorString(unsigned long err) { |
28 | std::array<char, 256> errBuff; |
29 | ERR_error_string_n(err, errBuff.data(), errBuff.size()); |
30 | return std::string(errBuff.data()); |
31 | } |
32 | } // namespace |
33 | |
34 | Optional<std::string> OpenSSLCertUtils::getCommonName(X509& x509) { |
35 | auto subject = X509_get_subject_name(&x509); |
36 | if (!subject) { |
37 | return none; |
38 | } |
39 | |
40 | auto cnLoc = X509_NAME_get_index_by_NID(subject, NID_commonName, -1); |
41 | if (cnLoc < 0) { |
42 | return none; |
43 | } |
44 | |
45 | auto cnEntry = X509_NAME_get_entry(subject, cnLoc); |
46 | if (!cnEntry) { |
47 | return none; |
48 | } |
49 | |
50 | auto cnAsn = X509_NAME_ENTRY_get_data(cnEntry); |
51 | if (!cnAsn) { |
52 | return none; |
53 | } |
54 | |
55 | auto cnData = reinterpret_cast<const char*>(ASN1_STRING_get0_data(cnAsn)); |
56 | auto cnLen = ASN1_STRING_length(cnAsn); |
57 | if (!cnData || cnLen <= 0) { |
58 | return none; |
59 | } |
60 | |
61 | return Optional<std::string>(std::string(cnData, cnLen)); |
62 | } |
63 | |
64 | std::vector<std::string> OpenSSLCertUtils::getSubjectAltNames(X509& x509) { |
65 | auto names = reinterpret_cast<STACK_OF(GENERAL_NAME)*>( |
66 | X509_get_ext_d2i(&x509, NID_subject_alt_name, nullptr, nullptr)); |
67 | if (!names) { |
68 | return {}; |
69 | } |
70 | SCOPE_EXIT { |
71 | sk_GENERAL_NAME_pop_free(names, GENERAL_NAME_free); |
72 | }; |
73 | |
74 | std::vector<std::string> ret; |
75 | auto count = sk_GENERAL_NAME_num(names); |
76 | for (int i = 0; i < count; i++) { |
77 | auto genName = sk_GENERAL_NAME_value(names, i); |
78 | if (!genName || genName->type != GEN_DNS) { |
79 | continue; |
80 | } |
81 | auto nameData = reinterpret_cast<const char*>( |
82 | ASN1_STRING_get0_data(genName->d.dNSName)); |
83 | auto nameLen = ASN1_STRING_length(genName->d.dNSName); |
84 | if (!nameData || nameLen <= 0) { |
85 | continue; |
86 | } |
87 | ret.emplace_back(nameData, nameLen); |
88 | } |
89 | return ret; |
90 | } |
91 | |
92 | Optional<std::string> OpenSSLCertUtils::getSubject(X509& x509) { |
93 | auto subject = X509_get_subject_name(&x509); |
94 | if (!subject) { |
95 | return none; |
96 | } |
97 | |
98 | auto bio = BioUniquePtr(BIO_new(BIO_s_mem())); |
99 | if (bio == nullptr) { |
100 | throw std::runtime_error("Cannot allocate bio" ); |
101 | } |
102 | if (X509_NAME_print_ex(bio.get(), subject, 0, XN_FLAG_ONELINE) <= 0) { |
103 | return none; |
104 | } |
105 | |
106 | char* bioData = nullptr; |
107 | size_t bioLen = BIO_get_mem_data(bio.get(), &bioData); |
108 | return std::string(bioData, bioLen); |
109 | } |
110 | |
111 | Optional<std::string> OpenSSLCertUtils::getIssuer(X509& x509) { |
112 | auto issuer = X509_get_issuer_name(&x509); |
113 | if (!issuer) { |
114 | return none; |
115 | } |
116 | |
117 | auto bio = BioUniquePtr(BIO_new(BIO_s_mem())); |
118 | if (bio == nullptr) { |
119 | throw std::runtime_error("Cannot allocate bio" ); |
120 | } |
121 | |
122 | if (X509_NAME_print_ex(bio.get(), issuer, 0, XN_FLAG_ONELINE) <= 0) { |
123 | return none; |
124 | } |
125 | |
126 | char* bioData = nullptr; |
127 | size_t bioLen = BIO_get_mem_data(bio.get(), &bioData); |
128 | return std::string(bioData, bioLen); |
129 | } |
130 | |
131 | folly::Optional<std::string> OpenSSLCertUtils::toString(X509& x509) { |
132 | auto in = BioUniquePtr(BIO_new(BIO_s_mem())); |
133 | if (in == nullptr) { |
134 | throw std::runtime_error("Cannot allocate bio" ); |
135 | } |
136 | |
137 | int flags = 0; |
138 | |
139 | flags |= X509_FLAG_NO_HEADER | /* A few bytes of cert and data */ |
140 | X509_FLAG_NO_PUBKEY | /* Public key */ |
141 | X509_FLAG_NO_AUX | /* Auxiliary info? */ |
142 | X509_FLAG_NO_SIGDUMP | /* Prints the signature */ |
143 | X509_FLAG_NO_SIGNAME; /* Signature algorithms */ |
144 | |
145 | #ifdef X509_FLAG_NO_IDS |
146 | flags |= X509_FLAG_NO_IDS; /* Issuer/subject IDs */ |
147 | #endif |
148 | |
149 | if (X509_print_ex(in.get(), &x509, XN_FLAG_ONELINE, flags) > 0) { |
150 | char* bioData = nullptr; |
151 | size_t bioLen = BIO_get_mem_data(in.get(), &bioData); |
152 | return std::string(bioData, bioLen); |
153 | } else { |
154 | return none; |
155 | } |
156 | } |
157 | |
158 | std::string OpenSSLCertUtils::getNotAfterTime(X509& x509) { |
159 | return getDateTimeStr(X509_get_notAfter(&x509)); |
160 | } |
161 | |
162 | std::string OpenSSLCertUtils::getNotBeforeTime(X509& x509) { |
163 | return getDateTimeStr(X509_get_notBefore(&x509)); |
164 | } |
165 | |
166 | std::string OpenSSLCertUtils::getDateTimeStr(const ASN1_TIME* time) { |
167 | if (!time) { |
168 | return "" ; |
169 | } |
170 | |
171 | auto bio = BioUniquePtr(BIO_new(BIO_s_mem())); |
172 | if (bio == nullptr) { |
173 | throw std::runtime_error("Cannot allocate bio" ); |
174 | } |
175 | |
176 | if (ASN1_TIME_print(bio.get(), time) <= 0) { |
177 | throw std::runtime_error("Cannot print ASN1_TIME" ); |
178 | } |
179 | |
180 | char* bioData = nullptr; |
181 | size_t bioLen = BIO_get_mem_data(bio.get(), &bioData); |
182 | return std::string(bioData, bioLen); |
183 | } |
184 | |
185 | X509UniquePtr OpenSSLCertUtils::derDecode(ByteRange range) { |
186 | auto begin = range.data(); |
187 | X509UniquePtr cert(d2i_X509(nullptr, &begin, range.size())); |
188 | if (!cert) { |
189 | throw std::runtime_error("could not read cert" ); |
190 | } |
191 | return cert; |
192 | } |
193 | |
194 | std::unique_ptr<IOBuf> OpenSSLCertUtils::derEncode(X509& x509) { |
195 | auto len = i2d_X509(&x509, nullptr); |
196 | if (len < 0) { |
197 | throw std::runtime_error("Error computing length" ); |
198 | } |
199 | auto buf = IOBuf::create(len); |
200 | auto dataPtr = buf->writableData(); |
201 | len = i2d_X509(&x509, &dataPtr); |
202 | if (len < 0) { |
203 | throw std::runtime_error("Error converting cert to DER" ); |
204 | } |
205 | buf->append(len); |
206 | return buf; |
207 | } |
208 | |
209 | std::vector<X509UniquePtr> OpenSSLCertUtils::readCertsFromBuffer( |
210 | ByteRange range) { |
211 | BioUniquePtr b( |
212 | BIO_new_mem_buf(const_cast<unsigned char*>(range.data()), range.size())); |
213 | if (!b) { |
214 | throw std::runtime_error("failed to create BIO" ); |
215 | } |
216 | std::vector<X509UniquePtr> certs; |
217 | ERR_clear_error(); |
218 | while (true) { |
219 | X509UniquePtr x509(PEM_read_bio_X509(b.get(), nullptr, nullptr, nullptr)); |
220 | if (x509) { |
221 | certs.push_back(std::move(x509)); |
222 | continue; |
223 | } |
224 | auto err = ERR_get_error(); |
225 | ERR_clear_error(); |
226 | if (BIO_eof(b.get()) && ERR_GET_LIB(err) == ERR_LIB_PEM && |
227 | ERR_GET_REASON(err) == PEM_R_NO_START_LINE) { |
228 | // Reach end of buffer. |
229 | break; |
230 | } |
231 | throw std::runtime_error(folly::to<std::string>( |
232 | "Unable to parse cert " , |
233 | certs.size(), |
234 | ": " , |
235 | getOpenSSLErrorString(err))); |
236 | } |
237 | return certs; |
238 | } |
239 | |
240 | std::array<uint8_t, SHA_DIGEST_LENGTH> OpenSSLCertUtils::getDigestSha1( |
241 | X509& x509) { |
242 | unsigned int len; |
243 | std::array<uint8_t, SHA_DIGEST_LENGTH> md; |
244 | int rc = X509_digest(&x509, EVP_sha1(), md.data(), &len); |
245 | |
246 | if (rc <= 0) { |
247 | throw std::runtime_error("Could not calculate SHA1 digest for cert" ); |
248 | } |
249 | return md; |
250 | } |
251 | |
252 | std::array<uint8_t, SHA256_DIGEST_LENGTH> OpenSSLCertUtils::getDigestSha256( |
253 | X509& x509) { |
254 | unsigned int len; |
255 | std::array<uint8_t, SHA256_DIGEST_LENGTH> md; |
256 | int rc = X509_digest(&x509, EVP_sha256(), md.data(), &len); |
257 | |
258 | if (rc <= 0) { |
259 | throw std::runtime_error("Could not calculate SHA256 digest for cert" ); |
260 | } |
261 | return md; |
262 | } |
263 | |
264 | X509StoreUniquePtr OpenSSLCertUtils::readStoreFromFile(std::string caFile) { |
265 | std::string certData; |
266 | if (!folly::readFile(caFile.c_str(), certData)) { |
267 | throw std::runtime_error( |
268 | folly::to<std::string>("Could not read store file: " , caFile)); |
269 | } |
270 | return readStoreFromBuffer(folly::StringPiece(certData)); |
271 | } |
272 | |
273 | X509StoreUniquePtr OpenSSLCertUtils::readStoreFromBuffer(ByteRange certRange) { |
274 | auto certs = readCertsFromBuffer(certRange); |
275 | ERR_clear_error(); |
276 | folly::ssl::X509StoreUniquePtr store(X509_STORE_new()); |
277 | for (auto& caCert : certs) { |
278 | if (X509_STORE_add_cert(store.get(), caCert.get()) != 1) { |
279 | auto err = ERR_get_error(); |
280 | if (ERR_GET_LIB(err) != ERR_LIB_X509 || |
281 | ERR_GET_REASON(err) != X509_R_CERT_ALREADY_IN_HASH_TABLE) { |
282 | throw std::runtime_error(folly::to<std::string>( |
283 | "Could not insert CA certificate into store: " , |
284 | getOpenSSLErrorString(err))); |
285 | } |
286 | } |
287 | } |
288 | return store; |
289 | } |
290 | } // namespace ssl |
291 | } // namespace folly |
292 | |