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 "util/util.h"
22 :
23 : #include <nss.h>
24 : #include <cert.h>
25 : #include <base64.h>
26 : #include <key.h>
27 : #include <prerror.h>
28 :
29 : #include "util/crypto/sss_crypto.h"
30 : #include "util/crypto/nss/nss_util.h"
31 :
32 : #define NS_CERT_HEADER "-----BEGIN CERTIFICATE-----"
33 : #define NS_CERT_TRAILER "-----END CERTIFICATE-----"
34 : #define NS_CERT_HEADER_LEN ((sizeof NS_CERT_HEADER) - 1)
35 : #define NS_CERT_TRAILER_LEN ((sizeof NS_CERT_TRAILER) - 1)
36 :
37 3 : errno_t sss_cert_der_to_pem(TALLOC_CTX *mem_ctx, const uint8_t *der_blob,
38 : size_t der_size, char **pem, size_t *pem_size)
39 : {
40 :
41 : CERTCertDBHandle *handle;
42 3 : CERTCertificate *cert = NULL;
43 : SECItem der_item;
44 3 : char *ascii_crlf = NULL;
45 : size_t ascii_crlf_len;
46 3 : char *ascii_lf = NULL;
47 3 : char *pem_cert_str = NULL;
48 : int ret;
49 : size_t c;
50 : size_t d;
51 :
52 : /* initialize NSS if needed */
53 3 : ret = nspr_nss_init();
54 3 : if (ret != EOK) {
55 0 : DEBUG(SSSDBG_OP_FAILURE, "nspr_nss_init failed.\n");
56 0 : return ret;
57 : }
58 :
59 3 : handle = CERT_GetDefaultCertDB();
60 :
61 3 : der_item.len = der_size;
62 3 : der_item.data = discard_const(der_blob);
63 :
64 3 : cert = CERT_NewTempCertificate(handle, &der_item, NULL, PR_FALSE, PR_TRUE);
65 3 : if (cert == NULL) {
66 1 : DEBUG(SSSDBG_OP_FAILURE, "CERT_NewTempCertificate failed.\n");
67 1 : return EINVAL;
68 : }
69 :
70 2 : ascii_crlf = BTOA_DataToAscii(cert->derCert.data, cert->derCert.len);
71 2 : if (ascii_crlf == NULL) {
72 0 : DEBUG(SSSDBG_OP_FAILURE, "BTOA_DataToAscii failed.\n");
73 0 : ret = EIO;
74 0 : goto done;
75 : }
76 :
77 2 : ascii_crlf_len = strlen(ascii_crlf) + 1;
78 2 : ascii_lf = talloc_size(mem_ctx, ascii_crlf_len * sizeof(char));
79 2 : if (ascii_lf == NULL) {
80 0 : DEBUG(SSSDBG_OP_FAILURE, "malloc failed.\n");
81 0 : ret = ENOMEM;
82 0 : goto done;
83 : }
84 :
85 2 : d = 0;
86 2856 : for (c = 0; c < ascii_crlf_len; c++) {
87 2854 : if (ascii_crlf[c] != '\r') {
88 2812 : ascii_lf[d++] = ascii_crlf[c];
89 : }
90 : }
91 :
92 2 : pem_cert_str = talloc_asprintf(mem_ctx, "%s\n%s\n%s\n", NS_CERT_HEADER,
93 : ascii_lf,
94 : NS_CERT_TRAILER);
95 2 : if (pem_cert_str == NULL) {
96 0 : DEBUG(SSSDBG_OP_FAILURE, "talloc_asprintf failed.\n");
97 0 : ret = ENOMEM;
98 0 : goto done;
99 : }
100 :
101 2 : if (pem_size != NULL) {
102 2 : *pem_size = strlen(pem_cert_str);
103 : }
104 :
105 2 : if (pem != NULL) {
106 2 : *pem = pem_cert_str;
107 2 : pem_cert_str = NULL;
108 : }
109 :
110 2 : ret = EOK;
111 : done:
112 2 : talloc_free(pem_cert_str);
113 2 : talloc_free(ascii_lf);
114 2 : PORT_Free(ascii_crlf);
115 2 : CERT_DestroyCertificate(cert);
116 :
117 2 : return ret;
118 : }
119 :
120 4 : errno_t sss_cert_pem_to_der(TALLOC_CTX *mem_ctx, const char *pem,
121 : uint8_t **_der_blob, size_t *_der_size)
122 : {
123 : const char *ps;
124 : const char *pe;
125 : size_t pem_len;
126 4 : uint8_t *der_blob = NULL;
127 : unsigned int der_size; /* unsigned int to match 2nd parameter of
128 : ATOB_AsciiToData */
129 : CERTCertDBHandle *handle;
130 4 : CERTCertificate *cert = NULL;
131 : SECItem der_item;
132 : int ret;
133 4 : char *b64 = NULL;
134 :
135 : /* initialize NSS if needed */
136 4 : ret = nspr_nss_init();
137 4 : if (ret != EOK) {
138 0 : DEBUG(SSSDBG_OP_FAILURE, "nspr_nss_init failed.\n");
139 0 : return ret;
140 : }
141 :
142 4 : if (pem == NULL || *pem == '\0') {
143 2 : return EINVAL;
144 : }
145 :
146 2 : pem_len = strlen(pem);
147 2 : if (pem_len <= NS_CERT_HEADER_LEN + NS_CERT_TRAILER_LEN) {
148 0 : DEBUG(SSSDBG_CRIT_FAILURE, "PEM data too short.\n");
149 0 : return EINVAL;
150 : }
151 :
152 2 : if (strncmp(pem, NS_CERT_HEADER, NS_CERT_HEADER_LEN) != 0) {
153 0 : DEBUG(SSSDBG_CRIT_FAILURE, "Wrong PEM header.\n");
154 0 : return EINVAL;
155 : }
156 2 : if (pem[NS_CERT_HEADER_LEN] != '\n') {
157 0 : DEBUG(SSSDBG_CRIT_FAILURE, "Missing newline in PEM data.\n");
158 0 : return EINVAL;
159 : }
160 :
161 2 : pe = pem + pem_len - NS_CERT_TRAILER_LEN;
162 2 : if (pem[pem_len - 1] == '\n') {
163 2 : pe--;
164 : }
165 2 : if (strncmp(pe, NS_CERT_TRAILER, NS_CERT_TRAILER_LEN) != 0) {
166 0 : DEBUG(SSSDBG_CRIT_FAILURE, "Wrong PEM trailer.\n");
167 0 : return EINVAL;
168 : }
169 :
170 2 : ps = pem + NS_CERT_HEADER_LEN + 1;
171 :
172 2 : b64 = talloc_strndup(mem_ctx, ps, pe - ps);
173 2 : if(b64 == NULL) {
174 0 : DEBUG(SSSDBG_OP_FAILURE, "talloc_strndup failed.\n");
175 0 : ret = ENOMEM;
176 0 : goto done;
177 : }
178 :
179 2 : der_blob = ATOB_AsciiToData(b64, &der_size);
180 2 : if (der_blob == NULL) {
181 0 : DEBUG(SSSDBG_OP_FAILURE, "ATOB_AsciiToData failed.\n");
182 0 : return EIO;
183 : }
184 :
185 2 : handle = CERT_GetDefaultCertDB();
186 :
187 2 : der_item.len = der_size;
188 2 : der_item.data = der_blob;
189 :
190 2 : cert = CERT_NewTempCertificate(handle, &der_item, NULL, PR_FALSE, PR_TRUE);
191 2 : if (cert == NULL) {
192 0 : DEBUG(SSSDBG_OP_FAILURE, "CERT_NewTempCertificate failed.\n");
193 0 : ret = EINVAL;
194 0 : goto done;
195 : }
196 :
197 2 : if (_der_blob != NULL) {
198 2 : *_der_blob = talloc_memdup(mem_ctx, cert->derCert.data,
199 : cert->derCert.len);
200 2 : if (*_der_blob == NULL) {
201 0 : DEBUG(SSSDBG_OP_FAILURE, "talloc_memdup failed.\n");
202 0 : ret = ENOMEM;
203 0 : goto done;
204 : }
205 : }
206 :
207 2 : if (_der_size != NULL) {
208 2 : *_der_size = cert->derCert.len;
209 : }
210 : done:
211 2 : PORT_Free(der_blob);
212 2 : talloc_free(b64);
213 2 : CERT_DestroyCertificate(cert);
214 :
215 2 : return ret;
216 : }
217 :
218 : #define SSH_RSA_HEADER "ssh-rsa"
219 : #define SSH_RSA_HEADER_LEN (sizeof(SSH_RSA_HEADER) - 1)
220 :
221 1 : errno_t cert_to_ssh_key(TALLOC_CTX *mem_ctx, const char *ca_db,
222 : const uint8_t *der_blob, size_t der_size,
223 : uint8_t **key, size_t *key_size)
224 : {
225 : CERTCertDBHandle *handle;
226 1 : CERTCertificate *cert = NULL;
227 : SECItem der_item;
228 1 : SECKEYPublicKey *cert_pub_key = NULL;
229 : int ret;
230 : size_t size;
231 1 : uint8_t *buf = NULL;
232 : size_t c;
233 : NSSInitContext *nss_ctx;
234 1 : NSSInitParameters parameters = { 0 };
235 1 : parameters.length = sizeof (parameters);
236 : SECStatus rv;
237 :
238 1 : if (der_blob == NULL || der_size == 0) {
239 0 : return EINVAL;
240 : }
241 :
242 : /* initialize NSS with context, we might have already called
243 : * NSS_NoDB_Init() but for validation we need to have access to a DB with
244 : * the trusted issuer cert. Only NSS_InitContext will really open the DB
245 : * in this case. I'm not sure about how long validation might need e.g. if
246 : * CRLs or OSCP is enabled, maybe it would be better to run validation in
247 : * p11_child ? */
248 1 : nss_ctx = NSS_InitContext(ca_db, "", "", SECMOD_DB, ¶meters,
249 : NSS_INIT_READONLY);
250 1 : if (nss_ctx == NULL) {
251 0 : DEBUG(SSSDBG_OP_FAILURE, "NSS_InitContext failed [%d].\n",
252 : PR_GetError());
253 0 : return EIO;
254 : }
255 :
256 1 : handle = CERT_GetDefaultCertDB();
257 :
258 1 : der_item.len = der_size;
259 1 : der_item.data = discard_const(der_blob);
260 :
261 1 : cert = CERT_NewTempCertificate(handle, &der_item, NULL, PR_FALSE, PR_TRUE);
262 1 : if (cert == NULL) {
263 0 : DEBUG(SSSDBG_OP_FAILURE, "CERT_NewTempCertificate failed.\n");
264 0 : ret = EINVAL;
265 0 : goto done;
266 : }
267 :
268 1 : rv = CERT_VerifyCertificateNow(handle, cert, PR_TRUE,
269 : certificateUsageSSLClient, NULL, NULL);
270 1 : if (rv != SECSuccess) {
271 0 : DEBUG(SSSDBG_CRIT_FAILURE, "CERT_VerifyCertificateNow failed [%d].\n",
272 : PR_GetError());
273 0 : ret = EACCES;
274 0 : goto done;
275 : }
276 :
277 1 : cert_pub_key = CERT_ExtractPublicKey(cert);
278 1 : if (cert_pub_key == NULL) {
279 0 : DEBUG(SSSDBG_OP_FAILURE, "CERT_ExtractPublicKey failed.\n");
280 0 : ret = EIO;
281 0 : goto done;
282 : }
283 :
284 1 : if (cert_pub_key->keyType != rsaKey) {
285 0 : DEBUG(SSSDBG_CRIT_FAILURE,
286 : "Expected RSA public key, found unsupported [%d].\n",
287 : cert_pub_key->keyType);
288 0 : ret = EINVAL;
289 0 : goto done;
290 : }
291 :
292 1 : size = SSH_RSA_HEADER_LEN + 3 * sizeof(uint32_t)
293 1 : + cert_pub_key->u.rsa.modulus.len
294 1 : + cert_pub_key->u.rsa.publicExponent.len
295 : + 1; /* see comment about missing 00 below */
296 :
297 1 : buf = talloc_size(mem_ctx, size);
298 1 : if (buf == NULL) {
299 0 : DEBUG(SSSDBG_OP_FAILURE, "talloc_size failed.\n");
300 0 : ret = ENOMEM;
301 0 : goto done;
302 : }
303 :
304 1 : c = 0;
305 :
306 1 : SAFEALIGN_SET_UINT32(buf, htobe32(SSH_RSA_HEADER_LEN), &c);
307 1 : safealign_memcpy(&buf[c], SSH_RSA_HEADER, SSH_RSA_HEADER_LEN, &c);
308 1 : SAFEALIGN_SET_UINT32(&buf[c],
309 : htobe32(cert_pub_key->u.rsa.publicExponent.len), &c);
310 1 : safealign_memcpy(&buf[c], cert_pub_key->u.rsa.publicExponent.data,
311 1 : cert_pub_key->u.rsa.publicExponent.len, &c);
312 :
313 : /* Looks like nss drops the leading 00 which afaik is added to make sure
314 : * the bigint is handled as positive number */
315 : /* TODO: make a better check if 00 must be added or not, e.g. ... & 0x80)
316 : */
317 1 : SAFEALIGN_SET_UINT32(&buf[c],
318 : htobe32(cert_pub_key->u.rsa.modulus.len + 1 ), &c);
319 1 : SAFEALIGN_SETMEM_VALUE(&buf[c], '\0', unsigned char, &c);
320 1 : safealign_memcpy(&buf[c], cert_pub_key->u.rsa.modulus.data,
321 1 : cert_pub_key->u.rsa.modulus.len, &c);
322 :
323 1 : *key = buf;
324 1 : *key_size = size;
325 :
326 1 : ret = EOK;
327 :
328 : done:
329 1 : if (ret != EOK) {
330 0 : talloc_free(buf);
331 : }
332 1 : SECKEY_DestroyPublicKey(cert_pub_key);
333 1 : CERT_DestroyCertificate(cert);
334 :
335 1 : rv = NSS_ShutdownContext(nss_ctx);
336 1 : if (rv != SECSuccess) {
337 0 : DEBUG(SSSDBG_OP_FAILURE, "NSS_ShutdownContext failed [%d].\n",
338 : PR_GetError());
339 : }
340 :
341 1 : return ret;
342 : }
|