Skip to content

Commit fbd5ef1

Browse files
committed
Add: New data stream validation utilities
New utility functions have been added to allow validating the size and checksum / hash of contents of a data stream like a file being read.
1 parent 67e48c9 commit fbd5ef1

3 files changed

Lines changed: 283 additions & 0 deletions

File tree

util/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@ set(
135135
radiusutils.c
136136
serverutils.c
137137
sshutils.c
138+
streamvalidator.c
138139
uuidutils.c
139140
versionutils.c
140141
vtparser.c
@@ -158,6 +159,7 @@ set(
158159
radiusutils.h
159160
serverutils.h
160161
sshutils.h
162+
streamvalidator.h
161163
uuidutils.h
162164
versionutils.h
163165
vtparser.h

util/streamvalidator.c

Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
/* SPDX-FileCopyrightText: 2025 Greenbone AG
2+
*
3+
* SPDX-License-Identifier: GPL-2.0-or-later
4+
*/
5+
6+
#include <assert.h>
7+
#include <glib.h>
8+
#include <gcrypt.h>
9+
#include "authutils.h"
10+
#include "streamvalidator.h"
11+
12+
/**
13+
* @file
14+
* @brief Data stream validation.
15+
*/
16+
17+
/**
18+
* @brief Data stream validator structure.
19+
*/
20+
struct gvm_stream_validator {
21+
gchar *expected_hash_str; ///< Expected hash algorithm and hex string.
22+
gchar *expected_hash_hex; ///< Expected hash value as hexadecimal string.
23+
int algorithm; ///< The hash algorithm used.
24+
size_t expected_size; ///< Expected amount of data to validate.
25+
size_t current_size; ///< Current total amount of data received.
26+
gcry_md_hd_t gcrypt_md_hd; ///< gcrypt message digest handle.
27+
};
28+
29+
/**
30+
* @brief Gets a string representation of a gvm_stream_validator_return_t
31+
*
32+
* @param[in] value The value to get a string representation of.
33+
*
34+
* @return Static string describing the return value
35+
* or NULL on success.
36+
*/
37+
const char *
38+
gvm_stream_validator_return_str (gvm_stream_validator_return_t value)
39+
{
40+
switch (value)
41+
{
42+
case GVM_STREAM_VALIDATOR_INTERNAL_ERROR:
43+
return "internal error";
44+
case GVM_STREAM_VALIDATOR_OK:
45+
return NULL;
46+
case GVM_STREAM_VALIDATOR_DATA_TOO_SHORT:
47+
return "too short";
48+
case GVM_STREAM_VALIDATOR_DATA_TOO_LONG:
49+
return "too long";
50+
case GVM_STREAM_VALIDATOR_INVALID_HASH_SYNTAX:
51+
return "invalid hash syntax";
52+
case GVM_STREAM_VALIDATOR_INVALID_HASH_ALGORITHM:
53+
return "invalid or unsupported hash algorithm";
54+
case GVM_STREAM_VALIDATOR_INVALID_HASH_VALUE:
55+
return "invalid hash value";
56+
case GVM_STREAM_VALIDATOR_HASH_MISMATCH:
57+
return "hash does not match";
58+
default:
59+
return "unknown error";
60+
}
61+
}
62+
63+
/**
64+
* @brief Allocate and initialize a new data stream validator.
65+
*
66+
* @param[in] expected_hash_str Expected hash / checksum string consisting of
67+
* an algorithm name or OID as recognized by
68+
* gcrypt, followed by a colon and the
69+
* hex-encoded hash,
70+
* e.g. "md5:70165459812a0d38851a4a4c3e4124c9".
71+
* @param[in] expected_size The number of bytes expected to be sent.
72+
* @param[out] validator_out Pointer to output location of the newly allocated
73+
* validator.
74+
*
75+
* @return A validator return code, returning a failure if the expeced hash
76+
* string is invalid or uses an unsupported algorithm.
77+
*/
78+
gvm_stream_validator_return_t
79+
gvm_stream_validator_new (const char *expected_hash_str,
80+
size_t expected_size,
81+
gvm_stream_validator_t *validator_out)
82+
{
83+
assert (validator_out);
84+
85+
static GRegex* hex_regex = NULL;
86+
gchar **split_hash_str = g_strsplit (expected_hash_str, ":", 2);
87+
const char *algo_str, *hex_str;
88+
int algo;
89+
unsigned int expected_hex_len;
90+
gcry_md_hd_t gcrypt_md_hd;
91+
92+
if (hex_regex == NULL)
93+
hex_regex = g_regex_new ("^(?:[0-9A-Fa-f][0-9A-Fa-f])+$",
94+
G_REGEX_DEFAULT,
95+
G_REGEX_MATCH_DEFAULT,
96+
NULL);
97+
98+
*validator_out = NULL;
99+
if (g_strv_length (split_hash_str) != 2)
100+
{
101+
g_strfreev (split_hash_str);
102+
return GVM_STREAM_VALIDATOR_INVALID_HASH_SYNTAX;
103+
}
104+
algo_str = split_hash_str[0];
105+
hex_str = split_hash_str[1];
106+
107+
algo = gcry_md_map_name (algo_str);
108+
if (algo == GCRY_MD_NONE || gcry_md_test_algo (algo))
109+
{
110+
g_strfreev (split_hash_str);
111+
return GVM_STREAM_VALIDATOR_INVALID_HASH_ALGORITHM;
112+
}
113+
114+
expected_hex_len = gcry_md_get_algo_dlen (algo) * 2;
115+
if (strlen (hex_str) != expected_hex_len
116+
|| g_regex_match (hex_regex, hex_str, 0, NULL) == FALSE)
117+
{
118+
g_strfreev (split_hash_str);
119+
return GVM_STREAM_VALIDATOR_INVALID_HASH_VALUE;
120+
}
121+
122+
gcrypt_md_hd = NULL;
123+
if (gcry_md_open (&gcrypt_md_hd, algo, 0))
124+
{
125+
g_strfreev (split_hash_str);
126+
return GVM_STREAM_VALIDATOR_INTERNAL_ERROR;
127+
}
128+
129+
*validator_out = g_malloc0 (sizeof (struct gvm_stream_validator));
130+
(*validator_out)->algorithm = algo;
131+
(*validator_out)->expected_size = expected_size;
132+
(*validator_out)->expected_hash_str = g_strdup (expected_hash_str);
133+
(*validator_out)->expected_hash_hex = g_strdup (hex_str);
134+
(*validator_out)->gcrypt_md_hd = gcrypt_md_hd;
135+
136+
g_strfreev (split_hash_str);
137+
138+
return GVM_STREAM_VALIDATOR_OK;
139+
}
140+
141+
/**
142+
* @brief Rewind the validation state of a stream validator while keeping the
143+
* expected hash and data size.
144+
*
145+
* @param[in] validator The validator to rewind.
146+
*/
147+
void
148+
gvm_stream_validator_rewind (gvm_stream_validator_t validator)
149+
{
150+
gcry_md_reset (validator->gcrypt_md_hd);
151+
validator->current_size = 0;
152+
}
153+
154+
/**
155+
* @brief Free a stream validator and all of its fields.
156+
*/
157+
void
158+
gvm_stream_validator_free (gvm_stream_validator_t validator)
159+
{
160+
gcry_md_close (validator->gcrypt_md_hd);
161+
g_free (validator->expected_hash_str);
162+
g_free (validator->expected_hash_hex);
163+
g_free (validator);
164+
}
165+
166+
/**
167+
* @brief Write data to a validator, updating the hash state and current size.
168+
*
169+
* Will fail if the total data size exceeds the expected size.
170+
*
171+
* @param[in] validator The validator to handle the data
172+
* @param[in] data The data to write.
173+
* @param[in] length Length of the data.
174+
*
175+
* @return Validator return code, either a "success" or "too long".
176+
*/
177+
gvm_stream_validator_return_t
178+
gvm_stream_validator_write (gvm_stream_validator_t validator,
179+
const char *data, size_t length)
180+
{
181+
if (length > validator->expected_size - validator->current_size)
182+
return GVM_STREAM_VALIDATOR_DATA_TOO_LONG;
183+
184+
gcry_md_write (validator->gcrypt_md_hd, data, length);
185+
validator->current_size += length;
186+
187+
return GVM_STREAM_VALIDATOR_OK;
188+
}
189+
190+
/**
191+
* @brief Signal the end of data input into a validator and produce the result
192+
* of the validation.
193+
*
194+
* @param[in] validator The validator to signal the end of data input of.
195+
*
196+
* @return The validation result.
197+
*/
198+
gvm_stream_validator_return_t
199+
gvm_stream_validator_end (gvm_stream_validator_t validator)
200+
{
201+
unsigned char *actual_hash_bin;
202+
gchar *actual_hash_hex;
203+
204+
if (validator->current_size < validator->expected_size)
205+
return GVM_STREAM_VALIDATOR_DATA_TOO_SHORT;
206+
207+
if (validator->current_size > validator->expected_size)
208+
return GVM_STREAM_VALIDATOR_DATA_TOO_LONG;
209+
210+
actual_hash_bin = gcry_md_read (validator->gcrypt_md_hd,
211+
validator->algorithm);
212+
actual_hash_hex = digest_hex (validator->algorithm,
213+
actual_hash_bin);
214+
if (strcasecmp (validator->expected_hash_hex, actual_hash_hex))
215+
{
216+
g_free (actual_hash_hex);
217+
return GVM_STREAM_VALIDATOR_HASH_MISMATCH;
218+
}
219+
g_free (actual_hash_hex);
220+
221+
return GVM_STREAM_VALIDATOR_OK;
222+
}

util/streamvalidator.h

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/* SPDX-FileCopyrightText: 2025 Greenbone AG
2+
*
3+
* SPDX-License-Identifier: GPL-2.0-or-later
4+
*/
5+
6+
#ifndef _GVM_STREAMVALIDATOR_H
7+
#define _GVM_STREAMVALIDATOR_H
8+
9+
#include <stdio.h>
10+
11+
/**
12+
* @file
13+
* @brief Data stream validation headers.
14+
*/
15+
16+
typedef enum {
17+
/** An internal error ocurred. */
18+
GVM_STREAM_VALIDATOR_INTERNAL_ERROR = -1,
19+
/** Action successful / data is valid. */
20+
GVM_STREAM_VALIDATOR_OK = 0,
21+
/** Not enough data received. */
22+
GVM_STREAM_VALIDATOR_DATA_TOO_SHORT = 1,
23+
/** Too much data received. */
24+
GVM_STREAM_VALIDATOR_DATA_TOO_LONG,
25+
/** Syntax error in hash string (not using "algo:hex" format). */
26+
GVM_STREAM_VALIDATOR_INVALID_HASH_SYNTAX,
27+
/** Invalid or unsupported hash algorithm. */
28+
GVM_STREAM_VALIDATOR_INVALID_HASH_ALGORITHM,
29+
/** Hash value is not valid.
30+
* (e.g. not hexadecimal or length does not match algorithm) */
31+
GVM_STREAM_VALIDATOR_INVALID_HASH_VALUE,
32+
/** Hash of received data does not match the expected hash */
33+
GVM_STREAM_VALIDATOR_HASH_MISMATCH
34+
} gvm_stream_validator_return_t;
35+
36+
/**
37+
* @brief Pointer to an opaque stream validator data structure.
38+
*/
39+
typedef struct gvm_stream_validator* gvm_stream_validator_t;
40+
41+
const char *
42+
gvm_stream_validator_return_str (gvm_stream_validator_return_t);
43+
44+
gvm_stream_validator_return_t
45+
gvm_stream_validator_new (const char *, size_t, gvm_stream_validator_t*);
46+
47+
void
48+
gvm_stream_validator_rewind (gvm_stream_validator_t);
49+
50+
void
51+
gvm_stream_validator_free (gvm_stream_validator_t);
52+
53+
gvm_stream_validator_return_t
54+
gvm_stream_validator_write (gvm_stream_validator_t, const char *, size_t);
55+
56+
gvm_stream_validator_return_t
57+
gvm_stream_validator_end (gvm_stream_validator_t);
58+
59+
#endif /* not _GVM_STREAMVALIDATOR_H */

0 commit comments

Comments
 (0)