Line data Source code
1 :
2 : /*
3 : SSSD - certificate handling utils - NSS version
4 :
5 : Copyright (C) Sumit Bose <sbose@redhat.com> 2015
6 :
7 : This program is free software; you can redistribute it and/or modify
8 : it under the terms of the GNU General Public License as published by
9 : the Free Software Foundation; either version 3 of the License, or
10 : (at your option) any later version.
11 :
12 : This program is distributed in the hope that it will be useful,
13 : but WITHOUT ANY WARRANTY; without even the implied warranty of
14 : MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 : GNU General Public License for more details.
16 :
17 : You should have received a copy of the GNU General Public License
18 : along with this program. If not, see <http://www.gnu.org/licenses/>.
19 : */
20 :
21 : #include "config.h"
22 :
23 : #include <nss.h>
24 : #include <cert.h>
25 : #include <base64.h>
26 : #include <key.h>
27 : #include <prerror.h>
28 : #include <ocsp.h>
29 : #include <talloc.h>
30 :
31 : #include "util/crypto/sss_crypto.h"
32 : #include "util/crypto/nss/nss_util.h"
33 : #include "util/cert.h"
34 :
35 : #define NS_CERT_HEADER "-----BEGIN CERTIFICATE-----"
36 : #define NS_CERT_TRAILER "-----END CERTIFICATE-----"
37 : #define NS_CERT_HEADER_LEN ((sizeof NS_CERT_HEADER) - 1)
38 : #define NS_CERT_TRAILER_LEN ((sizeof NS_CERT_TRAILER) - 1)
39 :
40 6 : errno_t sss_cert_der_to_pem(TALLOC_CTX *mem_ctx, const uint8_t *der_blob,
41 : size_t der_size, char **pem, size_t *pem_size)
42 : {
43 :
44 : CERTCertDBHandle *handle;
45 6 : CERTCertificate *cert = NULL;
46 : SECItem der_item;
47 6 : char *ascii_crlf = NULL;
48 : size_t ascii_crlf_len;
49 6 : char *ascii_lf = NULL;
50 6 : char *pem_cert_str = NULL;
51 : int ret;
52 : size_t c;
53 : size_t d;
54 :
55 : /* initialize NSS if needed */
56 6 : ret = nspr_nss_init();
57 6 : if (ret != EOK) {
58 0 : DEBUG(SSSDBG_OP_FAILURE, "nspr_nss_init failed.\n");
59 0 : return ret;
60 : }
61 :
62 6 : handle = CERT_GetDefaultCertDB();
63 :
64 6 : der_item.len = der_size;
65 6 : der_item.data = discard_const(der_blob);
66 :
67 6 : cert = CERT_NewTempCertificate(handle, &der_item, NULL, PR_FALSE, PR_TRUE);
68 6 : if (cert == NULL) {
69 1 : DEBUG(SSSDBG_OP_FAILURE, "CERT_NewTempCertificate failed.\n");
70 1 : return EINVAL;
71 : }
72 :
73 5 : ascii_crlf = BTOA_DataToAscii(cert->derCert.data, cert->derCert.len);
74 5 : if (ascii_crlf == NULL) {
75 0 : DEBUG(SSSDBG_OP_FAILURE, "BTOA_DataToAscii failed.\n");
76 0 : ret = EIO;
77 0 : goto done;
78 : }
79 :
80 5 : ascii_crlf_len = strlen(ascii_crlf) + 1;
81 5 : ascii_lf = talloc_size(mem_ctx, ascii_crlf_len * sizeof(char));
82 5 : if (ascii_lf == NULL) {
83 0 : DEBUG(SSSDBG_OP_FAILURE, "malloc failed.\n");
84 0 : ret = ENOMEM;
85 0 : goto done;
86 : }
87 :
88 5 : d = 0;
89 7140 : for (c = 0; c < ascii_crlf_len; c++) {
90 7135 : if (ascii_crlf[c] != '\r') {
91 7030 : ascii_lf[d++] = ascii_crlf[c];
92 : }
93 : }
94 :
95 5 : pem_cert_str = talloc_asprintf(mem_ctx, "%s\n%s\n%s\n", NS_CERT_HEADER,
96 : ascii_lf,
97 : NS_CERT_TRAILER);
98 5 : if (pem_cert_str == NULL) {
99 0 : DEBUG(SSSDBG_OP_FAILURE, "talloc_asprintf failed.\n");
100 0 : ret = ENOMEM;
101 0 : goto done;
102 : }
103 :
104 5 : if (pem_size != NULL) {
105 5 : *pem_size = strlen(pem_cert_str);
106 : }
107 :
108 5 : if (pem != NULL) {
109 5 : *pem = pem_cert_str;
110 5 : pem_cert_str = NULL;
111 : }
112 :
113 5 : ret = EOK;
114 : done:
115 5 : talloc_free(pem_cert_str);
116 5 : talloc_free(ascii_lf);
117 5 : PORT_Free(ascii_crlf);
118 5 : CERT_DestroyCertificate(cert);
119 :
120 5 : return ret;
121 : }
122 :
123 4 : errno_t sss_cert_pem_to_der(TALLOC_CTX *mem_ctx, const char *pem,
124 : uint8_t **_der_blob, size_t *_der_size)
125 : {
126 : const char *ps;
127 : const char *pe;
128 : size_t pem_len;
129 4 : uint8_t *der_blob = NULL;
130 : unsigned int der_size; /* unsigned int to match 2nd parameter of
131 : ATOB_AsciiToData */
132 : CERTCertDBHandle *handle;
133 4 : CERTCertificate *cert = NULL;
134 : SECItem der_item;
135 : int ret;
136 4 : char *b64 = NULL;
137 :
138 : /* initialize NSS if needed */
139 4 : ret = nspr_nss_init();
140 4 : if (ret != EOK) {
141 0 : DEBUG(SSSDBG_OP_FAILURE, "nspr_nss_init failed.\n");
142 0 : return ret;
143 : }
144 :
145 4 : if (pem == NULL || *pem == '\0') {
146 2 : return EINVAL;
147 : }
148 :
149 2 : pem_len = strlen(pem);
150 2 : if (pem_len <= NS_CERT_HEADER_LEN + NS_CERT_TRAILER_LEN) {
151 0 : DEBUG(SSSDBG_CRIT_FAILURE, "PEM data too short.\n");
152 0 : return EINVAL;
153 : }
154 :
155 2 : if (strncmp(pem, NS_CERT_HEADER, NS_CERT_HEADER_LEN) != 0) {
156 0 : DEBUG(SSSDBG_CRIT_FAILURE, "Wrong PEM header.\n");
157 0 : return EINVAL;
158 : }
159 2 : if (pem[NS_CERT_HEADER_LEN] != '\n') {
160 0 : DEBUG(SSSDBG_CRIT_FAILURE, "Missing newline in PEM data.\n");
161 0 : return EINVAL;
162 : }
163 :
164 2 : pe = pem + pem_len - NS_CERT_TRAILER_LEN;
165 2 : if (pem[pem_len - 1] == '\n') {
166 2 : pe--;
167 : }
168 2 : if (strncmp(pe, NS_CERT_TRAILER, NS_CERT_TRAILER_LEN) != 0) {
169 0 : DEBUG(SSSDBG_CRIT_FAILURE, "Wrong PEM trailer.\n");
170 0 : return EINVAL;
171 : }
172 :
173 2 : ps = pem + NS_CERT_HEADER_LEN + 1;
174 :
175 2 : b64 = talloc_strndup(mem_ctx, ps, pe - ps);
176 2 : if(b64 == NULL) {
177 0 : DEBUG(SSSDBG_OP_FAILURE, "talloc_strndup failed.\n");
178 0 : ret = ENOMEM;
179 0 : goto done;
180 : }
181 :
182 2 : der_blob = ATOB_AsciiToData(b64, &der_size);
183 2 : if (der_blob == NULL) {
184 0 : DEBUG(SSSDBG_OP_FAILURE, "ATOB_AsciiToData failed.\n");
185 0 : return EIO;
186 : }
187 :
188 2 : handle = CERT_GetDefaultCertDB();
189 :
190 2 : der_item.len = der_size;
191 2 : der_item.data = der_blob;
192 :
193 2 : cert = CERT_NewTempCertificate(handle, &der_item, NULL, PR_FALSE, PR_TRUE);
194 2 : if (cert == NULL) {
195 0 : DEBUG(SSSDBG_OP_FAILURE, "CERT_NewTempCertificate failed.\n");
196 0 : ret = EINVAL;
197 0 : goto done;
198 : }
199 :
200 2 : if (_der_blob != NULL) {
201 2 : *_der_blob = talloc_memdup(mem_ctx, cert->derCert.data,
202 : cert->derCert.len);
203 2 : if (*_der_blob == NULL) {
204 0 : DEBUG(SSSDBG_OP_FAILURE, "talloc_memdup failed.\n");
205 0 : ret = ENOMEM;
206 0 : goto done;
207 : }
208 : }
209 :
210 2 : if (_der_size != NULL) {
211 2 : *_der_size = cert->derCert.len;
212 : }
213 : done:
214 2 : PORT_Free(der_blob);
215 2 : talloc_free(b64);
216 2 : CERT_DestroyCertificate(cert);
217 :
218 2 : return ret;
219 : }
220 :
221 : #define SSH_RSA_HEADER "ssh-rsa"
222 : #define SSH_RSA_HEADER_LEN (sizeof(SSH_RSA_HEADER) - 1)
223 :
224 1 : errno_t cert_to_ssh_key(TALLOC_CTX *mem_ctx, const char *ca_db,
225 : const uint8_t *der_blob, size_t der_size,
226 : struct cert_verify_opts *cert_verify_opts,
227 : uint8_t **key, size_t *key_size)
228 : {
229 : CERTCertDBHandle *handle;
230 1 : CERTCertificate *cert = NULL;
231 : SECItem der_item;
232 1 : SECKEYPublicKey *cert_pub_key = NULL;
233 : int ret;
234 : size_t size;
235 1 : uint8_t *buf = NULL;
236 : size_t c;
237 : NSSInitContext *nss_ctx;
238 1 : NSSInitParameters parameters = { 0 };
239 1 : parameters.length = sizeof (parameters);
240 : SECStatus rv;
241 : SECStatus rv_verify;
242 :
243 1 : if (der_blob == NULL || der_size == 0) {
244 0 : return EINVAL;
245 : }
246 :
247 : /* initialize NSS with context, we might have already called
248 : * NSS_NoDB_Init() but for validation we need to have access to a DB with
249 : * the trusted issuer cert. Only NSS_InitContext will really open the DB
250 : * in this case. I'm not sure about how long validation might need e.g. if
251 : * CRLs or OSCP is enabled, maybe it would be better to run validation in
252 : * p11_child ? */
253 1 : nss_ctx = NSS_InitContext(ca_db, "", "", SECMOD_DB, ¶meters,
254 : NSS_INIT_READONLY);
255 1 : if (nss_ctx == NULL) {
256 0 : DEBUG(SSSDBG_OP_FAILURE, "NSS_InitContext failed [%d].\n",
257 : PR_GetError());
258 0 : return EIO;
259 : }
260 :
261 1 : handle = CERT_GetDefaultCertDB();
262 :
263 1 : if (cert_verify_opts->do_ocsp) {
264 0 : rv = CERT_EnableOCSPChecking(handle);
265 0 : if (rv != SECSuccess) {
266 0 : DEBUG(SSSDBG_OP_FAILURE, "CERT_EnableOCSPChecking failed: [%d].\n",
267 : PR_GetError());
268 0 : return EIO;
269 : }
270 :
271 0 : if (cert_verify_opts->ocsp_default_responder != NULL
272 0 : && cert_verify_opts->ocsp_default_responder_signing_cert != NULL) {
273 0 : rv = CERT_SetOCSPDefaultResponder(handle,
274 0 : cert_verify_opts->ocsp_default_responder,
275 0 : cert_verify_opts->ocsp_default_responder_signing_cert);
276 0 : if (rv != SECSuccess) {
277 0 : DEBUG(SSSDBG_OP_FAILURE,
278 : "CERT_SetOCSPDefaultResponder failed: [%d].\n",
279 : PR_GetError());
280 0 : return EIO;
281 : }
282 :
283 0 : rv = CERT_EnableOCSPDefaultResponder(handle);
284 0 : if (rv != SECSuccess) {
285 0 : DEBUG(SSSDBG_OP_FAILURE,
286 : "CERT_EnableOCSPDefaultResponder failed: [%d].\n",
287 : PR_GetError());
288 0 : return EIO;
289 : }
290 : }
291 : }
292 :
293 1 : der_item.len = der_size;
294 1 : der_item.data = discard_const(der_blob);
295 :
296 1 : cert = CERT_NewTempCertificate(handle, &der_item, NULL, PR_FALSE, PR_TRUE);
297 1 : if (cert == NULL) {
298 0 : DEBUG(SSSDBG_OP_FAILURE, "CERT_NewTempCertificate failed.\n");
299 0 : ret = EINVAL;
300 0 : goto done;
301 : }
302 :
303 1 : if (cert_verify_opts->do_verification) {
304 1 : rv_verify = CERT_VerifyCertificateNow(handle, cert, PR_TRUE,
305 : certificateUsageSSLClient,
306 : NULL, NULL);
307 :
308 : /* Disable OCSP default responder so that NSS can shutdown properly */
309 1 : if (cert_verify_opts->do_ocsp
310 0 : && cert_verify_opts->ocsp_default_responder != NULL
311 0 : && cert_verify_opts->ocsp_default_responder_signing_cert
312 : != NULL) {
313 0 : rv = CERT_DisableOCSPDefaultResponder(handle);
314 0 : if (rv != SECSuccess) {
315 0 : DEBUG(SSSDBG_OP_FAILURE,
316 : "CERT_DisableOCSPDefaultResponder failed: [%d].\n",
317 : PR_GetError());
318 : }
319 : }
320 :
321 1 : if (rv_verify != SECSuccess) {
322 0 : DEBUG(SSSDBG_CRIT_FAILURE, "CERT_VerifyCertificateNow failed [%d].\n",
323 : PR_GetError());
324 0 : ret = EACCES;
325 0 : goto done;
326 : }
327 : }
328 :
329 1 : cert_pub_key = CERT_ExtractPublicKey(cert);
330 1 : if (cert_pub_key == NULL) {
331 0 : DEBUG(SSSDBG_OP_FAILURE, "CERT_ExtractPublicKey failed.\n");
332 0 : ret = EIO;
333 0 : goto done;
334 : }
335 :
336 1 : if (cert_pub_key->keyType != rsaKey) {
337 0 : DEBUG(SSSDBG_CRIT_FAILURE,
338 : "Expected RSA public key, found unsupported [%d].\n",
339 : cert_pub_key->keyType);
340 0 : ret = EINVAL;
341 0 : goto done;
342 : }
343 :
344 1 : size = SSH_RSA_HEADER_LEN + 3 * sizeof(uint32_t)
345 1 : + cert_pub_key->u.rsa.modulus.len
346 1 : + cert_pub_key->u.rsa.publicExponent.len
347 : + 1; /* see comment about missing 00 below */
348 :
349 1 : buf = talloc_size(mem_ctx, size);
350 1 : if (buf == NULL) {
351 0 : DEBUG(SSSDBG_OP_FAILURE, "talloc_size failed.\n");
352 0 : ret = ENOMEM;
353 0 : goto done;
354 : }
355 :
356 1 : c = 0;
357 :
358 1 : SAFEALIGN_SET_UINT32(buf, htobe32(SSH_RSA_HEADER_LEN), &c);
359 1 : safealign_memcpy(&buf[c], SSH_RSA_HEADER, SSH_RSA_HEADER_LEN, &c);
360 1 : SAFEALIGN_SET_UINT32(&buf[c],
361 : htobe32(cert_pub_key->u.rsa.publicExponent.len), &c);
362 1 : safealign_memcpy(&buf[c], cert_pub_key->u.rsa.publicExponent.data,
363 1 : cert_pub_key->u.rsa.publicExponent.len, &c);
364 :
365 : /* Looks like nss drops the leading 00 which afaik is added to make sure
366 : * the bigint is handled as positive number */
367 : /* TODO: make a better check if 00 must be added or not, e.g. ... & 0x80)
368 : */
369 1 : SAFEALIGN_SET_UINT32(&buf[c],
370 : htobe32(cert_pub_key->u.rsa.modulus.len + 1 ), &c);
371 1 : SAFEALIGN_SETMEM_VALUE(&buf[c], '\0', unsigned char, &c);
372 1 : safealign_memcpy(&buf[c], cert_pub_key->u.rsa.modulus.data,
373 1 : cert_pub_key->u.rsa.modulus.len, &c);
374 :
375 1 : *key = buf;
376 1 : *key_size = size;
377 :
378 1 : ret = EOK;
379 :
380 : done:
381 1 : if (ret != EOK) {
382 0 : talloc_free(buf);
383 : }
384 1 : SECKEY_DestroyPublicKey(cert_pub_key);
385 1 : CERT_DestroyCertificate(cert);
386 :
387 1 : rv = NSS_ShutdownContext(nss_ctx);
388 1 : if (rv != SECSuccess) {
389 0 : DEBUG(SSSDBG_OP_FAILURE, "NSS_ShutdownContext failed [%d].\n",
390 : PR_GetError());
391 : }
392 :
393 1 : return ret;
394 : }
|