VirtualBox

source: vbox/trunk/src/VBox/Runtime/common/crypto/pemfile-read.cpp@ 84204

Last change on this file since 84204 was 84163, checked in by vboxsync, 5 years ago

IPRT: PEM writer functions. bugref:9699

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 23.2 KB
Line 
1/* $Id: pemfile-read.cpp 84163 2020-05-06 15:31:33Z vboxsync $ */
2/** @file
3 * IPRT - Crypto - PEM file reader.
4 *
5 * See RFC-1341 for the original ideas for the format, but keep in mind
6 * that the format was hijacked and put to different uses. We're aiming at
7 * dealing with the different uses rather than anything email related here.
8 */
9
10/*
11 * Copyright (C) 2006-2020 Oracle Corporation
12 *
13 * This file is part of VirtualBox Open Source Edition (OSE), as
14 * available from http://www.virtualbox.org. This file is free software;
15 * you can redistribute it and/or modify it under the terms of the GNU
16 * General Public License (GPL) as published by the Free Software
17 * Foundation, in version 2 as it comes in the "COPYING" file of the
18 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
19 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
20 *
21 * The contents of this file may alternatively be used under the terms
22 * of the Common Development and Distribution License Version 1.0
23 * (CDDL) only, as it comes in the "COPYING.CDDL" file of the
24 * VirtualBox OSE distribution, in which case the provisions of the
25 * CDDL are applicable instead of those of the GPL.
26 *
27 * You may elect to license modified versions of this file under the
28 * terms and conditions of either the GPL or the CDDL or both.
29 */
30
31
32/*********************************************************************************************************************************
33* Header Files *
34*********************************************************************************************************************************/
35#include "internal/iprt.h"
36#include <iprt/crypto/pem.h>
37
38#include <iprt/asm.h>
39#include <iprt/base64.h>
40#include <iprt/ctype.h>
41#include <iprt/err.h>
42#include <iprt/mem.h>
43#include <iprt/memsafer.h>
44#include <iprt/file.h>
45#include <iprt/string.h>
46
47
48
49/**
50 * Looks for a PEM-like marker.
51 *
52 * @returns true if found, false if not.
53 * @param pbContent Start of the content to search thru.
54 * @param cbContent The size of the content to search.
55 * @param offStart The offset into pbContent to start searching.
56 * @param pszLeadWord The lead word (BEGIN/END).
57 * @param cchLeadWord The length of the lead word.
58 * @param paMarkers Pointer to an array of markers.
59 * @param cMarkers Number of markers in the array.
60 * @param ppMatch Where to return the pointer to the matching
61 * marker. Optional.
62 * @param poffBegin Where to return the start offset of the marker.
63 * Optional.
64 * @param poffEnd Where to return the end offset of the marker
65 * (trailing whitespace and newlines will be
66 * skipped). Optional.
67 */
68static bool rtCrPemFindMarker(uint8_t const *pbContent, size_t cbContent, size_t offStart,
69 const char *pszLeadWord, size_t cchLeadWord, PCRTCRPEMMARKER paMarkers, size_t cMarkers,
70 PCRTCRPEMMARKER *ppMatch, size_t *poffBegin, size_t *poffEnd)
71{
72 /* Remember the start of the content for the purpose of calculating offsets. */
73 uint8_t const * const pbStart = pbContent;
74
75 /* Skip adhead by offStart */
76 if (offStart >= cbContent)
77 return false;
78 pbContent += offStart;
79 cbContent -= offStart;
80
81 /*
82 * Search the content.
83 */
84 while (cbContent > 6)
85 {
86 /*
87 * Look for dashes.
88 */
89 uint8_t const *pbStartSearch = pbContent;
90 pbContent = (uint8_t const *)memchr(pbContent, '-', cbContent);
91 if (!pbContent)
92 break;
93
94 cbContent -= pbContent - pbStartSearch;
95 if (cbContent < 6)
96 break;
97
98 /*
99 * There must be at least three to interest us.
100 */
101 if ( pbContent[1] == '-'
102 && pbContent[2] == '-')
103 {
104 unsigned cDashes = 3;
105 while (cDashes < cbContent && pbContent[cDashes] == '-')
106 cDashes++;
107
108 if (poffBegin)
109 *poffBegin = pbContent - pbStart;
110 cbContent -= cDashes;
111 pbContent += cDashes;
112
113 /*
114 * Match lead word.
115 */
116 if ( cbContent > cchLeadWord
117 && memcmp(pbContent, pszLeadWord, cchLeadWord) == 0
118 && RT_C_IS_BLANK(pbContent[cchLeadWord]) )
119 {
120 pbContent += cchLeadWord;
121 cbContent -= cchLeadWord;
122 while (cbContent > 0 && RT_C_IS_BLANK(*pbContent))
123 {
124 pbContent++;
125 cbContent--;
126 }
127
128 /*
129 * Match one of the specified markers.
130 */
131 uint8_t const *pbSavedContent = pbContent;
132 size_t const cbSavedContent = cbContent;
133 for (uint32_t iMarker = 0; iMarker < cMarkers; iMarker++)
134 {
135 pbContent = pbSavedContent;
136 cbContent = cbSavedContent;
137
138 uint32_t cWords = paMarkers[iMarker].cWords;
139 PCRTCRPEMMARKERWORD pWord = paMarkers[iMarker].paWords;
140 while (cWords > 0)
141 {
142 uint32_t const cchWord = pWord->cchWord;
143 if (cbContent <= cchWord)
144 break;
145 if (memcmp(pbContent, pWord->pszWord, cchWord))
146 break;
147 pbContent += cchWord;
148 cbContent -= cchWord;
149
150 if (!cbContent)
151 break;
152 if (RT_C_IS_BLANK(*pbContent))
153 do
154 {
155 pbContent++;
156 cbContent--;
157 } while (cbContent > 0 && RT_C_IS_BLANK(*pbContent));
158 else if (cWords > 1 || pbContent[0] != '-')
159 break;
160
161 cWords--;
162 if (cWords == 0)
163 {
164 /*
165 * If there are three or more dashes following now, we've got a hit.
166 */
167 if ( cbContent > 3
168 && pbContent[0] == '-'
169 && pbContent[1] == '-'
170 && pbContent[2] == '-')
171 {
172 cDashes = 3;
173 while (cDashes < cbContent && pbContent[cDashes] == '-')
174 cDashes++;
175 cbContent -= cDashes;
176 pbContent += cDashes;
177
178 /*
179 * Skip spaces and newline.
180 */
181 while (cbContent > 0 && RT_C_IS_SPACE(*pbContent))
182 pbContent++, cbContent--;
183 if (poffEnd)
184 *poffEnd = pbContent - pbStart;
185 if (ppMatch)
186 *ppMatch = &paMarkers[iMarker];
187 return true;
188 }
189 break;
190 }
191 pWord++;
192 } /* for each word in marker. */
193 } /* for each marker. */
194 }
195 }
196 else
197 {
198 pbContent++;
199 cbContent--;
200 }
201 }
202
203 return false;
204}
205
206
207static bool rtCrPemFindMarkerSection(uint8_t const *pbContent, size_t cbContent, size_t offStart,
208 PCRTCRPEMMARKER paMarkers, size_t cMarkers,
209 PCRTCRPEMMARKER *ppMatch, size_t *poffBegin, size_t *poffEnd, size_t *poffResume)
210{
211 /** @todo Detect BEGIN / END mismatch. */
212 PCRTCRPEMMARKER pMatch;
213 if (rtCrPemFindMarker(pbContent, cbContent, offStart, "BEGIN", 5, paMarkers, cMarkers,
214 &pMatch, NULL /*poffStart*/, poffBegin))
215 {
216 if (rtCrPemFindMarker(pbContent, cbContent, *poffBegin, "END", 3, pMatch, 1,
217 NULL /*ppMatch*/, poffEnd, poffResume))
218 {
219 *ppMatch = pMatch;
220 return true;
221 }
222 }
223 *ppMatch = NULL;
224 return false;
225}
226
227
228/**
229 * Parses any fields the message may contain.
230 *
231 * @retval VINF_SUCCESS
232 * @retval VERR_NO_MEMORY
233 * @retval VERR_CR_MALFORMED_PEM_HEADER
234 *
235 * @param pSection The current section, where we will attach a list of
236 * fields to the pFieldHead member.
237 * @param pbContent The content of the PEM message being parsed.
238 * @param cbContent The length of the PEM message.
239 * @param pcbFields Where to return the length of the header fields we found.
240 */
241static int rtCrPemProcessFields(PRTCRPEMSECTION pSection, uint8_t const *pbContent, size_t cbContent, size_t *pcbFields)
242{
243 uint8_t const * const pbContentStart = pbContent;
244
245 /*
246 * Work the encapulated header protion field by field.
247 *
248 * This is optional, so currently we don't throw errors here but leave that
249 * to when we work the text portion with the base64 decoder. Also, as a reader
250 * we don't go all pedanic on confirming to specification (RFC-1421), especially
251 * given that it's used for crypto certificates, keys and the like not email. :-)
252 */
253 PCRTCRPEMFIELD *ppNext = &pSection->pFieldHead;
254 while (cbContent > 0)
255 {
256 /* Just look for a colon first. */
257 const uint8_t *pbColon = (const uint8_t *)memchr(pbContent, ':', cbContent);
258 if (!pbColon)
259 break;
260 size_t offColon = pbColon - pbContent;
261
262 /* Check that the colon is within the first line. */
263 if (!memchr(pbContent, '\n', cbContent - offColon))
264 return VERR_CR_MALFORMED_PEM_HEADER;
265
266 /* Skip leading spaces (there shouldn't be any, but just in case). */
267 while (RT_C_IS_BLANK(*pbContent) && /*paranoia:*/ offColon > 0)
268 {
269 offColon--;
270 cbContent--;
271 pbContent++;
272 }
273
274 /* There shouldn't be any spaces before the colon, but just in case */
275 size_t cchName = offColon;
276 while (cchName > 0 && RT_C_IS_BLANK(pbContent[cchName - 1]))
277 cchName--;
278
279 /* Skip leading value spaces (there typically is at least one). */
280 size_t offValue = offColon + 1;
281 while (offValue < cbContent && RT_C_IS_BLANK(pbContent[offValue]))
282 offValue++;
283
284 /* Find the newline the field value ends with and where the next iteration should start later on. */
285 size_t cbLeft;
286 uint8_t const *pbNext = (uint8_t const *)memchr(&pbContent[offValue], '\n', cbContent - offValue);
287 while ( pbNext
288 && (cbLeft = pbNext - pbContent) < cbContent
289 && RT_C_IS_BLANK(pbNext[1]) /* next line must start with a space or tab */)
290 pbNext = (uint8_t const *)memchr(&pbNext[1], '\n', cbLeft - 1);
291
292 size_t cchValue;
293 if (pbNext)
294 {
295 cchValue = pbNext - &pbContent[offValue];
296 if (cchValue > 0 && pbNext[-1] == '\r')
297 cchValue--;
298 pbNext++;
299 }
300 else
301 {
302 cchValue = cbContent - offValue;
303 pbNext = &pbContent[cbContent];
304 }
305
306 /* Strip trailing spaces. */
307 while (cchValue > 0 && RT_C_IS_BLANK(pbContent[offValue + cchValue - 1]))
308 cchValue--;
309
310 /*
311 * Allocate a field instance.
312 *
313 * Note! We don't consider field data sensitive at the moment. This
314 * mainly because the fields are chiefly used to indicate the
315 * encryption parameters to the body.
316 */
317 PRTCRPEMFIELD pNewField = (PRTCRPEMFIELD)RTMemAllocZVar(sizeof(*pNewField) + cchName + 1 + cchValue + 1);
318 if (!pNewField)
319 return VERR_NO_MEMORY;
320 pNewField->cchName = cchName;
321 pNewField->cchValue = cchValue;
322 memcpy(pNewField->szName, pbContent, cchName);
323 pNewField->szName[cchName] = '\0';
324 char *pszDst = (char *)memcpy(&pNewField->szName[cchName + 1], &pbContent[offValue], cchValue);
325 pNewField->pszValue = pszDst;
326 pszDst[cchValue] = '\0';
327 pNewField->pNext = NULL;
328
329 *ppNext = pNewField;
330 ppNext = &pNewField->pNext;
331
332 /*
333 * Advance past the field.
334 */
335 cbContent -= pbNext - pbContent;
336 pbContent = pbNext;
337 }
338
339 /*
340 * Skip blank line(s) before the body.
341 */
342 while (cbContent >= 1)
343 {
344 size_t cbSkip;
345 if (pbContent[0] == '\n')
346 cbSkip = 1;
347 else if ( pbContent[0] == '\r'
348 && cbContent >= 2
349 && pbContent[1] == '\n')
350 cbSkip = 2;
351 else
352 break;
353 pbContent += cbSkip;
354 cbContent -= cbSkip;
355 }
356
357 *pcbFields = pbContent - pbContentStart;
358 return VINF_SUCCESS;
359}
360
361
362/**
363 * Does the decoding of a PEM-like data blob after it has been located.
364 *
365 * @returns IPRT status ocde
366 * @param pbContent The start of the PEM-like content (text).
367 * @param cbContent The max size of the PEM-like content.
368 * @param fSensitive Set if the safer allocator should be used.
369 * @param ppvDecoded Where to return a heap block containing the
370 * decoded content.
371 * @param pcbDecoded Where to return the size of the decoded content.
372 */
373static int rtCrPemDecodeBase64(uint8_t const *pbContent, size_t cbContent, bool fSensitive,
374 void **ppvDecoded, size_t *pcbDecoded)
375{
376 ssize_t cbDecoded = RTBase64DecodedSizeEx((const char *)pbContent, cbContent, NULL);
377 if (cbDecoded < 0)
378 return VERR_INVALID_BASE64_ENCODING;
379
380 *pcbDecoded = cbDecoded;
381 void *pvDecoded = !fSensitive ? RTMemAlloc(cbDecoded) : RTMemSaferAllocZ(cbDecoded);
382 if (!pvDecoded)
383 return VERR_NO_MEMORY;
384
385 size_t cbActual;
386 int rc = RTBase64DecodeEx((const char *)pbContent, cbContent, pvDecoded, cbDecoded, &cbActual, NULL);
387 if (RT_SUCCESS(rc))
388 {
389 if (cbActual == (size_t)cbDecoded)
390 {
391 *ppvDecoded = pvDecoded;
392 return VINF_SUCCESS;
393 }
394
395 rc = VERR_INTERNAL_ERROR_3;
396 }
397 if (!fSensitive)
398 RTMemFree(pvDecoded);
399 else
400 RTMemSaferFree(pvDecoded, cbDecoded);
401 return rc;
402}
403
404
405/**
406 * Checks if the content of a file looks to be binary or not.
407 *
408 * @returns true if likely to be binary, false if not binary.
409 * @param pbFile The file bytes to scan.
410 * @param cbFile The number of bytes.
411 * @param fFlags RTCRPEMREADFILE_F_XXX
412 */
413static bool rtCrPemIsBinaryBlob(uint8_t const *pbFile, size_t cbFile, uint32_t fFlags)
414{
415 if (fFlags & RTCRPEMREADFILE_F_ONLY_PEM)
416 return false;
417
418 /*
419 * Well formed PEM files should probably only contain 7-bit ASCII and
420 * restrict thenselfs to the following control characters:
421 * tab, newline, return, form feed
422 *
423 * However, if we want to read PEM files which contains human readable
424 * certificate details before or after each base-64 section, we can't stick
425 * to 7-bit ASCII. We could say it must be UTF-8, but that's probably to
426 * limited as well. So, we'll settle for detecting binary files by control
427 * characters alone (safe enough for DER encoded stuff, I think).
428 */
429 while (cbFile-- > 0)
430 {
431 uint8_t const b = *pbFile++;
432 if (b < 32 && b != '\t' && b != '\n' && b != '\r' && b != '\f')
433 {
434 /* Ignore EOT (4), SUB (26) and NUL (0) at the end of the file. */
435 if ( (b == 4 || b == 26)
436 && ( cbFile == 0
437 || ( cbFile == 1
438 && *pbFile == '\0')))
439 return false;
440
441 if (b == 0 && cbFile == 0)
442 return false;
443
444 return true;
445 }
446 }
447 return false;
448}
449
450
451RTDECL(int) RTCrPemFreeSections(PCRTCRPEMSECTION pSectionHead)
452{
453 while (pSectionHead != NULL)
454 {
455 PRTCRPEMSECTION pFree = (PRTCRPEMSECTION)pSectionHead;
456 pSectionHead = pSectionHead->pNext;
457 ASMCompilerBarrier(); /* paranoia */
458
459 if (pFree->pbData)
460 {
461 if (!pFree->fSensitive)
462 RTMemFree(pFree->pbData);
463 else
464 RTMemSaferFree(pFree->pbData, pFree->cbData);
465 pFree->pbData = NULL;
466 pFree->cbData = 0;
467 }
468
469 PRTCRPEMFIELD pField = (PRTCRPEMFIELD)pFree->pFieldHead;
470 if (pField)
471 {
472 pFree->pFieldHead = NULL;
473 do
474 {
475 PRTCRPEMFIELD pFreeField = pField;
476 pField = (PRTCRPEMFIELD)pField->pNext;
477 ASMCompilerBarrier(); /* paranoia */
478
479 pFreeField->pszValue = NULL;
480 RTMemFree(pFreeField);
481 } while (pField);
482 }
483
484 RTMemFree(pFree);
485 }
486 return VINF_SUCCESS;
487}
488
489
490RTDECL(int) RTCrPemParseContent(void const *pvContent, size_t cbContent, uint32_t fFlags,
491 PCRTCRPEMMARKER paMarkers, size_t cMarkers,
492 PCRTCRPEMSECTION *ppSectionHead, PRTERRINFO pErrInfo)
493{
494 RT_NOREF_PV(pErrInfo);
495
496 /*
497 * Input validation.
498 */
499 AssertPtr(ppSectionHead);
500 *ppSectionHead = NULL;
501 AssertReturn(cbContent, VINF_EOF);
502 AssertPtr(pvContent);
503 AssertPtr(paMarkers);
504 AssertReturn(!(fFlags & ~RTCRPEMREADFILE_F_VALID_MASK), VERR_INVALID_FLAGS);
505
506 /*
507 * Pre-allocate a section.
508 */
509 int rc = VINF_SUCCESS;
510 PRTCRPEMSECTION pSection = (PRTCRPEMSECTION)RTMemAllocZ(sizeof(*pSection));
511 if (pSection)
512 {
513 bool const fSensitive = RT_BOOL(fFlags & RTCRPEMREADFILE_F_SENSITIVE);
514
515 /*
516 * Try locate the first section.
517 */
518 uint8_t const *pbContent = (uint8_t const *)pvContent;
519 size_t offBegin, offEnd, offResume;
520 PCRTCRPEMMARKER pMatch;
521 if ( !rtCrPemIsBinaryBlob(pbContent, cbContent, fFlags)
522 && rtCrPemFindMarkerSection(pbContent, cbContent, 0 /*offStart*/, paMarkers, cMarkers,
523 &pMatch, &offBegin, &offEnd, &offResume) )
524 {
525 PCRTCRPEMSECTION *ppNext = ppSectionHead;
526 for (;;)
527 {
528 //pSection->pNext = NULL;
529 pSection->pMarker = pMatch;
530 //pSection->pbData = NULL;
531 //pSection->cbData = 0;
532 //pSection->pFieldHead = NULL;
533 pSection->fSensitive = fSensitive;
534
535 *ppNext = pSection;
536 ppNext = &pSection->pNext;
537
538 /*
539 * Decode the section.
540 */
541 size_t cbFields = 0;
542 int rc2 = rtCrPemProcessFields(pSection, pbContent + offBegin, offEnd - offBegin, &cbFields);
543 offBegin += cbFields;
544 if (RT_SUCCESS(rc2))
545 rc2 = rtCrPemDecodeBase64(pbContent + offBegin, offEnd - offBegin, fSensitive,
546 (void **)&pSection->pbData, &pSection->cbData);
547 if (RT_FAILURE(rc2))
548 {
549 pSection->pbData = NULL;
550 pSection->cbData = 0;
551 if ( rc2 == VERR_INVALID_BASE64_ENCODING
552 && (fFlags & RTCRPEMREADFILE_F_CONTINUE_ON_ENCODING_ERROR))
553 rc = -rc2;
554 else
555 {
556 rc = rc2;
557 break;
558 }
559 }
560
561 /*
562 * More sections?
563 */
564 if ( offResume + 12 >= cbContent
565 || offResume >= cbContent
566 || !rtCrPemFindMarkerSection(pbContent, cbContent, offResume, paMarkers, cMarkers,
567 &pMatch, &offBegin, &offEnd, &offResume) )
568 break; /* No. */
569
570 /* Ok, allocate a new record for it. */
571 pSection = (PRTCRPEMSECTION)RTMemAllocZ(sizeof(*pSection));
572 if (RT_UNLIKELY(!pSection))
573 {
574 rc = VERR_NO_MEMORY;
575 break;
576 }
577 }
578 if (RT_SUCCESS(rc))
579 return rc;
580
581 RTCrPemFreeSections(*ppSectionHead);
582 }
583 else
584 {
585 if (!(fFlags & RTCRPEMREADFILE_F_ONLY_PEM))
586 {
587 /*
588 * No PEM section found. Return the whole file as one binary section.
589 */
590 //pSection->pNext = NULL;
591 //pSection->pMarker = NULL;
592 //pSection->pFieldHead = NULL;
593 pSection->cbData = cbContent;
594 pSection->fSensitive = fSensitive;
595 if (!fSensitive)
596 pSection->pbData = (uint8_t *)RTMemDup(pbContent, cbContent);
597 else
598 {
599 pSection->pbData = (uint8_t *)RTMemSaferAllocZ(cbContent);
600 if (pSection->pbData)
601 memcpy(pSection->pbData, pbContent, cbContent);
602 }
603 if (pSection->pbData)
604 {
605 *ppSectionHead = pSection;
606 return VINF_SUCCESS;
607 }
608
609 rc = VERR_NO_MEMORY;
610 }
611 else
612 rc = VWRN_NOT_FOUND;
613 RTMemFree(pSection);
614 }
615 }
616 else
617 rc = VERR_NO_MEMORY;
618 *ppSectionHead = NULL;
619 return rc;
620}
621
622
623RTDECL(int) RTCrPemReadFile(const char *pszFilename, uint32_t fFlags, PCRTCRPEMMARKER paMarkers, size_t cMarkers,
624 PCRTCRPEMSECTION *ppSectionHead, PRTERRINFO pErrInfo)
625{
626 *ppSectionHead = NULL;
627 AssertReturn(!(fFlags & ~RTCRPEMREADFILE_F_VALID_MASK), VERR_INVALID_FLAGS);
628
629 size_t cbContent;
630 void *pvContent;
631 int rc = RTFileReadAllEx(pszFilename, 0, 64U*_1M, RTFILE_RDALL_O_DENY_WRITE, &pvContent, &cbContent);
632 if (RT_SUCCESS(rc))
633 {
634 rc = RTCrPemParseContent(pvContent, cbContent, fFlags, paMarkers, cMarkers, ppSectionHead, pErrInfo);
635 if (fFlags & RTCRPEMREADFILE_F_SENSITIVE)
636 RTMemWipeThoroughly(pvContent, cbContent, 3);
637 RTFileReadAllFree(pvContent, cbContent);
638 }
639 else
640 rc = RTErrInfoSetF(pErrInfo, rc, "RTFileReadAllEx failed with %Rrc on '%s'", rc, pszFilename);
641 return rc;
642}
643
644
645RTDECL(const char *) RTCrPemFindFirstSectionInContent(void const *pvContent, size_t cbContent,
646 PCRTCRPEMMARKER paMarkers, size_t cMarkers)
647{
648 size_t offBegin;
649 if (rtCrPemFindMarker((uint8_t *)pvContent, cbContent, 0, "BEGIN", 5, paMarkers, cMarkers, NULL, &offBegin, NULL))
650 return (const char *)pvContent + offBegin;
651 return NULL;
652}
653
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