/* * * Embedded Linux library * * Copyright (C) 2015 Intel Corporation. All rights reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include #include #include #include #include #include "util.h" #include "private.h" #include "key.h" #include "cert.h" #include "queue.h" #include "pem.h" #include "base64.h" #include "utf8.h" #include "asn1-private.h" #include "pkcs5-private.h" #include "cipher.h" #include "cert-private.h" #include "missing.h" #include "pem-private.h" #define PEM_START_BOUNDARY "-----BEGIN " #define PEM_END_BOUNDARY "-----END " static const char *is_start_boundary(const void *buf, size_t buf_len, size_t *label_len) { const char *start, *end, *ptr; int prev_special, special; const char *buf_ptr = buf; if (buf_len < strlen(PEM_START_BOUNDARY)) return NULL; /* Check we have a "-----BEGIN " (RFC7468 section 2) */ if (memcmp(buf, PEM_START_BOUNDARY, strlen(PEM_START_BOUNDARY))) return NULL; /* * Check we have a string of printable characters in which no * two consecutive characters are "special" nor is the first or the * final character "special". These special characters are space * and hyphen. (RFC7468 section 3) * The loop will end on the second hyphen of the final "-----" if * no error found earlier. */ start = buf + strlen(PEM_START_BOUNDARY); end = start; prev_special = 1; while (end < buf_ptr + buf_len && l_ascii_isprint(*end)) { special = *end == ' ' || *end == '-'; if (prev_special && special) break; end++; prev_special = special; } /* Rewind to the first '-', but handle empty labels */ if (end != start) end--; /* Check we have a "-----" (RFC7468 section 2) */ if (end + 5 > buf_ptr + buf_len || memcmp(end, "-----", 5)) return NULL; /* Check all remaining characters are horizontal whitespace (WSP) */ for (ptr = end + 5; ptr < buf_ptr + buf_len; ptr++) if (*ptr != ' ' && *ptr != '\t') return NULL; *label_len = end - start; return start; } static bool is_end_boundary(const void *buf, size_t buf_len, const char *label, size_t label_len) { const char *buf_ptr = buf; size_t len = strlen(PEM_END_BOUNDARY) + label_len + 5; if (buf_len < len) return false; if (memcmp(buf_ptr, PEM_END_BOUNDARY, strlen(PEM_END_BOUNDARY)) || memcmp(buf_ptr + strlen(PEM_END_BOUNDARY), label, label_len) || memcmp(buf_ptr + (len - 5), "-----", 5)) return false; /* Check all remaining characters are horizontal whitespace (WSP) */ for (; len < buf_len; len++) if (buf_ptr[len] != ' ' && buf_ptr[len] != '\t') return false; return true; } const char *pem_next(const void *buf, size_t buf_len, char **type_label, size_t *base64_len, const char **endp, bool strict) { const char *buf_ptr = buf; const char *base64_data = NULL, *eol; const char *label = NULL; size_t label_len = 0; const char *start = NULL; /* * The base64 parser uses the RFC7468 laxbase64text grammar but we * do full checks on the encapsulation boundary lines, i.e. no * leading spaces allowed, making sure quoted text and similar * are not confused for actual PEM "textual encoding". */ while (buf_len) { for (eol = buf_ptr; eol < buf_ptr + buf_len; eol++) if (*eol == '\r' || *eol == '\n') break; if (!base64_data) { label = is_start_boundary(buf_ptr, eol - buf_ptr, &label_len); if (label) { start = label - strlen("-----BEGIN "); base64_data = eol; } else if (strict) break; } else if (start && is_end_boundary(buf_ptr, eol - buf_ptr, label, label_len)) { if (type_label) *type_label = l_strndup(label, label_len); if (base64_len) *base64_len = buf_ptr - base64_data; if (endp) { if (eol == buf + buf_len) *endp = eol; else *endp = eol + 1; } return base64_data; } if (eol == buf_ptr + buf_len) break; buf_len -= eol + 1 - buf_ptr; buf_ptr = eol + 1; if (buf_len && *eol == '\r' && *buf_ptr == '\n') { buf_ptr++; buf_len--; } } /* If we found no label signal EOF rather than parse error */ if (!base64_data && endp) *endp = NULL; return NULL; } static uint8_t *pem_load_buffer(const void *buf, size_t buf_len, char **type_label, size_t *len) { size_t base64_len; const char *base64; char *label; uint8_t *ret; base64 = pem_next(buf, buf_len, &label, &base64_len, NULL, false); if (!base64) return NULL; ret = l_base64_decode(base64, base64_len, len); if (ret) { *type_label = label; return ret; } l_free(label); return NULL; } LIB_EXPORT uint8_t *l_pem_load_buffer(const void *buf, size_t buf_len, char **type_label, size_t *out_len) { return pem_load_buffer(buf, buf_len, type_label, out_len); } struct pem_file_info { int fd; struct stat st; uint8_t *data; }; static int pem_file_open(struct pem_file_info *info, const char *filename) { info->fd = open(filename, O_RDONLY); if (info->fd < 0) return -errno; if (fstat(info->fd, &info->st) < 0) { int r = -errno; close(info->fd); return r; } info->data = mmap(NULL, info->st.st_size, PROT_READ, MAP_SHARED, info->fd, 0); if (info->data == MAP_FAILED) { int r = -errno; close(info->fd); return r; } return 0; } static void pem_file_close(struct pem_file_info *info) { munmap(info->data, info->st.st_size); close(info->fd); } LIB_EXPORT uint8_t *l_pem_load_file(const char *filename, char **type_label, size_t *len) { struct pem_file_info file; uint8_t *result; if (unlikely(!filename)) return NULL; if (pem_file_open(&file, filename) < 0) return NULL; result = pem_load_buffer(file.data, file.st.st_size, type_label, len); pem_file_close(&file); return result; } static struct l_certchain *pem_list_to_chain(struct l_queue *list) { struct l_certchain *chain; if (!list) return NULL; chain = certchain_new_from_leaf(l_queue_pop_head(list)); while (!l_queue_isempty(list)) certchain_link_issuer(chain, l_queue_pop_head(list)); l_queue_destroy(list, NULL); return chain; } LIB_EXPORT struct l_certchain *l_pem_load_certificate_chain_from_data( const void *buf, size_t len) { struct l_queue *list = l_pem_load_certificate_list_from_data(buf, len); if (!list) return NULL; return pem_list_to_chain(list); } LIB_EXPORT struct l_certchain *l_pem_load_certificate_chain( const char *filename) { struct l_queue *list = l_pem_load_certificate_list(filename); if (!list) return NULL; return pem_list_to_chain(list); } LIB_EXPORT struct l_queue *l_pem_load_certificate_list_from_data( const void *buf, size_t len) { const char *ptr, *end; struct l_queue *list = NULL; ptr = buf; end = buf + len; while (ptr && ptr < end) { uint8_t *der; size_t der_len; char *label = NULL; struct l_cert *cert; const char *base64; size_t base64_len; base64 = pem_next(ptr, end - ptr, &label, &base64_len, &ptr, false); if (!base64) { if (!ptr) break; /* if ptr was not reset to NULL; parse error */ goto error; } der = l_base64_decode(base64, base64_len, &der_len); if (!der || strcmp(label, "CERTIFICATE")) { if (der) l_free(label); l_free(der); goto error; } l_free(label); cert = l_cert_new_from_der(der, der_len); l_free(der); if (!cert) goto error; if (!list) list = l_queue_new(); l_queue_push_tail(list, cert); } return list; error: l_queue_destroy(list, (l_queue_destroy_func_t) l_cert_free); return NULL; } LIB_EXPORT struct l_queue *l_pem_load_certificate_list(const char *filename) { struct pem_file_info file; struct l_queue *list = NULL; if (unlikely(!filename)) return NULL; if (pem_file_open(&file, filename) < 0) return NULL; list = l_pem_load_certificate_list_from_data(file.data, file.st.st_size); pem_file_close(&file); return list; } static struct l_key *pem_load_private_key(uint8_t *content, size_t len, char *label, const char *passphrase, bool *encrypted) { struct l_key *pkey = NULL; /* * RFC7469- and PKCS#8-compatible label (default in OpenSSL 1.0.1+) * and the older (OpenSSL <= 0.9.8 default) label. */ if (!strcmp(label, "PRIVATE KEY") || !strcmp(label, "RSA PRIVATE KEY")) goto done; /* RFC5958 (PKCS#8) section 3 type encrypted key label */ if (!strcmp(label, "ENCRYPTED PRIVATE KEY")) { const uint8_t *key_info, *alg_id, *data; uint8_t tag; size_t key_info_len, alg_id_len, data_len, tmp_len; struct l_cipher *alg; uint8_t *decrypted; int i; if (encrypted) *encrypted = true; if (!passphrase) goto err; /* Technically this is BER, not limited to DER */ key_info = asn1_der_find_elem(content, len, 0, &tag, &key_info_len); if (!key_info || tag != ASN1_ID_SEQUENCE) goto err; alg_id = asn1_der_find_elem(key_info, key_info_len, 0, &tag, &alg_id_len); if (!alg_id || tag != ASN1_ID_SEQUENCE) goto err; data = asn1_der_find_elem(key_info, key_info_len, 1, &tag, &data_len); if (!data || tag != ASN1_ID_OCTET_STRING || data_len < 8 || (data_len & 7) != 0) goto err; if (asn1_der_find_elem(content, len, 2, &tag, &tmp_len)) goto err; alg = pkcs5_cipher_from_alg_id(alg_id, alg_id_len, passphrase); if (!alg) goto err; decrypted = l_malloc(data_len); if (!l_cipher_decrypt(alg, data, decrypted, data_len)) { l_cipher_free(alg); l_free(decrypted); goto err; } l_cipher_free(alg); explicit_bzero(content, len); l_free(content); content = decrypted; len = data_len; /* * Strip padding as defined in RFC8018 (for PKCS#5 v1) or * RFC1423 / RFC5652 (for v2). */ if (content[data_len - 1] >= data_len || content[data_len - 1] > 16) goto err; for (i = 1; i < content[data_len - 1]; i++) if (content[data_len - 1 - i] != content[data_len - 1]) goto err; len = data_len - content[data_len - 1]; goto done; } /* * TODO: handle RSA PRIVATE KEY format encrypted keys * (as produced by "openssl rsa" commands), incompatible with * RFC7468 parsing because of the headers present before * base64-encoded data. */ /* Label not known */ goto err; done: pkey = l_key_new(L_KEY_RSA, content, len); err: if (content) { explicit_bzero(content, len); l_free(content); } l_free(label); return pkey; } LIB_EXPORT struct l_key *l_pem_load_private_key_from_data(const void *buf, size_t buf_len, const char *passphrase, bool *encrypted) { uint8_t *content; char *label; size_t len; if (encrypted) *encrypted = false; content = pem_load_buffer(buf, buf_len, &label, &len); if (!content) return NULL; return pem_load_private_key(content, len, label, passphrase, encrypted); } /** * l_pem_load_private_key * @filename: path string to the PEM file to load * @passphrase: private key encryption passphrase or NULL for unencrypted * @encrypted: receives indication whether the file was encrypted if non-NULL * * Load the PEM encoded RSA Private Key file at @filename. If it is an * encrypted private key and @passphrase was non-NULL, the file is * decrypted. If it's unencrypted @passphrase is ignored. @encrypted * stores information of whether the file was encrypted, both in a * success case and on error when NULL is returned. This can be used to * check if a passphrase is required without prior information. * * Returns: An l_key object to be freed with an l_key_free* function, * or NULL. **/ LIB_EXPORT struct l_key *l_pem_load_private_key(const char *filename, const char *passphrase, bool *encrypted) { uint8_t *content; char *label; size_t len; if (encrypted) *encrypted = false; content = l_pem_load_file(filename, &label, &len); if (!content) return NULL; return pem_load_private_key(content, len, label, passphrase, encrypted); }