VirtualBox

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

Last change on this file since 58591 was 57641, checked in by vboxsync, 9 years ago

RTCrPemFreeSections: pbData is always on the heap, so don't call RTFileReadAllFree any more.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 16.0 KB
Line 
1/* $Id: pemfile.cpp 57641 2015-09-07 13:00:03Z vboxsync $ */
2/** @file
3 * IPRT - Crypto - PEM file reader / writer.
4 */
5
6/*
7 * Copyright (C) 2006-2015 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 */
266static bool rtCrPemIsBinaryBlob(uint8_t const *pbFile, size_t cbFile)
267{
268 /*
269 * Well formed PEM files should probably only contain 7-bit ASCII and
270 * restrict thenselfs to the following control characters:
271 * tab, newline, return, form feed
272 *
273 * However, if we wan't to read PEM files which contains human readable
274 * certificate details before or after each base-64 section, we can't stick
275 * to 7-bit ASCII. We could say it must be UTF-8, but that's probably to
276 * limited too. So, we'll settle for detecting binary files by control
277 * characters alone (safe enough for DER encoded stuff, I think).
278 */
279 while (cbFile-- > 0)
280 {
281 uint8_t const b = *pbFile++;
282 if (b < 32 && b != '\t' && b != '\n' && b != '\r' && b != '\f')
283 {
284 /* Ignore EOT (4), SUB (26) and NUL (0) at the end of the file. */
285 if ( (b == 4 || b == 26)
286 && ( cbFile == 0
287 || ( cbFile == 1
288 && *pbFile == '\0')))
289 return false;
290
291 if (b == 0 && cbFile == 0)
292 return false;
293
294 return true;
295 }
296 }
297 return false;
298}
299
300
301RTDECL(int) RTCrPemFreeSections(PCRTCRPEMSECTION pSectionHead)
302{
303 while (pSectionHead != NULL)
304 {
305 PRTCRPEMSECTION pFree = (PRTCRPEMSECTION)pSectionHead;
306 pSectionHead = pSectionHead->pNext;
307
308 Assert(pFree->pMarker || !pFree->pszPreamble);
309
310 if (pFree->pbData)
311 {
312 RTMemFree(pFree->pbData);
313 pFree->pbData = NULL;
314 pFree->cbData = 0;
315 }
316
317 if (pFree->pszPreamble)
318 {
319 RTMemFree(pFree->pszPreamble);
320 pFree->pszPreamble = NULL;
321 pFree->cchPreamble = 0;
322 }
323 }
324 return VINF_SUCCESS;
325}
326
327
328RTDECL(int) RTCrPemParseContent(void const *pvContent, size_t cbContent, uint32_t fFlags,
329 PCRTCRPEMMARKER paMarkers, size_t cMarkers,
330 PCRTCRPEMSECTION *ppSectionHead, PRTERRINFO pErrInfo)
331{
332 /*
333 * Input validation.
334 */
335 AssertPtr(ppSectionHead);
336 *ppSectionHead = NULL;
337 AssertReturn(cbContent, VINF_EOF);
338 AssertPtr(pvContent);
339 AssertPtr(paMarkers);
340
341 /*
342 * Pre-allocate a section.
343 */
344 int rc = VINF_SUCCESS;
345 PRTCRPEMSECTION pSection = (PRTCRPEMSECTION)RTMemAllocZ(sizeof(*pSection));
346 if (pSection)
347 {
348 /*
349 * Try locate the first section.
350 */
351 uint8_t const *pbContent = (uint8_t const *)pvContent;
352 size_t offBegin, offEnd, offResume;
353 PCRTCRPEMMARKER pMatch;
354 if ( !rtCrPemIsBinaryBlob(pbContent, cbContent)
355 && rtCrPemFindMarkerSection(pbContent, cbContent, 0 /*offStart*/, paMarkers, cMarkers,
356 &pMatch, &offBegin, &offEnd, &offResume) )
357 {
358 PCRTCRPEMSECTION *ppNext = ppSectionHead;
359 for (;;)
360 {
361 //pSection->pNext = NULL;
362 pSection->pMarker = pMatch;
363 //pSection->pbData = NULL;
364 //pSection->cbData = 0;
365 //pSection->pszPreamble = NULL;
366 //pSection->cchPreamble = 0;
367
368 *ppNext = pSection;
369 ppNext = &pSection->pNext;
370
371 /* Decode the section. */
372 /** @todo copy the preamble as well. */
373 int rc2 = rtCrPemDecodeBase64(pbContent + offBegin, offEnd - offBegin,
374 (void **)&pSection->pbData, &pSection->cbData);
375 if (RT_FAILURE(rc2))
376 {
377 pSection->pbData = NULL;
378 pSection->cbData = 0;
379 if ( rc2 == VERR_INVALID_BASE64_ENCODING
380 && (fFlags & RTCRPEMREADFILE_F_CONTINUE_ON_ENCODING_ERROR))
381 rc = -rc2;
382 else
383 {
384 rc = rc2;
385 break;
386 }
387 }
388
389 /* More sections? */
390 if ( offResume + 12 >= cbContent
391 || offResume >= cbContent
392 || !rtCrPemFindMarkerSection(pbContent, cbContent, offResume, paMarkers, cMarkers,
393 &pMatch, &offBegin, &offEnd, &offResume) )
394 break; /* No. */
395
396 /* Ok, allocate a new record for it. */
397 pSection = (PRTCRPEMSECTION)RTMemAllocZ(sizeof(*pSection));
398 if (RT_UNLIKELY(!pSection))
399 {
400 rc = VERR_NO_MEMORY;
401 break;
402 }
403 }
404 if (RT_SUCCESS(rc))
405 return rc;
406
407 RTCrPemFreeSections(*ppSectionHead);
408 }
409 else
410 {
411 /*
412 * No PEM section found. Return the whole file as one binary section.
413 */
414 //pSection->pNext = NULL;
415 //pSection->pMarker = NULL;
416 pSection->pbData = (uint8_t *)RTMemDup(pbContent, cbContent);
417 pSection->cbData = cbContent;
418 //pSection->pszPreamble = NULL;
419 //pSection->cchPreamble = 0;
420 if (pSection->pbData)
421 {
422 *ppSectionHead = pSection;
423 return VINF_SUCCESS;
424 }
425
426 rc = VERR_NO_MEMORY;
427 RTMemFree(pSection);
428 }
429 }
430 else
431 rc = VERR_NO_MEMORY;
432 *ppSectionHead = NULL;
433 return rc;
434}
435
436
437
438RTDECL(int) RTCrPemReadFile(const char *pszFilename, uint32_t fFlags, PCRTCRPEMMARKER paMarkers, size_t cMarkers,
439 PCRTCRPEMSECTION *ppSectionHead, PRTERRINFO pErrInfo)
440{
441 *ppSectionHead = NULL;
442 AssertReturn(!(fFlags & ~RTCRPEMREADFILE_F_CONTINUE_ON_ENCODING_ERROR), VERR_INVALID_FLAGS);
443
444 size_t cbContent;
445 void *pvContent;
446 int rc = RTFileReadAllEx(pszFilename, 0, 64U*_1M, RTFILE_RDALL_O_DENY_WRITE, &pvContent, &cbContent);
447 if (RT_SUCCESS(rc))
448 {
449 rc = RTCrPemParseContent(pvContent, cbContent, fFlags, paMarkers, cMarkers, ppSectionHead, pErrInfo);
450 RTFileReadAllFree(pvContent, cbContent);
451 }
452 else
453 rc = RTErrInfoSetF(pErrInfo, rc, "RTFileReadAllEx failed with %Rrc on '%s'", rc, pszFilename);
454 return rc;
455}
456
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