1/*
2 * Copyright (c) 2020-2022 Yubico AB. All rights reserved.
3 * Use of this source code is governed by a BSD-style
4 * license that can be found in the LICENSE file.
5 */
6
7#include <zlib.h>
8#include "fido.h"
9
10#define BOUND (1024UL * 1024UL)
11
12/* zlib inflate (raw + headers) */
13static int
14rfc1950_inflate(fido_blob_t *out, const fido_blob_t *in, size_t origsiz)
15{
16	u_long ilen, olen;
17	int z;
18
19	memset(out, 0, sizeof(*out));
20
21	if (in->len > ULONG_MAX || (ilen = (u_long)in->len) > BOUND ||
22	    origsiz > ULONG_MAX || (olen = (u_long)origsiz) > BOUND) {
23		fido_log_debug("%s: in->len=%zu, origsiz=%zu", __func__,
24		    in->len, origsiz);
25		return FIDO_ERR_INVALID_ARGUMENT;
26	}
27
28	if ((out->ptr = calloc(1, olen)) == NULL)
29		return FIDO_ERR_INTERNAL;
30	out->len = olen;
31
32	if ((z = uncompress(out->ptr, &olen, in->ptr, ilen)) != Z_OK ||
33	    olen > SIZE_MAX || olen != out->len) {
34		fido_log_debug("%s: uncompress: %d, olen=%lu, out->len=%zu",
35		    __func__, z, olen, out->len);
36		fido_blob_reset(out);
37		return FIDO_ERR_COMPRESS;
38	}
39
40	return FIDO_OK;
41}
42
43/* raw inflate */
44static int
45rfc1951_inflate(fido_blob_t *out, const fido_blob_t *in, size_t origsiz)
46{
47	z_stream zs;
48	u_int ilen, olen;
49	int r, z;
50
51	memset(&zs, 0, sizeof(zs));
52	memset(out, 0, sizeof(*out));
53
54	if (in->len > UINT_MAX || (ilen = (u_int)in->len) > BOUND ||
55	    origsiz > UINT_MAX || (olen = (u_int)origsiz) > BOUND) {
56		fido_log_debug("%s: in->len=%zu, origsiz=%zu", __func__,
57		    in->len, origsiz);
58		return FIDO_ERR_INVALID_ARGUMENT;
59	}
60	if ((z = inflateInit2(&zs, -MAX_WBITS)) != Z_OK) {
61		fido_log_debug("%s: inflateInit2: %d", __func__, z);
62		return FIDO_ERR_COMPRESS;
63	}
64
65	if ((out->ptr = calloc(1, olen)) == NULL) {
66		r = FIDO_ERR_INTERNAL;
67		goto fail;
68	}
69	out->len = olen;
70	zs.next_in = in->ptr;
71	zs.avail_in = ilen;
72	zs.next_out = out->ptr;
73	zs.avail_out = olen;
74
75	if ((z = inflate(&zs, Z_FINISH)) != Z_STREAM_END) {
76		fido_log_debug("%s: inflate: %d", __func__, z);
77		r = FIDO_ERR_COMPRESS;
78		goto fail;
79	}
80	if (zs.avail_out != 0) {
81		fido_log_debug("%s: %u != 0", __func__, zs.avail_out);
82		r = FIDO_ERR_COMPRESS;
83		goto fail;
84	}
85
86	r = FIDO_OK;
87fail:
88	if ((z = inflateEnd(&zs)) != Z_OK) {
89		fido_log_debug("%s: inflateEnd: %d", __func__, z);
90		r = FIDO_ERR_COMPRESS;
91	}
92	if (r != FIDO_OK)
93		fido_blob_reset(out);
94
95	return r;
96}
97
98/* raw deflate */
99static int
100rfc1951_deflate(fido_blob_t *out, const fido_blob_t *in)
101{
102	z_stream zs;
103	u_int ilen, olen;
104	int r, z;
105
106	memset(&zs, 0, sizeof(zs));
107	memset(out, 0, sizeof(*out));
108
109	if (in->len > UINT_MAX || (ilen = (u_int)in->len) > BOUND) {
110		fido_log_debug("%s: in->len=%zu", __func__, in->len);
111		return FIDO_ERR_INVALID_ARGUMENT;
112	}
113	if ((z = deflateInit2(&zs, Z_DEFAULT_COMPRESSION, Z_DEFLATED,
114	    -MAX_WBITS, 8, Z_DEFAULT_STRATEGY)) != Z_OK) {
115		fido_log_debug("%s: deflateInit2: %d", __func__, z);
116		return FIDO_ERR_COMPRESS;
117	}
118
119	olen = BOUND;
120	if ((out->ptr = calloc(1, olen)) == NULL) {
121		r = FIDO_ERR_INTERNAL;
122		goto fail;
123	}
124	out->len = olen;
125	zs.next_in = in->ptr;
126	zs.avail_in = ilen;
127	zs.next_out = out->ptr;
128	zs.avail_out = olen;
129
130	if ((z = deflate(&zs, Z_FINISH)) != Z_STREAM_END) {
131		fido_log_debug("%s: inflate: %d", __func__, z);
132		r = FIDO_ERR_COMPRESS;
133		goto fail;
134	}
135	if (zs.avail_out >= out->len) {
136		fido_log_debug("%s: %u > %zu", __func__, zs.avail_out,
137		    out->len);
138		r = FIDO_ERR_COMPRESS;
139		goto fail;
140	}
141	out->len -= zs.avail_out;
142
143	r = FIDO_OK;
144fail:
145	if ((z = deflateEnd(&zs)) != Z_OK) {
146		fido_log_debug("%s: deflateEnd: %d", __func__, z);
147		r = FIDO_ERR_COMPRESS;
148	}
149	if (r != FIDO_OK)
150		fido_blob_reset(out);
151
152	return r;
153}
154
155int
156fido_compress(fido_blob_t *out, const fido_blob_t *in)
157{
158	return rfc1951_deflate(out, in);
159}
160
161int
162fido_uncompress(fido_blob_t *out, const fido_blob_t *in, size_t origsiz)
163{
164	if (rfc1950_inflate(out, in, origsiz) == FIDO_OK)
165		return FIDO_OK; /* backwards compat with libfido2 < 1.11 */
166	return rfc1951_inflate(out, in, origsiz);
167}
168