Put realloc logic into pem_read()

Signed-off-by: Steffen Jaeckel <s@jaeckel.eu>
This commit is contained in:
Steffen Jaeckel 2025-09-11 12:25:58 +02:00
parent def00a6b11
commit 6ae30f2660
5 changed files with 183 additions and 108 deletions

View file

@ -382,12 +382,23 @@ struct bufp {
};
#define SET_BUFP(n, d, l) n.start = (char*)d, n.work = (char*)d, n.end = (char*)d + l + 1
#define UPDATE_BUFP(n, d, w, l) n.start = (char*)d, n.work = (char*)d + w, n.end = (char*)d + l + 1
struct get_char;
struct get_char_api {
int (*get)(struct get_char*);
int (*reset)(struct get_char*);
unsigned long (*len)(struct get_char*);
};
struct get_char {
int (*get)(struct get_char*);
struct get_char_api api;
union {
#ifndef LTC_NO_FILE
FILE *f;
struct {
FILE *f;
long original_pos;
} f;
#endif /* LTC_NO_FILE */
struct bufp buf;
} data;
@ -395,6 +406,17 @@ struct get_char {
char unget_buf_[LTC_PEM_DECODE_BUFSZ];
int prev_get;
};
#define pem_get_char_init(b, l) { \
.api = get_char_buffer_api, \
SET_BUFP(.data.buf, (b), (l)) \
}
#define pem_get_char_init_filehandle(fi) { \
.api = get_char_filehandle_api, \
.data.f.f = (fi), \
.data.f.original_pos = ftell(fi) \
}
#endif
/* others */
@ -417,10 +439,10 @@ int pem_decrypt(unsigned char *data, unsigned long *datalen,
const struct blockcipher_info *info,
enum padding_type padding);
#ifndef LTC_NO_FILE
int pem_get_char_from_file(struct get_char *g);
extern const struct get_char_api get_char_filehandle_api;
#endif /* LTC_NO_FILE */
int pem_get_char_from_buf(struct get_char *g);
int pem_read(void *asn1_cert, unsigned long *asn1_len, struct pem_headers *hdr, struct get_char *g);
extern const struct get_char_api get_char_buffer_api;
int pem_read(void **dest, unsigned long *len, struct pem_headers *hdr, struct get_char *g);
#endif
/* tomcrypt_pk.h */
@ -709,7 +731,7 @@ int x509_decode_subject_public_key_info(const unsigned char *in, unsigned long i
int x509_get_pka(const ltc_asn1_list *pub, enum ltc_pka_id *pka);
int x509_get_sig_alg(const ltc_asn1_list *pub, ltc_x509_signature_algorithm *sig_alg);
int x509_import_spki(const unsigned char *asn1_cert, unsigned long asn1_len, ltc_pka_key *k, ltc_asn1_list **root);
int x509_import_spki(const unsigned char *buf, unsigned long len, ltc_pka_key *k, ltc_asn1_list **root);
int x509_get_extensions(const ltc_asn1_list *seq, ltc_x509_extensions *extensions);
void x509_free_extensions(const ltc_x509_extensions *extensions);
int x509_get_serial(const ltc_asn1_list *asn1, ltc_x509_string *serial);

View file

@ -127,21 +127,16 @@ static const import_fn s_import_openssl_fns[LTC_PKA_NUM] = {
static int s_decode(struct get_char *g, ltc_pka_key *k, const password_ctx *pw_ctx)
{
unsigned char *asn1_cert = NULL;
unsigned long w, asn1_len, n;
unsigned long w = 0, asn1_len, n;
int err = CRYPT_ERROR;
struct pem_headers hdr = { 0 };
struct password pw = { 0 };
enum ltc_pka_id pka;
XMEMSET(k, 0, sizeof(*k));
w = LTC_PEM_READ_BUFSIZE * 2;
retry:
asn1_cert = XREALLOC(asn1_cert, w);
for (n = 0; n < pem_std_headers_num; ++n) {
hdr.id = &pem_std_headers[n];
err = pem_read(asn1_cert, &w, &hdr, g);
if (err == CRYPT_BUFFER_OVERFLOW) {
goto retry;
} else if (err == CRYPT_OK) {
err = pem_read((void**)&asn1_cert, &w, &hdr, g);
if (err == CRYPT_OK) {
break;
} else if (err != CRYPT_UNKNOWN_PEM) {
goto cleanup;
@ -204,7 +199,7 @@ int pem_decode_pkcs_filehandle(FILE *f, ltc_pka_key *k, const password_ctx *pw_c
LTC_ARGCHK(f != NULL);
LTC_ARGCHK(k != NULL);
{
struct get_char g = { .get = pem_get_char_from_file, .data.f = f };
struct get_char g = pem_get_char_init_filehandle(f);
return s_decode(&g, k, pw_ctx);
}
}
@ -216,7 +211,7 @@ int pem_decode_pkcs(const void *buf, unsigned long len, ltc_pka_key *k, const pa
LTC_ARGCHK(len != 0);
LTC_ARGCHK(k != NULL);
{
struct get_char g = { .get = pem_get_char_from_buf, SET_BUFP(.data.buf, buf, len) };
struct get_char g = pem_get_char_init(buf, len);
return s_decode(&g, k, pw_ctx);
}
}

View file

@ -17,14 +17,98 @@ extern const struct str pem_dek_info_start;
extern const struct blockcipher_info pem_dek_infos[];
extern const unsigned long pem_dek_infos_num;
#ifndef LTC_NO_FILE
int pem_get_char_from_file(struct get_char *g)
static LTC_INLINE unsigned long s_bufp_alloc_len(struct bufp *buf)
{
return getc(g->data.f);
if (buf->start == NULL || buf->end == NULL)
return 0;
return buf->end - buf->start - 1;
}
static LTC_INLINE unsigned long s_bufp_used_len(struct bufp *buf)
{
if (buf->start == NULL || buf->end == NULL)
return 0;
return buf->work - buf->start;
}
static LTC_INLINE int s_bufp_grow(struct bufp *buf)
{
int err = CRYPT_OK;
void *ret;
unsigned long alloc_len = s_bufp_alloc_len(buf), realloc_len;
unsigned long work_offset = s_bufp_used_len(buf);
if (alloc_len == 0)
realloc_len = LTC_PEM_READ_BUFSIZE;
else
realloc_len = alloc_len * 2;
if (realloc_len < alloc_len)
return CRYPT_OVERFLOW;
ret = XREALLOC(buf->start, realloc_len);
if (ret == NULL) {
err = CRYPT_MEM;
} else {
UPDATE_BUFP((*buf), ret, work_offset, realloc_len);
}
return err;
}
static LTC_INLINE int s_bufp_fits(struct bufp *buf, unsigned long to_write)
{
char *d = buf->work;
char *e = buf->end;
char *w = d + to_write;
if (d == NULL || w < d || w > e)
return 0;
return 1;
}
static LTC_INLINE int s_bufp_add(struct bufp *buf, const void *src, unsigned long len)
{
int err;
if (!s_bufp_fits(buf, len)) {
if ((err = s_bufp_grow(buf)) != CRYPT_OK) {
return err;
}
}
XMEMCPY(buf->work, src, len);
buf->work += len;
return CRYPT_OK;
}
#ifndef LTC_NO_FILE
static int s_pem_get_char_from_file(struct get_char *g)
{
return getc(g->data.f.f);
}
static int s_pem_get_char_reset_file(struct get_char *g)
{
if (g->data.f.original_pos == -1)
return -1;
return fseek(g->data.f.f, g->data.f.original_pos, SEEK_SET);
}
static unsigned long s_pem_get_char_len_file(struct get_char *g)
{
FILE *f = g->data.f.f;
long len, cur_pos = ftell(f);
if (cur_pos != -1) {
fseek(f, 0, SEEK_END);
len = ftell(f);
fseek(f, cur_pos, SEEK_SET);
return len - cur_pos;
}
return LTC_PEM_READ_BUFSIZE * 2;
}
const struct get_char_api get_char_filehandle_api = {
.get = s_pem_get_char_from_file,
.reset = s_pem_get_char_reset_file,
.len = s_pem_get_char_len_file,
};
#endif /* LTC_NO_FILE */
int pem_get_char_from_buf(struct get_char *g)
static int s_pem_get_char_from_buf(struct get_char *g)
{
int ret;
if (g->data.buf.work == g->data.buf.end) {
@ -35,6 +119,25 @@ int pem_get_char_from_buf(struct get_char *g)
return ret;
}
static int s_pem_get_char_reset(struct get_char *g)
{
g->data.buf.work = g->data.buf.start;
return 0;
}
static unsigned long s_pem_get_char_len(struct get_char *g)
{
return s_bufp_alloc_len(&g->data.buf);
}
const struct get_char_api get_char_buffer_api = {
.get = s_pem_get_char_from_buf,
.reset = s_pem_get_char_reset,
.len = s_pem_get_char_len,
};
static void s_unget_line(char *buf, unsigned long buflen, struct get_char *g)
{
if (buflen > sizeof(g->unget_buf_))
@ -81,7 +184,7 @@ static char* s_get_line_i(char *buf, unsigned long *buflen, struct get_char *g,
while(blen < *buflen || search_for_start) {
wr = blen < *buflen ? blen : *buflen - 1;
c_ = g->prev_get;
g->prev_get = g->get(g);
g->prev_get = g->api.get(g);
if (g->prev_get == '\n') {
buf[wr] = '\0';
if (c_ == '\r') {
@ -103,26 +206,16 @@ static char* s_get_line_i(char *buf, unsigned long *buflen, struct get_char *g,
return NULL;
}
LTC_INLINE static char* s_get_first_line(char *buf, unsigned long *buflen, struct get_char *g)
static LTC_INLINE char* s_get_first_line(char *buf, unsigned long *buflen, struct get_char *g)
{
return s_get_line_i(buf, buflen, g, 1);
}
LTC_INLINE static char* s_get_line(char *buf, unsigned long *buflen, struct get_char *g)
static LTC_INLINE char* s_get_line(char *buf, unsigned long *buflen, struct get_char *g)
{
return s_get_line_i(buf, buflen, g, 0);
}
static LTC_INLINE int s_fits_buf(void *dest, unsigned long to_write, void *end)
{
unsigned char *d = dest;
unsigned char *e = end;
unsigned char *w = d + to_write;
if (w < d || w > e)
return 0;
return 1;
}
static int s_pem_decode_headers(struct pem_headers *hdr, struct get_char *g)
{
char buf[LTC_PEM_DECODE_BUFSZ], *alg_start;
@ -190,21 +283,19 @@ static int s_pem_decode_headers(struct pem_headers *hdr, struct get_char *g)
return CRYPT_OK;
}
int pem_read(void *asn1_cert, unsigned long *asn1_len, struct pem_headers *hdr, struct get_char *g)
int pem_read(void **dest, unsigned long *len, struct pem_headers *hdr, struct get_char *g)
{
char buf[LTC_PEM_DECODE_BUFSZ];
char *wpem = asn1_cert;
char *end = wpem + *asn1_len;
char line[LTC_PEM_DECODE_BUFSZ];
struct bufp b_ = {0}, *b = &b_;
const char pem_start[] = "----";
unsigned long slen, linelen;
int err, hdr_ok = 0;
int would_overflow = 0;
unsigned char empty_lines = 0;
g->prev_get = 0;
do {
linelen = sizeof(buf);
if (s_get_first_line(buf, &linelen, g) == NULL) {
linelen = sizeof(line);
if (s_get_first_line(line, &linelen, g) == NULL) {
if (g->prev_get == -1)
return CRYPT_NOP;
else
@ -212,9 +303,9 @@ int pem_read(void *asn1_cert, unsigned long *asn1_len, struct pem_headers *hdr,
}
if (linelen < sizeof(pem_start) - 1)
continue;
} while(XMEMCMP(buf, pem_start, sizeof(pem_start) - 1) != 0);
if (hdr->id->start.len != linelen || XMEMCMP(buf, hdr->id->start.p, hdr->id->start.len)) {
s_unget_line(buf, linelen, g);
} while(XMEMCMP(line, pem_start, sizeof(pem_start) - 1) != 0);
if (hdr->id->start.len != linelen || XMEMCMP(line, hdr->id->start.p, hdr->id->start.len)) {
s_unget_line(line, linelen, g);
return CRYPT_UNKNOWN_PEM;
}
@ -223,9 +314,9 @@ int pem_read(void *asn1_cert, unsigned long *asn1_len, struct pem_headers *hdr,
return err;
/* Read the base64 encoded part of the PEM */
slen = sizeof(buf);
while (s_get_line(buf, &slen, g)) {
if (slen == hdr->id->end.len && !XMEMCMP(buf, hdr->id->end.p, slen)) {
slen = sizeof(line);
while (s_get_line(line, &slen, g)) {
if (slen == hdr->id->end.len && !XMEMCMP(line, hdr->id->end.p, slen)) {
hdr_ok = 1;
break;
}
@ -234,34 +325,25 @@ int pem_read(void *asn1_cert, unsigned long *asn1_len, struct pem_headers *hdr,
break;
empty_lines++;
}
if (!would_overflow && s_fits_buf(wpem, slen, end)) {
XMEMCPY(wpem, buf, slen);
} else {
would_overflow = 1;
if ((err = s_bufp_add(b, line, slen)) != CRYPT_OK) {
goto error_out;
}
wpem += slen;
slen = sizeof(buf);
slen = sizeof(line);
}
if (!hdr_ok)
return CRYPT_INVALID_PACKET;
if (would_overflow || !s_fits_buf(wpem, 1, end)) {
/* NUL termination */
wpem++;
/* prevent a wrap-around */
if (wpem < (char*)asn1_cert)
return CRYPT_OVERFLOW;
*asn1_len = wpem - (char*)asn1_cert;
return CRYPT_BUFFER_OVERFLOW;
if (!hdr_ok) {
err = CRYPT_INVALID_PACKET;
} else {
slen = s_bufp_alloc_len(b);
err = base64_strict_decode(b->start, s_bufp_used_len(b), (void*)b->start, &slen);
}
*asn1_len = wpem - (char*)asn1_cert;
*wpem++ = '\0';
if ((err = base64_strict_decode(asn1_cert, *asn1_len, asn1_cert, asn1_len)) != CRYPT_OK) {
return err;
if (err == CRYPT_OK) {
*dest = b->start;
*len = slen;
} else {
error_out:
XFREE(b->start);
}
return CRYPT_OK;
return err;
}
#endif /* LTC_PEM */

View file

@ -712,20 +712,15 @@ static const unsigned long pem_openssh_num = LTC_ARRAY_SIZE(pem_openssh);
static int s_decode_openssh(struct get_char *g, ltc_pka_key *k, const password_ctx *pw_ctx)
{
unsigned char *pem = NULL, *p, *privkey = NULL, *tag;
unsigned long n, w, l, privkey_len, taglen;
unsigned long n, w = 0, l, privkey_len, taglen;
int err;
struct pem_headers hdr;
struct kdf_options opts = { 0 };
XMEMSET(k, 0, sizeof(*k));
w = LTC_PEM_READ_BUFSIZE * 2;
retry:
pem = XREALLOC(pem, w);
for (n = 0; n < pem_openssh_num; ++n) {
hdr.id = &pem_openssh[n];
err = pem_read(pem, &w, &hdr, g);
if (err == CRYPT_BUFFER_OVERFLOW) {
goto retry;
} else if (err == CRYPT_OK) {
err = pem_read((void**)&pem, &w, &hdr, g);
if (err == CRYPT_OK) {
break;
} else if (err != CRYPT_UNKNOWN_PEM) {
goto cleanup;
@ -791,7 +786,9 @@ cleanup:
zeromem(privkey, privkey_len);
XFREE(privkey);
}
XFREE(pem);
if (pem) {
XFREE(pem);
}
return err;
}
@ -801,7 +798,7 @@ int pem_decode_openssh_filehandle(FILE *f, ltc_pka_key *k, const password_ctx *p
LTC_ARGCHK(f != NULL);
LTC_ARGCHK(k != NULL);
{
struct get_char g = { .get = pem_get_char_from_file, .data.f = f };
struct get_char g = pem_get_char_init_filehandle(f);
return s_decode_openssh(&g, k, pw_ctx);
}
}
@ -841,7 +838,7 @@ int pem_decode_openssh(const void *buf, unsigned long len, ltc_pka_key *k, const
LTC_ARGCHK(len != 0);
LTC_ARGCHK(k != NULL);
{
struct get_char g = { .get = pem_get_char_from_buf, SET_BUFP(.data.buf, buf, len) };
struct get_char g = pem_get_char_init(buf, len);
return s_decode_openssh(&g, k, pw_ctx);
}
}

View file

@ -592,35 +592,24 @@ static int s_x509_import_pem(struct get_char *g, unsigned long *pem_len, const l
{
int err;
struct pem_headers hdr = { .id = &pem_std_headers[0] };
unsigned long alloc_len = *pem_len, len;
unsigned long len = 0;
unsigned char *asn1_cert = NULL;
len = alloc_len;
retry:
asn1_cert = XREALLOC(asn1_cert, alloc_len);
if (asn1_cert == NULL) {
err = CRYPT_MEM;
goto err_out;
}
if ((err = pem_read(asn1_cert, &len, &hdr, g)) != CRYPT_OK) {
if (err == CRYPT_BUFFER_OVERFLOW && alloc_len == *pem_len) {
alloc_len = len;
goto retry;
}
goto err_out;
if ((err = pem_read((void**)&asn1_cert, &len, &hdr, g)) != CRYPT_OK) {
return err;
}
err = x509_import(asn1_cert, len, out);
*pem_len = len;
err_out:
XFREE(asn1_cert);
return err;
}
int x509_import_pem(const char *pem, unsigned long *pem_len, const ltc_x509_certificate **out)
{
struct get_char g = { .get = pem_get_char_from_buf, SET_BUFP(.data.buf, pem, *pem_len) };
struct get_char g = pem_get_char_init(pem, *pem_len);
return s_x509_import_pem(&g, pem_len, out);
}
@ -628,17 +617,7 @@ int x509_import_pem(const char *pem, unsigned long *pem_len, const ltc_x509_cert
int x509_import_pem_filehandle(FILE *f, const ltc_x509_certificate **out)
{
unsigned long pem_len;
long cur_pos;
struct get_char g = { .get = pem_get_char_from_file, .data.f = f };
cur_pos = ftell(f);
if (cur_pos != -1) {
fseek(f, 0, SEEK_END);
pem_len = ftell(f);
fseek(f, cur_pos, SEEK_SET);
pem_len -= cur_pos;
} else {
pem_len = LTC_PEM_READ_BUFSIZE;
}
struct get_char g = pem_get_char_init_filehandle(f);
return s_x509_import_pem(&g, &pem_len, out);
}
#endif