VirtualBox

source: vbox/trunk/src/VBox/Runtime/common/crypto/pemfile.cpp@ 73603

Last change on this file since 73603 was 69521, checked in by vboxsync, 7 years ago

pemfile.cpp: Don't leak pSection if binary and _F_ONLY_PEM is used. (parfait/clang-analyzer)

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 16.9 KB
Line 
1/* $Id: pemfile.cpp 69521 2017-10-30 10:59:20Z vboxsync $ */
2/** @file
3 * IPRT - Crypto - PEM file reader / writer.
4 */
5
6/*
7 * Copyright (C) 2006-2017 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 *
17 * The contents of this file may alternatively be used under the terms
18 * of the Common Development and Distribution License Version 1.0
19 * (CDDL) only, as it comes in the "COPYING.CDDL" file of the
20 * VirtualBox OSE distribution, in which case the provisions of the
21 * CDDL are applicable instead of those of the GPL.
22 *
23 * You may elect to license modified versions of this file under the
24 * terms and conditions of either the GPL or the CDDL or both.
25 */
26
27
28/*********************************************************************************************************************************
29* Header Files *
30*********************************************************************************************************************************/
31#include "internal/iprt.h"
32#include <iprt/crypto/pem.h>
33
34#include <iprt/base64.h>
35#include <iprt/ctype.h>
36#include <iprt/err.h>
37#include <iprt/mem.h>
38#include <iprt/file.h>
39#include <iprt/string.h>
40
41
42
43/**
44 * Looks for a PEM-like marker.
45 *
46 * @returns true if found, fasle if not.
47 * @param pbContent Start of the content to search thru.
48 * @param cbContent The size of the content to search.
49 * @param offStart The offset into pbContent to start searching.
50 * @param pszLeadWord The lead word (BEGIN/END).
51 * @param cchLeadWord The length of the lead word.
52 * @param paMarkers Pointer to an array of markers.
53 * @param cMarkers Number of markers in the array.
54 * @param ppMatch Where to return the pointer to the matching
55 * marker. Optional.
56 * @param poffBegin Where to return the start offset of the marker.
57 * Optional.
58 * @param poffEnd Where to return the end offset of the marker
59 * (trailing whitespace and newlines will be
60 * skipped). Optional.
61 */
62static bool rtCrPemFindMarker(uint8_t const *pbContent, size_t cbContent, size_t offStart,
63 const char *pszLeadWord, size_t cchLeadWord, PCRTCRPEMMARKER paMarkers, size_t cMarkers,
64 PCRTCRPEMMARKER *ppMatch, size_t *poffBegin, size_t *poffEnd)
65{
66 /* Remember the start of the content for the purpose of calculating offsets. */
67 uint8_t const * const pbStart = pbContent;
68
69 /* Skip adhead by offStart */
70 if (offStart >= cbContent)
71 return false;
72 pbContent += offStart;
73 cbContent -= offStart;
74
75 /*
76 * Search the content.
77 */
78 while (cbContent > 6)
79 {
80 /*
81 * Look for dashes.
82 */
83 uint8_t const *pbStartSearch = pbContent;
84 pbContent = (uint8_t const *)memchr(pbContent, '-', cbContent);
85 if (!pbContent)
86 break;
87
88 cbContent -= pbContent - pbStartSearch;
89 if (cbContent < 6)
90 break;
91
92 /*
93 * There must be at least three to interest us.
94 */
95 if ( pbContent[1] == '-'
96 && pbContent[2] == '-')
97 {
98 unsigned cDashes = 3;
99 while (cDashes < cbContent && pbContent[cDashes] == '-')
100 cDashes++;
101
102 if (poffBegin)
103 *poffBegin = pbContent - pbStart;
104 cbContent -= cDashes;
105 pbContent += cDashes;
106
107 /*
108 * Match lead word.
109 */
110 if ( cbContent > cchLeadWord
111 && memcmp(pbContent, pszLeadWord, cchLeadWord) == 0
112 && RT_C_IS_BLANK(pbContent[cchLeadWord]) )
113 {
114 pbContent += cchLeadWord;
115 cbContent -= cchLeadWord;
116 while (cbContent > 0 && RT_C_IS_BLANK(*pbContent))
117 {
118 pbContent++;
119 cbContent--;
120 }
121
122 /*
123 * Match one of the specified markers.
124 */
125 uint8_t const *pbSavedContent = pbContent;
126 size_t const cbSavedContent = cbContent;
127 for (uint32_t iMarker = 0; iMarker < cMarkers; iMarker++)
128 {
129 pbContent = pbSavedContent;
130 cbContent = cbSavedContent;
131
132 uint32_t cWords = paMarkers[iMarker].cWords;
133 PCRTCRPEMMARKERWORD pWord = paMarkers[iMarker].paWords;
134 while (cWords > 0)
135 {
136 uint32_t const cchWord = pWord->cchWord;
137 if (cbContent <= cchWord)
138 break;
139 if (memcmp(pbContent, pWord->pszWord, cchWord))
140 break;
141 pbContent += cchWord;
142 cbContent -= cchWord;
143
144 if (!cbContent)
145 break;
146 if (RT_C_IS_BLANK(*pbContent))
147 do
148 {
149 pbContent++;
150 cbContent--;
151 } while (cbContent > 0 && RT_C_IS_BLANK(*pbContent));
152 else if (cWords > 1 || pbContent[0] != '-')
153 break;
154
155 cWords--;
156 if (cWords == 0)
157 {
158 /*
159 * If there are three or more dashes following now, we've got a hit.
160 */
161 if ( cbContent > 3
162 && pbContent[0] == '-'
163 && pbContent[1] == '-'
164 && pbContent[2] == '-')
165 {
166 cDashes = 3;
167 while (cDashes < cbContent && pbContent[cDashes] == '-')
168 cDashes++;
169 cbContent -= cDashes;
170 pbContent += cDashes;
171
172 /*
173 * Skip spaces and newline.
174 */
175 while (cbContent > 0 && RT_C_IS_SPACE(*pbContent))
176 pbContent++, cbContent--;
177 if (poffEnd)
178 *poffEnd = pbContent - pbStart;
179 if (ppMatch)
180 *ppMatch = &paMarkers[iMarker];
181 return true;
182 }
183 break;
184 }
185 } /* for each word in marker. */
186 } /* for each marker. */
187 }
188 }
189 else
190 {
191 pbContent++;
192 cbContent--;
193 }
194 }
195
196 return false;
197}
198
199
200static bool rtCrPemFindMarkerSection(uint8_t const *pbContent, size_t cbContent, size_t offStart,
201 PCRTCRPEMMARKER paMarkers, size_t cMarkers,
202 PCRTCRPEMMARKER *ppMatch, size_t *poffBegin, size_t *poffEnd, size_t *poffResume)
203{
204 /** @todo Detect BEGIN / END mismatch. */
205 PCRTCRPEMMARKER pMatch;
206 if (rtCrPemFindMarker(pbContent, cbContent, offStart, "BEGIN", 5, paMarkers, cMarkers,
207 &pMatch, NULL /*poffStart*/, poffBegin))
208 {
209 if (rtCrPemFindMarker(pbContent, cbContent, *poffBegin, "END", 3, pMatch, 1,
210 NULL /*ppMatch*/, poffEnd, poffResume))
211 {
212 *ppMatch = pMatch;
213 return true;
214 }
215 }
216 *ppMatch = NULL;
217 return false;
218}
219
220
221
222/**
223 * Does the decoding of a PEM-like data blob after it has been located.
224 *
225 * @returns IPRT status ocde
226 * @param pbContent The start of the PEM-like content (text).
227 * @param cbContent The max size of the PEM-like content.
228 * @param ppvDecoded Where to return a heap block containing the
229 * decoded content.
230 * @param pcbDecoded Where to return the size of the decoded content.
231 */
232static int rtCrPemDecodeBase64(uint8_t const *pbContent, size_t cbContent, void **ppvDecoded, size_t *pcbDecoded)
233{
234 ssize_t cbDecoded = RTBase64DecodedSizeEx((const char *)pbContent, cbContent, NULL);
235 if (cbDecoded < 0)
236 return VERR_INVALID_BASE64_ENCODING;
237
238 *pcbDecoded = cbDecoded;
239 void *pvDecoded = RTMemAlloc(cbDecoded);
240 if (!pvDecoded)
241 return VERR_NO_MEMORY;
242
243 size_t cbActual;
244 int rc = RTBase64DecodeEx((const char *)pbContent, cbContent, pvDecoded, cbDecoded, &cbActual, NULL);
245 if (RT_SUCCESS(rc))
246 {
247 if (cbActual == (size_t)cbDecoded)
248 {
249 *ppvDecoded = pvDecoded;
250 return VINF_SUCCESS;
251 }
252 rc = VERR_INTERNAL_ERROR_3;
253 }
254 RTMemFree(pvDecoded);
255 return rc;
256}
257
258
259/**
260 * Checks if the content of a file looks to be binary or not.
261 *
262 * @returns true if likely to be binary, false if not binary.
263 * @param pbFile The file bytes to scan.
264 * @param cbFile The number of bytes.
265 * @param fFlags RTCRPEMREADFILE_F_XXX
266 */
267static bool rtCrPemIsBinaryBlob(uint8_t const *pbFile, size_t cbFile, uint32_t fFlags)
268{
269 if (fFlags & RTCRPEMREADFILE_F_ONLY_PEM)
270 return false;
271
272 /*
273 * Well formed PEM files should probably only contain 7-bit ASCII and
274 * restrict thenselfs to the following control characters:
275 * tab, newline, return, form feed
276 *
277 * However, if we want to read PEM files which contains human readable
278 * certificate details before or after each base-64 section, we can't stick
279 * to 7-bit ASCII. We could say it must be UTF-8, but that's probably to
280 * limited as well. So, we'll settle for detecting binary files by control
281 * characters alone (safe enough for DER encoded stuff, I think).
282 */
283 while (cbFile-- > 0)
284 {
285 uint8_t const b = *pbFile++;
286 if (b < 32 && b != '\t' && b != '\n' && b != '\r' && b != '\f')
287 {
288 /* Ignore EOT (4), SUB (26) and NUL (0) at the end of the file. */
289 if ( (b == 4 || b == 26)
290 && ( cbFile == 0
291 || ( cbFile == 1
292 && *pbFile == '\0')))
293 return false;
294
295 if (b == 0 && cbFile == 0)
296 return false;
297
298 return true;
299 }
300 }
301 return false;
302}
303
304
305RTDECL(int) RTCrPemFreeSections(PCRTCRPEMSECTION pSectionHead)
306{
307 while (pSectionHead != NULL)
308 {
309 PRTCRPEMSECTION pFree = (PRTCRPEMSECTION)pSectionHead;
310 pSectionHead = pSectionHead->pNext;
311
312 Assert(pFree->pMarker || !pFree->pszPreamble);
313
314 if (pFree->pbData)
315 {
316 RTMemFree(pFree->pbData);
317 pFree->pbData = NULL;
318 pFree->cbData = 0;
319 }
320
321 if (pFree->pszPreamble)
322 {
323 RTMemFree(pFree->pszPreamble);
324 pFree->pszPreamble = NULL;
325 pFree->cchPreamble = 0;
326 }
327 RTMemFree(pFree);
328 }
329 return VINF_SUCCESS;
330}
331
332
333RTDECL(int) RTCrPemParseContent(void const *pvContent, size_t cbContent, uint32_t fFlags,
334 PCRTCRPEMMARKER paMarkers, size_t cMarkers,
335 PCRTCRPEMSECTION *ppSectionHead, PRTERRINFO pErrInfo)
336{
337 RT_NOREF_PV(pErrInfo);
338
339 /*
340 * Input validation.
341 */
342 AssertPtr(ppSectionHead);
343 *ppSectionHead = NULL;
344 AssertReturn(cbContent, VINF_EOF);
345 AssertPtr(pvContent);
346 AssertPtr(paMarkers);
347 AssertReturn(!(fFlags & ~RTCRPEMREADFILE_F_VALID_MASK), VERR_INVALID_FLAGS);
348
349 /*
350 * Pre-allocate a section.
351 */
352 int rc = VINF_SUCCESS;
353 PRTCRPEMSECTION pSection = (PRTCRPEMSECTION)RTMemAllocZ(sizeof(*pSection));
354 if (pSection)
355 {
356 /*
357 * Try locate the first section.
358 */
359 uint8_t const *pbContent = (uint8_t const *)pvContent;
360 size_t offBegin, offEnd, offResume;
361 PCRTCRPEMMARKER pMatch;
362 if ( !rtCrPemIsBinaryBlob(pbContent, cbContent, fFlags)
363 && rtCrPemFindMarkerSection(pbContent, cbContent, 0 /*offStart*/, paMarkers, cMarkers,
364 &pMatch, &offBegin, &offEnd, &offResume) )
365 {
366 PCRTCRPEMSECTION *ppNext = ppSectionHead;
367 for (;;)
368 {
369 //pSection->pNext = NULL;
370 pSection->pMarker = pMatch;
371 //pSection->pbData = NULL;
372 //pSection->cbData = 0;
373 //pSection->pszPreamble = NULL;
374 //pSection->cchPreamble = 0;
375
376 *ppNext = pSection;
377 ppNext = &pSection->pNext;
378
379 /* Decode the section. */
380 /** @todo copy the preamble as well. */
381 int rc2 = rtCrPemDecodeBase64(pbContent + offBegin, offEnd - offBegin,
382 (void **)&pSection->pbData, &pSection->cbData);
383 if (RT_FAILURE(rc2))
384 {
385 pSection->pbData = NULL;
386 pSection->cbData = 0;
387 if ( rc2 == VERR_INVALID_BASE64_ENCODING
388 && (fFlags & RTCRPEMREADFILE_F_CONTINUE_ON_ENCODING_ERROR))
389 rc = -rc2;
390 else
391 {
392 rc = rc2;
393 break;
394 }
395 }
396
397 /* More sections? */
398 if ( offResume + 12 >= cbContent
399 || offResume >= cbContent
400 || !rtCrPemFindMarkerSection(pbContent, cbContent, offResume, paMarkers, cMarkers,
401 &pMatch, &offBegin, &offEnd, &offResume) )
402 break; /* No. */
403
404 /* Ok, allocate a new record for it. */
405 pSection = (PRTCRPEMSECTION)RTMemAllocZ(sizeof(*pSection));
406 if (RT_UNLIKELY(!pSection))
407 {
408 rc = VERR_NO_MEMORY;
409 break;
410 }
411 }
412 if (RT_SUCCESS(rc))
413 return rc;
414
415 RTCrPemFreeSections(*ppSectionHead);
416 }
417 else
418 {
419 if (!(fFlags & RTCRPEMREADFILE_F_ONLY_PEM))
420 {
421 /*
422 * No PEM section found. Return the whole file as one binary section.
423 */
424 //pSection->pNext = NULL;
425 //pSection->pMarker = NULL;
426 pSection->pbData = (uint8_t *)RTMemDup(pbContent, cbContent);
427 pSection->cbData = cbContent;
428 //pSection->pszPreamble = NULL;
429 //pSection->cchPreamble = 0;
430 if (pSection->pbData)
431 {
432 *ppSectionHead = pSection;
433 return VINF_SUCCESS;
434 }
435
436 rc = VERR_NO_MEMORY;
437 }
438 else
439 rc = VWRN_NOT_FOUND;
440 RTMemFree(pSection);
441 }
442 }
443 else
444 rc = VERR_NO_MEMORY;
445 *ppSectionHead = NULL;
446 return rc;
447}
448
449
450RTDECL(int) RTCrPemReadFile(const char *pszFilename, uint32_t fFlags, PCRTCRPEMMARKER paMarkers, size_t cMarkers,
451 PCRTCRPEMSECTION *ppSectionHead, PRTERRINFO pErrInfo)
452{
453 *ppSectionHead = NULL;
454 AssertReturn(!(fFlags & ~RTCRPEMREADFILE_F_VALID_MASK), VERR_INVALID_FLAGS);
455
456 size_t cbContent;
457 void *pvContent;
458 int rc = RTFileReadAllEx(pszFilename, 0, 64U*_1M, RTFILE_RDALL_O_DENY_WRITE, &pvContent, &cbContent);
459 if (RT_SUCCESS(rc))
460 {
461 rc = RTCrPemParseContent(pvContent, cbContent, fFlags, paMarkers, cMarkers, ppSectionHead, pErrInfo);
462 RTFileReadAllFree(pvContent, cbContent);
463 }
464 else
465 rc = RTErrInfoSetF(pErrInfo, rc, "RTFileReadAllEx failed with %Rrc on '%s'", rc, pszFilename);
466 return rc;
467}
468
469
470RTDECL(const char *) RTCrPemFindFirstSectionInContent(void const *pvContent, size_t cbContent,
471 PCRTCRPEMMARKER paMarkers, size_t cMarkers)
472{
473 size_t offBegin;
474 if (rtCrPemFindMarker((uint8_t *)pvContent, cbContent, 0, "BEGIN", 5, paMarkers, cMarkers, NULL, &offBegin, NULL))
475 return (const char *)pvContent + offBegin;
476 return NULL;
477}
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette