Add client cache class for manage data robustly
[platform/core/security/pubkey-pinning.git] / src / gnutls / tpkp_gnutls.cpp
1 /*
2  * Copyright (c) 2015 Samsung Electronics Co., Ltd All Rights Reserved
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 /*
17  * @file        tpkp_gnutls.cpp
18  * @author      Kyungwook Tak (k.tak@samsung.com)
19  * @version     1.0
20  * @brief       Tizen Https Public Key Pinning implementation for gnutls.
21  */
22 #include "tpkp_gnutls.h"
23
24 #include <string>
25 #include <memory>
26 #include <map>
27 #include <mutex>
28
29 #include <gnutls/gnutls.h>
30 #include <gnutls/abstract.h>
31 #include <gnutls/x509.h>
32
33 #include "tpkp_common.h"
34 #include "tpkp_client_cache.h"
35
36 namespace {
37
38 TPKP::ClientCache g_cache;
39
40 inline int err_tpkp_to_gnutlse(tpkp_e err) noexcept
41 {
42         switch (err) {
43         case TPKP_E_NONE:                     return GNUTLS_E_SUCCESS;
44         case TPKP_E_MEMORY:                   return GNUTLS_E_MEMORY_ERROR;
45         case TPKP_E_INVALID_URL:              return GNUTLS_E_INVALID_SESSION;
46         case TPKP_E_NO_URL_DATA:              return GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE;
47         case TPKP_E_PUBKEY_MISMATCH:          return GNUTLS_E_CERTIFICATE_KEY_MISMATCH;
48         case TPKP_E_INVALID_CERT:
49         case TPKP_E_INVALID_PEER_CERT_CHAIN:
50         case TPKP_E_FAILED_GET_PUBKEY_HASH:   return GNUTLS_E_PK_SIG_VERIFY_FAILED;
51         case TPKP_E_CERT_VERIFICATION_FAILED: return GNUTLS_E_CERTIFICATE_ERROR;
52         case TPKP_E_STD_EXCEPTION:
53         case TPKP_E_INTERNAL:
54         default:                              return GNUTLS_E_INTERNAL_ERROR;
55         }
56 }
57
58 using GnutlsX509Ptr = std::unique_ptr<gnutls_x509_crt_t, void(*)(gnutls_x509_crt_t *)>;
59 inline GnutlsX509Ptr createGnutlsX509Ptr(void)
60 {
61         return GnutlsX509Ptr(new gnutls_x509_crt_t, [](gnutls_x509_crt_t *ptr) {
62                 if (!!ptr) gnutls_x509_crt_deinit(*ptr);
63         });
64 }
65
66 TPKP::RawBuffer getPubkeyHash(gnutls_x509_crt_t cert, TPKP::HashAlgo algo)
67 {
68         std::unique_ptr<gnutls_pubkey_t, void(*)(gnutls_pubkey_t *)>
69                 pubkeyPtr(new gnutls_pubkey_t, [](gnutls_pubkey_t *ptr)->void
70                         {
71                                 if (ptr != nullptr)
72                                         gnutls_pubkey_deinit(*ptr);
73                         });
74
75         int ret = gnutls_pubkey_init(pubkeyPtr.get());
76         TPKP_CHECK_THROW_EXCEPTION(ret == GNUTLS_E_SUCCESS,
77                 TPKP_E_INTERNAL,
78                 "Failed to gnutls_pubkey_init. gnutls ret: " << ret);
79
80         ret = gnutls_pubkey_import_x509(*pubkeyPtr, cert, 0);
81         TPKP_CHECK_THROW_EXCEPTION(ret == GNUTLS_E_SUCCESS,
82                 TPKP_E_INVALID_CERT,
83                 "Failed to gnutls_pubkey_import_x509. gnutls ret: " << ret);
84
85         size_t len = 0;
86         ret = gnutls_pubkey_export(*pubkeyPtr, GNUTLS_X509_FMT_DER, nullptr, &len);
87         TPKP_CHECK_THROW_EXCEPTION(
88                 (ret == GNUTLS_E_SHORT_MEMORY_BUFFER || ret == GNUTLS_E_SUCCESS) && len != 0,
89                 TPKP_E_INVALID_CERT,
90                 "Failed to gnutls_pubkey_export for getting size. gnutls ret: "
91                         << ret << " desc: " << gnutls_strerror(ret) << " size: " << len);
92
93         TPKP::RawBuffer derbuf(len, 0x00);
94         ret = gnutls_pubkey_export(*pubkeyPtr, GNUTLS_X509_FMT_DER, derbuf.data(), &len);
95         TPKP_CHECK_THROW_EXCEPTION(ret == GNUTLS_E_SUCCESS && len == derbuf.size(),
96                 TPKP_E_INVALID_CERT,
97                 "Failed to gnutls_pubkey_export. gnutls ret: "
98                         << ret << " desc: " << gnutls_strerror(ret));
99
100         gnutls_datum_t pubkeyder = {
101                 derbuf.data(),
102                 static_cast<unsigned int>(derbuf.size())
103         };
104
105         auto gnutlsHashAlgo = GNUTLS_DIG_SHA1; /* default hash alog */
106         TPKP::RawBuffer out;
107         switch (algo) {
108         case TPKP::HashAlgo::SHA1:
109                 out.resize(TPKP::typeCast(TPKP::HashSize::SHA1), 0x00);
110                 len = out.size();
111                 gnutlsHashAlgo = GNUTLS_DIG_SHA1;
112                 break;
113
114         default:
115                 TPKP_CHECK_THROW_EXCEPTION(
116                         false,
117                         TPKP_E_INTERNAL,
118                         "Invalid hash algo type in getPubkeyHash.");
119         }
120
121         ret = gnutls_fingerprint(gnutlsHashAlgo, &pubkeyder, out.data(), &len);
122         TPKP_CHECK_THROW_EXCEPTION(ret == GNUTLS_E_SUCCESS && len == out.size(),
123                 TPKP_E_FAILED_GET_PUBKEY_HASH,
124                 "Failed to gnutls_fingerprint. gnutls ret: "
125                         << ret << " desc: " << gnutls_strerror(ret));
126
127         return out;
128 }
129
130 GnutlsX509Ptr d2iCert(const gnutls_datum_t *datum)
131 {
132         auto crtPtr = createGnutlsX509Ptr();
133
134         TPKP_CHECK_THROW_EXCEPTION(
135                 gnutls_x509_crt_init(crtPtr.get()) == GNUTLS_E_SUCCESS,
136                 TPKP_E_INTERNAL, "Failed to gnutls_x509_crt_init.");
137         TPKP_CHECK_THROW_EXCEPTION(
138                 gnutls_x509_crt_import(*crtPtr, datum, GNUTLS_X509_FMT_DER) >= 0,
139                 TPKP_E_INTERNAL, "Failed to import DER to gnutls crt");
140
141         return crtPtr;
142 }
143
144 /*
145  *  Need not to gnutls_x509_crt_deinit for returned value unless GNUTLS_TL_GET_COPY
146  *  flag is used.
147  *  Refer API description of gnutls_certificate_get_issuer.
148  *
149  *  gnutls_certificate_get_issuer will return the issuer of a given certificate.
150  *  As with gnutls_x509_trust_list_get_issuer() this functions requires the
151  *  GNUTLS_TL_GET_COPY flag in order to operate with PKCS11 trust list. In
152  *  that case the issuer must be freed using gnutls_x509_crt_init().
153  */
154 gnutls_x509_crt_t getIssuer(gnutls_session_t session, gnutls_x509_crt_t cert)
155 {
156         gnutls_certificate_credentials_t cred;
157         TPKP_CHECK_THROW_EXCEPTION(
158                 gnutls_credentials_get(session, GNUTLS_CRD_CERTIFICATE, (void **)&cred)
159                         == GNUTLS_E_SUCCESS,
160                 TPKP_E_INTERNAL, "Failed to get credential on session");
161
162         gnutls_x509_crt_t issuer;
163         TPKP_CHECK_THROW_EXCEPTION(
164                 gnutls_x509_crt_init(&issuer) == GNUTLS_E_SUCCESS,
165                 TPKP_E_INTERNAL, "Failed to gnutls_x509_crt_init");
166
167         TPKP_CHECK_THROW_EXCEPTION(
168                 gnutls_certificate_get_issuer(cred, cert, &issuer, 0) == GNUTLS_E_SUCCESS,
169                 TPKP_E_INTERNAL,
170                 "Failed to get issuer! It's internal error because verify peer2 success already");
171
172         return issuer;
173 }
174
175 }
176
177 EXPORT_API
178 int tpkp_gnutls_verify_callback(gnutls_session_t session)
179 {
180         tpkp_e res = TPKP::ExceptionSafe([&]{
181                 gnutls_certificate_type_t type = gnutls_certificate_type_get(session);
182                 if (type != GNUTLS_CRT_X509) {
183                         /*
184                          * TODO: what should we do if it's not x509 type cert?
185                          * for now, just return 0 which means verification success
186                          */
187                         SLOGW("Certificate type of session isn't X509. skipt for now...");
188                         return;
189                 }
190
191                 unsigned int status = 0;
192                 int res = gnutls_certificate_verify_peers2(session, &status);
193                 TPKP_CHECK_THROW_EXCEPTION(res == GNUTLS_E_SUCCESS,
194                         TPKP_E_CERT_VERIFICATION_FAILED,
195                         "Failed to certificate verify peers2.. res: " << gnutls_strerror(res));
196
197                 TPKP_CHECK_THROW_EXCEPTION(status == 0,
198                         TPKP_E_CERT_VERIFICATION_FAILED,
199                         "Peer certificate verification failed!! status: " << status);
200
201                 std::string url = g_cache.getUrl();
202
203                 TPKP_CHECK_THROW_EXCEPTION(
204                         !url.empty(),
205                         TPKP_E_NO_URL_DATA,
206                         "No url of found in client cache!!");
207
208                 TPKP::Context ctx(url);
209                 if (!ctx.hasPins()) {
210                         SLOGI("Skip. No static pin data for url: %s", url.c_str());
211                         return;
212                 }
213
214                 unsigned int listSize = 0;
215                 const gnutls_datum_t *certChain = gnutls_certificate_get_peers(session, &listSize);
216                 TPKP_CHECK_THROW_EXCEPTION(certChain != nullptr && listSize != 0,
217                         TPKP_E_INVALID_PEER_CERT_CHAIN,
218                         "no certificate from peer!");
219
220                 for (unsigned int i = 0; i < listSize; i++) {
221                         auto crtPtr = d2iCert(certChain++);
222
223                         ctx.addPubkeyHash(
224                                 TPKP::HashAlgo::SHA1,
225                                 getPubkeyHash(*crtPtr, TPKP::HashAlgo::SHA1));
226
227                         /* add additional root CA cert for last one */
228                         if (i == listSize - 1) {
229                                 auto issuer = getIssuer(session, *crtPtr);
230
231                                 ctx.addPubkeyHash(
232                                         TPKP::HashAlgo::SHA1,
233                                         getPubkeyHash(issuer, TPKP::HashAlgo::SHA1));
234                         }
235                 }
236
237                 TPKP_CHECK_THROW_EXCEPTION(ctx.checkPubkeyPins(),
238                         TPKP_E_PUBKEY_MISMATCH, "THe pubkey mismatched with pinned data!");
239         });
240
241         return err_tpkp_to_gnutlse(res);
242 }
243
244 EXPORT_API
245 tpkp_e tpkp_gnutls_set_url_data(const char *url)
246 {
247         return TPKP::ExceptionSafe([&]{
248                 g_cache.setUrl(url);
249         });
250 }
251
252 EXPORT_API
253 void tpkp_gnutls_cleanup(void)
254 {
255         tpkp_e res = TPKP::ExceptionSafe([&]{
256                 g_cache.eraseUrl();
257         });
258
259         (void) res;
260 }
261
262 EXPORT_API
263 void tpkp_gnutls_cleanup_all(void)
264 {
265         g_cache.eraseUrlAll();
266 }