mirror of https://gitee.com/openkylin/bluez.git
834 lines
19 KiB
C
834 lines
19 KiB
C
/*
|
|
*
|
|
* 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 <fcntl.h>
|
|
#include <unistd.h>
|
|
#include <sys/mman.h>
|
|
#include <strings.h>
|
|
#include <errno.h>
|
|
|
|
#include "useful.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 "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;
|
|
}
|
|
|
|
uint8_t *pem_load_buffer(const void *buf, size_t buf_len,
|
|
char **out_type_label, size_t *out_len,
|
|
char **out_headers, const char **out_endp)
|
|
{
|
|
size_t base64_len;
|
|
const char *base64;
|
|
char *label;
|
|
const char *headers = NULL;
|
|
size_t headers_len;
|
|
uint8_t *ret;
|
|
|
|
base64 = pem_next(buf, buf_len, &label, &base64_len,
|
|
out_endp, false);
|
|
if (!base64)
|
|
return NULL;
|
|
|
|
if (memchr(base64, ':', base64_len)) {
|
|
const char *start;
|
|
const char *end;
|
|
|
|
while (base64_len && l_ascii_isspace(*base64)) {
|
|
base64++;
|
|
base64_len--;
|
|
}
|
|
|
|
start = base64;
|
|
|
|
if (!(end = memmem(start, base64_len, "\n\n", 2)) &&
|
|
!(end = memmem(start, base64_len, "\n\r\n", 3)))
|
|
goto err;
|
|
|
|
/* Check that each header line has a key and a colon */
|
|
while (start < end) {
|
|
const char *lf = rawmemchr(start, '\n');
|
|
const char *colon = memchr(start, ':', lf - start);
|
|
|
|
if (!colon)
|
|
goto err;
|
|
|
|
for (; start < colon; start++)
|
|
if (l_ascii_isalnum(*start))
|
|
break;
|
|
|
|
if (start == colon)
|
|
goto err;
|
|
|
|
start = lf + 1;
|
|
}
|
|
|
|
headers = base64;
|
|
headers_len = end - base64;
|
|
|
|
base64_len -= headers_len + 2;
|
|
base64 = end + 2;
|
|
}
|
|
|
|
ret = l_base64_decode(base64, base64_len, out_len);
|
|
if (ret) {
|
|
*out_type_label = label;
|
|
|
|
if (out_headers) {
|
|
if (headers)
|
|
*out_headers = l_strndup(headers, headers_len);
|
|
else
|
|
*out_headers = NULL;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
err:
|
|
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, NULL, NULL);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
void pem_file_close(struct pem_file_info *info)
|
|
{
|
|
munmap(info->data, info->st.st_size);
|
|
close(info->fd);
|
|
}
|
|
|
|
static uint8_t *pem_load_file(const char *filename, char **out_type_label,
|
|
size_t *out_len, char **out_headers)
|
|
{
|
|
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,
|
|
out_type_label, out_len, out_headers,
|
|
NULL);
|
|
pem_file_close(&file);
|
|
return result;
|
|
}
|
|
|
|
LIB_EXPORT uint8_t *l_pem_load_file(const char *filename,
|
|
char **out_type_label, size_t *out_len)
|
|
{
|
|
return pem_load_file(filename, out_type_label, out_len, NULL);
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
static bool pem_write_one_cert(struct l_cert *cert, void *user_data)
|
|
{
|
|
int *fd = user_data;
|
|
const uint8_t *der;
|
|
size_t der_len;
|
|
struct iovec iov[3];
|
|
ssize_t r;
|
|
|
|
der = l_cert_get_der_data(cert, &der_len);
|
|
|
|
iov[0].iov_base = "-----BEGIN CERTIFICATE-----\n";
|
|
iov[0].iov_len = strlen(iov[0].iov_base);
|
|
iov[1].iov_base = l_base64_encode(der, der_len, 64, &iov[1].iov_len);
|
|
iov[2].iov_base = "\n-----END CERTIFICATE-----\n";
|
|
iov[2].iov_len = strlen(iov[2].iov_base);
|
|
r = L_TFR(writev(*fd, iov, 3));
|
|
l_free(iov[1].iov_base);
|
|
|
|
if (r == (ssize_t) (iov[0].iov_len + iov[1].iov_len + iov[2].iov_len))
|
|
return false;
|
|
|
|
if (r < 0)
|
|
*fd = -errno;
|
|
else
|
|
*fd = -EIO;
|
|
|
|
return true;
|
|
}
|
|
|
|
int pem_write_certificate_chain(const struct l_certchain *chain,
|
|
const char *filename)
|
|
{
|
|
int fd = L_TFR(open(filename, O_CREAT | O_WRONLY | O_CLOEXEC, 0600));
|
|
int err = fd;
|
|
|
|
if (err < 0)
|
|
return -errno;
|
|
|
|
l_certchain_walk_from_leaf((struct l_certchain *) chain,
|
|
pem_write_one_cert, &err);
|
|
close(fd);
|
|
|
|
return err < 0 ? err : 0;
|
|
}
|
|
|
|
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;
|
|
bool is_certificate;
|
|
|
|
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;
|
|
}
|
|
|
|
is_certificate = !strcmp(label, "CERTIFICATE");
|
|
l_free(label);
|
|
|
|
if (!is_certificate)
|
|
goto error;
|
|
|
|
der = l_base64_decode(base64, base64_len, &der_len);
|
|
if (!der)
|
|
goto error;
|
|
|
|
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;
|
|
}
|
|
|
|
#define SKIP_WHITESPACE(str) \
|
|
while (l_ascii_isspace(*(str))) \
|
|
(str)++;
|
|
|
|
static const char *parse_rfc1421_dek_info(char *headers,
|
|
const char **out_params)
|
|
{
|
|
const char *proc_type = NULL;
|
|
char *dek_info = NULL;
|
|
char *comma;
|
|
|
|
while (headers) {
|
|
char *lf = strchrnul(headers, '\n');
|
|
char *key;
|
|
|
|
key = headers;
|
|
SKIP_WHITESPACE(key);
|
|
headers = (*lf == '\n') ? lf + 1 : NULL;
|
|
|
|
if (!memcmp(key, "X-", 2))
|
|
key += 2;
|
|
|
|
if (!memcmp(key, "Proc-Type:", 10)) {
|
|
if (proc_type)
|
|
return NULL;
|
|
|
|
proc_type = key + 10;
|
|
SKIP_WHITESPACE(proc_type);
|
|
} else if (!memcmp(key, "DEK-Info:", 9)) {
|
|
if (dek_info)
|
|
return NULL;
|
|
|
|
dek_info = key + 9;
|
|
SKIP_WHITESPACE(dek_info);
|
|
} else
|
|
continue;
|
|
|
|
while (l_ascii_isspace(lf[-1]))
|
|
lf--;
|
|
|
|
*lf = '\0';
|
|
}
|
|
|
|
if (!proc_type || !dek_info)
|
|
return NULL;
|
|
|
|
/* Skip the version field (should be 3 or 4) */
|
|
proc_type = strchr(proc_type, ',');
|
|
if (!proc_type)
|
|
return NULL;
|
|
|
|
proc_type++;
|
|
SKIP_WHITESPACE(proc_type);
|
|
|
|
/* Section 4.6.1.1 */
|
|
if (strcmp(proc_type, "ENCRYPTED"))
|
|
return NULL;
|
|
|
|
comma = strchr(dek_info, ',');
|
|
if (comma) {
|
|
*out_params = comma + 1;
|
|
SKIP_WHITESPACE(*out_params);
|
|
|
|
while (comma > dek_info && l_ascii_isspace(comma[-1]))
|
|
comma--;
|
|
|
|
*comma = '\0';
|
|
} else
|
|
*out_params = NULL;
|
|
|
|
return dek_info;
|
|
}
|
|
|
|
static struct l_cipher *cipher_from_dek_info(const char *algid, const char *params,
|
|
const char *passphrase,
|
|
size_t *block_len)
|
|
{
|
|
enum l_cipher_type type;
|
|
struct l_cipher *cipher;
|
|
struct l_checksum *md5;
|
|
uint8_t key[32];
|
|
size_t key_len;
|
|
bool ok;
|
|
L_AUTO_FREE_VAR(uint8_t *, iv) = NULL;
|
|
size_t iv_len;
|
|
|
|
if (!strcmp(algid, "DES-CBC")) {
|
|
type = L_CIPHER_DES_CBC;
|
|
key_len = 8;
|
|
iv_len = 8;
|
|
} else if (!strcmp(algid, "DES-EDE3-CBC")) {
|
|
type = L_CIPHER_DES3_EDE_CBC;
|
|
key_len = 24;
|
|
iv_len = 8;
|
|
} else if (!strcmp(algid, "AES-128-CBC")) {
|
|
type = L_CIPHER_AES_CBC;
|
|
key_len = 16;
|
|
iv_len = 16;
|
|
} else if (!strcmp(algid, "AES-192-CBC")) {
|
|
type = L_CIPHER_AES_CBC;
|
|
key_len = 24;
|
|
iv_len = 16;
|
|
} else if (!strcmp(algid, "AES-256-CBC")) {
|
|
type = L_CIPHER_AES_CBC;
|
|
key_len = 32;
|
|
iv_len = 16;
|
|
} else
|
|
return NULL;
|
|
|
|
if (!params || strlen(params) != 2 * iv_len)
|
|
return NULL;
|
|
|
|
*block_len = iv_len;
|
|
|
|
iv = l_util_from_hexstring(params, &iv_len);
|
|
if (!iv)
|
|
return NULL;
|
|
|
|
/*
|
|
* The encryption key is the MD5(password | IV[:8]), this comes from
|
|
* opessl's crypto/evp/evp_key.c:EVP_BytesToKey() and doesn't seem to
|
|
* be backed by any standard:
|
|
* https://web.archive.org/web/20190528100132/https://latacora.singles/2018/08/03/the-default-openssh.html
|
|
*/
|
|
md5 = l_checksum_new(L_CHECKSUM_MD5);
|
|
if (!md5)
|
|
return NULL;
|
|
|
|
ok = l_checksum_update(md5, passphrase, strlen(passphrase)) &&
|
|
l_checksum_update(md5, iv, 8) &&
|
|
l_checksum_get_digest(md5, key, 16) == 16;
|
|
|
|
if (ok && key_len > 16) {
|
|
l_checksum_reset(md5);
|
|
ok = l_checksum_update(md5, key, 16) &&
|
|
l_checksum_update(md5, passphrase, strlen(passphrase)) &&
|
|
l_checksum_update(md5, iv, 8) &&
|
|
l_checksum_get_digest(md5, key + 16, 16) == 16;
|
|
}
|
|
|
|
l_checksum_free(md5);
|
|
|
|
if (!ok) {
|
|
cipher = NULL;
|
|
goto cleanup;
|
|
}
|
|
|
|
cipher = l_cipher_new(type, key, key_len);
|
|
if (!cipher)
|
|
goto cleanup;
|
|
|
|
if (l_cipher_set_iv(cipher, iv, iv_len))
|
|
goto cleanup;
|
|
|
|
l_cipher_free(cipher);
|
|
cipher = NULL;
|
|
|
|
cleanup:
|
|
explicit_bzero(key, sizeof(key));
|
|
return cipher;
|
|
}
|
|
|
|
struct l_key *pem_load_private_key(uint8_t *content, size_t len, char *label,
|
|
const char *passphrase, char *headers,
|
|
bool *encrypted)
|
|
{
|
|
struct l_key *pkey;
|
|
|
|
/*
|
|
* RFC7468 Section 10-compatible unencrypted private key label
|
|
* (also mentioned in PKCS#8/RFC5958 Section 5), encodes
|
|
* the PKCS#8/RFC5958 PrivateKeyInfo structure -- supported
|
|
* directly by the pkcs8-key-parser kernel module.
|
|
*/
|
|
if (!strcmp(label, "PRIVATE KEY")) {
|
|
/* RFC822 Headers explicitly disallowed in RFC7468 */
|
|
if (headers)
|
|
goto err;
|
|
|
|
pkey = cert_key_from_pkcs8_private_key_info(content, len);
|
|
goto done;
|
|
}
|
|
|
|
/*
|
|
* RFC7468 Section 11-compatible encrypted private key label
|
|
* (also mentioned in PKCS#8/RFC5958 Section 5), encodes
|
|
* the PKCS#8/RFC5958 EncryptedPrivateKeyInfo structure. We
|
|
* decrypt it into a plain PrivateKeyInfo for the
|
|
* pkcs8-key-parser module.
|
|
*/
|
|
if (!strcmp(label, "ENCRYPTED PRIVATE KEY")) {
|
|
if (encrypted)
|
|
*encrypted = true;
|
|
|
|
if (!passphrase)
|
|
goto err;
|
|
|
|
/* RFC822 Headers explicitly disallowed in RFC7468 */
|
|
if (headers)
|
|
goto err;
|
|
|
|
pkey = cert_key_from_pkcs8_encrypted_private_key_info(content,
|
|
len,
|
|
passphrase);
|
|
goto done;
|
|
}
|
|
|
|
/*
|
|
* Legacy RSA private key label aka. SSLeay format, understood by
|
|
* most software but not documented in an RFC. Encodes the
|
|
* PKCS#1/RFC8017 RSAPrivateKey structure. We wrap it in a PKCS#8
|
|
* PrivateKeyInfo for the pkcs8-key-parser module.
|
|
*/
|
|
if (!strcmp(label, "RSA PRIVATE KEY")) {
|
|
const char *dekalgid;
|
|
const char *dekparameters;
|
|
|
|
/*
|
|
* "openssl rsa ..." can produce encrypted PKCS#1-formatted
|
|
* keys. These are incompatible with RFC7468 parsing because
|
|
* of the RFC822 headers present but the format is the same
|
|
* as documented in RFC1421. The encryption algorithms are
|
|
* supposed to be the ones defined in RFC1423 but that would
|
|
* be only DES-CBC while openssl allows other algorithms.
|
|
* When decrypted we get the RSAPrivateKey struct and proceed
|
|
* like with the unencrypted format.
|
|
*/
|
|
dekalgid = parse_rfc1421_dek_info(headers, &dekparameters);
|
|
if (dekalgid) {
|
|
struct l_cipher *alg;
|
|
bool r;
|
|
size_t block_len;
|
|
uint8_t pad;
|
|
|
|
if (encrypted)
|
|
*encrypted = true;
|
|
|
|
if (!passphrase)
|
|
goto err;
|
|
|
|
alg = cipher_from_dek_info(dekalgid, dekparameters,
|
|
passphrase, &block_len);
|
|
if (!alg)
|
|
goto err;
|
|
|
|
if (len % block_len || !len) {
|
|
l_cipher_free(alg);
|
|
goto err;
|
|
}
|
|
|
|
r = l_cipher_decrypt(alg, content, content, len);
|
|
l_cipher_free(alg);
|
|
|
|
if (!r)
|
|
goto err;
|
|
|
|
/* Remove padding like in RFC1423 Section 1.1 */
|
|
pad = content[len - 1];
|
|
if (pad > block_len || pad == 0)
|
|
goto err;
|
|
|
|
if (!l_secure_memeq(content + len - pad, pad - 1U, pad))
|
|
goto err;
|
|
|
|
len -= pad;
|
|
}
|
|
|
|
pkey = cert_key_from_pkcs1_rsa_private_key(content, len);
|
|
goto done;
|
|
}
|
|
|
|
/* Label not known */
|
|
err:
|
|
pkey = NULL;
|
|
done:
|
|
explicit_bzero(content, len);
|
|
l_free(content);
|
|
l_free(label);
|
|
l_free(headers);
|
|
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;
|
|
char *headers;
|
|
|
|
if (encrypted)
|
|
*encrypted = false;
|
|
|
|
content = pem_load_buffer(buf, buf_len, &label, &len, &headers, NULL);
|
|
|
|
if (!content)
|
|
return NULL;
|
|
|
|
return pem_load_private_key(content, len, label, passphrase, headers,
|
|
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.
|
|
*
|
|
* The passphrase, if given, must have been validated as UTF-8 unless the
|
|
* caller knows that PKCS#12 encryption algorithms are not used.
|
|
* Use l_utf8_validate.
|
|
*
|
|
* 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;
|
|
char *headers;
|
|
|
|
if (encrypted)
|
|
*encrypted = false;
|
|
|
|
content = pem_load_file(filename, &label, &len, &headers);
|
|
|
|
if (!content)
|
|
return NULL;
|
|
|
|
return pem_load_private_key(content, len, label, passphrase, headers,
|
|
encrypted);
|
|
}
|