blob: b54270efd9895b128cd66ec568611d98e7c651eb [file] [log] [blame]
/*
*
* 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 <config.h>
#endif
#define _GNU_SOURCE
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <strings.h>
#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);
}