diff --git a/Makefile.am b/Makefile.am index 0f95577..95ceb8f 100644 --- a/Makefile.am +++ b/Makefile.am @@ -252,3 +252,7 @@ t_test_str_to_rrtype_LDADD = wdns/libwdns.la TESTS += t/test-fast_inet_ntop check_PROGRAMS += t/test-fast_inet_ntop t_test_fast_inet_ntop_SOURCES = t/test-fast_inet_ntop.c t/test-common.c + +TESTS += t/test-b64_decode +check_PROGRAMS += t/test-b64_decode +t_test_b64_decode_SOURCES = t/test-b64_decode.c t/test-common.c diff --git a/libmy/b64_decode.c b/libmy/b64_decode.c index aeaf8ca..ba059f9 100644 --- a/libmy/b64_decode.c +++ b/libmy/b64_decode.c @@ -9,11 +9,11 @@ For details, see http://sourceforge.net/projects/libb64 int base64_decode_value(char value_in) { - static const char decoding[] = {62,-1,-1,-1,63,52,53,54,55,56,57,58,59,60,61,-1,-1,-1,-2,-1,-1,-1,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,-1,-1,-1,-1,-1,-1,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51}; - static const char decoding_size = sizeof(decoding); - value_in -= 43; - if (value_in < 0 || value_in > decoding_size) return -1; - return decoding[(int)value_in]; + static const signed char decoding[] = {62,-1,-1,-1,63,52,53,54,55,56,57,58,59,60,61,-1,-1,-1,-2,-1,-1,-1,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,-1,-1,-1,-1,-1,-1,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51}; + static const int decoding_size = sizeof(decoding); + int offset = (int) (unsigned char) value_in - 43; + if (offset < 0 || offset >= decoding_size) return -1; + return decoding[offset]; } void base64_init_decodestate(base64_decodestate* state_in) @@ -26,7 +26,7 @@ int base64_decode_block(const char* code_in, const int length_in, char* plaintex { const char* codechar = code_in; char* plainchar = plaintext_out; - char fragment; + int fragment; *plainchar = state_in->plainchar; @@ -42,7 +42,7 @@ int base64_decode_block(const char* code_in, const int length_in, char* plaintex state_in->plainchar = *plainchar; return plainchar - plaintext_out; } - fragment = (char)base64_decode_value(*codechar++); + fragment = base64_decode_value(*codechar++); } while (fragment < 0); *plainchar = (fragment & 0x03f) << 2; case step_b: @@ -53,7 +53,7 @@ int base64_decode_block(const char* code_in, const int length_in, char* plaintex state_in->plainchar = *plainchar; return plainchar - plaintext_out; } - fragment = (char)base64_decode_value(*codechar++); + fragment = base64_decode_value(*codechar++); } while (fragment < 0); *plainchar++ |= (fragment & 0x030) >> 4; *plainchar = (fragment & 0x00f) << 4; @@ -65,7 +65,7 @@ int base64_decode_block(const char* code_in, const int length_in, char* plaintex state_in->plainchar = *plainchar; return plainchar - plaintext_out; } - fragment = (char)base64_decode_value(*codechar++); + fragment = base64_decode_value(*codechar++); } while (fragment < 0); *plainchar++ |= (fragment & 0x03c) >> 2; *plainchar = (fragment & 0x003) << 6; @@ -77,7 +77,7 @@ int base64_decode_block(const char* code_in, const int length_in, char* plaintex state_in->plainchar = *plainchar; return plainchar - plaintext_out; } - fragment = (char)base64_decode_value(*codechar++); + fragment = base64_decode_value(*codechar++); } while (fragment < 0); *plainchar++ |= (fragment & 0x03f); } diff --git a/t/test-b64_decode.c b/t/test-b64_decode.c new file mode 100644 index 0000000..db1581a --- /dev/null +++ b/t/test-b64_decode.c @@ -0,0 +1,140 @@ +/* + * Copyright (c) 2026 DomainTools LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "test-common.h" + +#include "libmy/b64_decode.h" +#include "libmy/b64_decode.c" + +#include + +#define NAME "test-b64_decode" + +/* + * base64_decode_value() must report every non-alphabet octet as negative, + * whatever the sign of a plain char is on this platform. base64_decode_block() + * relies on that to skip padding and whitespace. + */ +static size_t +test_decode_value(void) { + size_t failures = 0; + size_t i; + static const struct { + char input; + int expected; + } testdata[] = { + { 'A', 0 }, + { 'Z', 25 }, + { 'a', 26 }, + { 'z', 51 }, + { '0', 52 }, + { '9', 61 }, + { '+', 62 }, + { '/', 63 }, + { '=', -2 }, + { ' ', -1 }, + { '\t', -1 }, + { '\n', -1 }, + { '\r', -1 }, + { '-', -1 }, + { '{', -1 }, /* one past the end of the decoding table */ + { '\x7f', -1 }, + { '\xff', -1 }, + }; + + for (i = 0; i < sizeof(testdata)/sizeof(testdata[0]); i++) { + int res = base64_decode_value(testdata[i].input); + + if (res == testdata[i].expected) { + fprintf(stderr, "PASS %zu: base64_decode_value(0x%02x) = %d\n", + i, (unsigned char) testdata[i].input, res); + } else { + fprintf(stderr, "FAIL %zu: base64_decode_value(0x%02x) = %d != %d\n", + i, (unsigned char) testdata[i].input, res, + testdata[i].expected); + failures++; + } + } + + return (failures); +} + +/* + * Decoding must ignore padding and embedded whitespace. DNSKEY, CDNSKEY, + * RRSIG and OPENPGPKEY presentation format all carry both. + */ +static size_t +test_decode_block(void) { + size_t failures = 0; + size_t i; + static const struct { + const char *input; + const char *expected; + size_t elen; + } testdata[] = { + { "AQIDBAUGBwg=", "\x01\x02\x03\x04\x05\x06\x07\x08", 8 }, + { "ZGVhZGJlZWY=", "deadbeef", 8 }, + { "ZGVhZGJlZWY==", "deadbeef", 8 }, + { "ZGVh ZGJl ZWY=", "deadbeef", 8 }, + { "ZGVhZGJl\nZWY=", "deadbeef", 8 }, + { "ZGVhZGJl\r\n\tZWY=", "deadbeef", 8 }, + { " ZGVhZGJlZWY= ", "deadbeef", 8 }, + { "AA==", "\x00", 1 }, + { "", "", 0 }, + }; + + for (i = 0; i < sizeof(testdata)/sizeof(testdata[0]); i++) { + base64_decodestate b64; + char buf[64]; + int len; + + memset(buf, 0, sizeof(buf)); + base64_init_decodestate(&b64); + len = base64_decode_block(testdata[i].input, + strlen(testdata[i].input), + buf, &b64); + + if (len == (int) testdata[i].elen && + memcmp(buf, testdata[i].expected, testdata[i].elen) == 0) + { + fprintf(stderr, "PASS %zu: base64_decode_block(\"%s\") len %d\n", + i, testdata[i].input, len); + } else { + ubuf *u = ubuf_init(64); + + escape(u, (const uint8_t *) buf, len < 0 ? 0 : (size_t) len); + fprintf(stderr, "FAIL %zu: base64_decode_block(\"%s\") len %d != %zu value=%s\n", + i, testdata[i].input, len, testdata[i].elen, + ubuf_cstr(u)); + ubuf_destroy(&u); + failures++; + } + } + + return (failures); +} + +int main (void) { + int ret = 0; + + ret |= check(test_decode_value(), "test_decode_value", NAME); + ret |= check(test_decode_block(), "test_decode_block", NAME); + + if (ret) + return (EXIT_FAILURE); + + return (EXIT_SUCCESS); +}