diff --git a/doc/configuration.txt b/doc/configuration.txt index f21a29a68..f242300e7 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -15660,11 +15660,14 @@ and() b64dec Converts (decodes) a base64 encoded input string to its binary representation. It performs the inverse operation of base64(). + For base64url("URL and Filename Safe Alphabet" (RFC 4648)) variant + see "ub64dec". base64 Converts a binary input sample to a base64 string. It is used to log or transfer binary content in a way that can be reliably transferred (e.g. - an SSL ID can be copied in a header). + an SSL ID can be copied in a header). For base64url("URL and Filename + Safe Alphabet" (RFC 4648)) variant see "ub64enc". bool Returns a boolean TRUE if the input value of type signed integer is @@ -16565,6 +16568,18 @@ table_trackers() connections there are from a given address for example. See also the sc_trackers sample fetch keyword. +ub64dec + This converter is the base64url variant of b64dec converter. base64url + encoding is the "URL and Filename Safe Alphabet" variant of base64 encoding. + It is also the encoding used in JWT (JSON Web Token) standard. + + Example: + # Decoding a JWT payload: + http-request set-var(txn.token_payload) req.hdr(Authorization),word(2,.),ub64dec + +ub64enc + This converter is the base64url variant of base64 converter. + upper Convert a string sample to upper case. This can only be placed after a string sample fetch function or after a transformation keyword returning a string diff --git a/include/haproxy/base64.h b/include/haproxy/base64.h index 1756bc058..ace606372 100644 --- a/include/haproxy/base64.h +++ b/include/haproxy/base64.h @@ -17,7 +17,9 @@ #include int a2base64(char *in, int ilen, char *out, int olen); +int a2base64url(const char *in, size_t ilen, char *out, size_t olen); int base64dec(const char *in, size_t ilen, char *out, size_t olen); +int base64urldec(const char *in, size_t ilen, char *out, size_t olen); const char *s30tob64(int in, char *out); int b64tos30(const char *in); diff --git a/reg-tests/sample_fetches/ubase64.vtc b/reg-tests/sample_fetches/ubase64.vtc new file mode 100644 index 000000000..d6973e3b7 --- /dev/null +++ b/reg-tests/sample_fetches/ubase64.vtc @@ -0,0 +1,51 @@ +varnishtest "ub64dec sample fetche Test" + +#REQUIRE_VERSION=2.4 + +feature ignore_unknown_macro + +haproxy h1 -conf { + defaults + mode http + timeout connect 1s + timeout client 1s + timeout server 1s + + frontend fe + bind "fd@${fe}" + acl input hdr(encode) -m found + http-request return content-type text/plain hdr encode %[hdr(encode),ub64enc] hdr decode %[hdr(decode),ub64dec] if input + http-request return content-type text/plain hdr encode %[bin(14fb9c03d97f12d97e),ub64enc] hdr decode %[str(FPucA9l_Etl-),ub64dec,hex,lower] if !input + +} -start + +client c1 -connect ${h1_fe_sock} { + txreq -hdr "encode: f" -hdr "decode: Zg" + rxresp + expect resp.http.encode == "Zg" + expect resp.http.decode == "f" + txreq -hdr "encode: fo" -hdr "decode: Zm8" + rxresp + expect resp.http.encode == "Zm8" + expect resp.http.decode == "fo" + txreq -hdr "encode: foo" -hdr "decode: Zm9v" + rxresp + expect resp.http.encode == "Zm9v" + expect resp.http.decode == "foo" + txreq -hdr "encode: foob" -hdr "decode: Zm9vYg" + rxresp + expect resp.http.encode == "Zm9vYg" + expect resp.http.decode == "foob" + txreq -hdr "encode: fooba" -hdr "decode: Zm9vYmE" + rxresp + expect resp.http.encode == "Zm9vYmE" + expect resp.http.decode == "fooba" + txreq -hdr "encode: foobar" -hdr "decode: Zm9vYmFy" + rxresp + expect resp.http.encode == "Zm9vYmFy" + expect resp.http.decode == "foobar" + txreq + rxresp + expect resp.http.encode == "FPucA9l_Etl-" + expect resp.http.decode == "14fb9c03d97f12d97e" +} -run diff --git a/src/base64.c b/src/base64.c index 53e4d65b2..5c4ab3a47 100644 --- a/src/base64.c +++ b/src/base64.c @@ -19,11 +19,14 @@ #define B64BASE '#' /* arbitrary chosen base value */ #define B64CMIN '+' +#define UB64CMIN '-' #define B64CMAX 'z' #define B64PADV 64 /* Base64 chosen special pad value */ const char base64tab[65]="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; const char base64rev[]="b###cXYZ[\\]^_`a###d###$%&'()*+,-./0123456789:;<=######>?@ABCDEFGHIJKLMNOPQRSTUVW"; +const char ubase64tab[65]="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; +const char ubase64rev[]="b##XYZ[\\]^_`a###c###$%&'()*+,-./0123456789:;<=####c#>?@ABCDEFGHIJKLMNOPQRSTUVW"; /* Encodes bytes from to for at most chars (including * the trailing zero). Returns the number of bytes written. No check is made @@ -68,6 +71,48 @@ int a2base64(char *in, int ilen, char *out, int olen) return convlen; } +/* url variant of a2base64 */ +int a2base64url(const char *in, size_t ilen, char *out, size_t olen) +{ + int convlen; + + convlen = ((ilen + 2) / 3) * 4; + + if (convlen >= olen) + return -1; + + /* we don't need to check olen anymore */ + while (ilen >= 3) { + out[0] = ubase64tab[(((unsigned char)in[0]) >> 2)]; + out[1] = ubase64tab[(((unsigned char)in[0] & 0x03) << 4) | (((unsigned char)in[1]) >> 4)]; + out[2] = ubase64tab[(((unsigned char)in[1] & 0x0F) << 2) | (((unsigned char)in[2]) >> 6)]; + out[3] = ubase64tab[(((unsigned char)in[2] & 0x3F))]; + out += 4; + in += 3; + ilen -= 3; + } + + if (!ilen) { + out[0] = '\0'; + return convlen; + } + + out[0] = ubase64tab[((unsigned char)in[0]) >> 2]; + if (ilen == 1) { + out[1] = ubase64tab[((unsigned char)in[0] & 0x03) << 4]; + out[2] = '\0'; + convlen -= 2; + } else { + out[1] = ubase64tab[(((unsigned char)in[0] & 0x03) << 4) | + (((unsigned char)in[1]) >> 4)]; + out[2] = ubase64tab[((unsigned char)in[1] & 0x0F) << 2]; + out[3] = '\0'; + convlen -= 1; + } + + return convlen; +} + /* Decodes bytes from to for at most chars. * Returns the number of bytes converted. No check is made for * or to be NULL. Returns -1 if is invalid or ilen @@ -138,6 +183,72 @@ int base64dec(const char *in, size_t ilen, char *out, size_t olen) { return convlen; } +/* url variant of base64dec */ +/* The reverse tab used to decode base64 is generated via /dev/base64/base64rev-gen.c */ +int base64urldec(const char *in, size_t ilen, char *out, size_t olen) +{ + unsigned char t[4]; + signed char b; + int convlen = 0, i = 0, pad = 0, padlen = 0; + + if (olen < ((ilen / 4 * 3))) + return -2; + + switch (ilen % 4) { + case 0: + break; + case 2: + padlen = pad = 2; + break; + case 3: + padlen = pad = 1; + break; + default: + return -1; + } + + while (ilen + pad) { + if (ilen) { + /* if (*p < UB64CMIN || *p > B64CMAX) */ + b = (signed char) * in - UB64CMIN; + if ((unsigned char)b > (B64CMAX - UB64CMIN)) + return -1; + + b = ubase64rev[b] - B64BASE - 1; + /* b == -1: invalid character */ + if (b < 0) + return -1; + + in++; + ilen--; + + } else { + b = B64PADV; + pad--; + } + + t[i++] = b; + + if (i == 4) { + /* + * WARNING: we allow to write little more data than we + * should, but the checks from the beginning of the + * functions guarantee that we can safely do that. + */ + + /* xx000000 xx001111 xx111122 xx222222 */ + out[convlen] = ((t[0] << 2) + (t[1] >> 4)); + out[convlen + 1] = ((t[1] << 4) + (t[2] >> 2)); + out[convlen + 2] = ((t[2] << 6) + (t[3] >> 0)); + + convlen += 3; + i = 0; + } + } + convlen -= padlen; + + return convlen; +} /* Converts the lower 30 bits of an integer to a 5-char base64 string. The * caller is responsible for ensuring that the output buffer can accept 6 bytes diff --git a/src/sample.c b/src/sample.c index 0c0f36ec5..04635a91f 100644 --- a/src/sample.c +++ b/src/sample.c @@ -1567,6 +1567,24 @@ static int sample_conv_base642bin(const struct arg *arg_p, struct sample *smp, v return 1; } +static int sample_conv_base64url2bin(const struct arg *arg_p, struct sample *smp, void *private) +{ + struct buffer *trash = get_trash_chunk(); + int bin_len; + + trash->data = 0; + bin_len = base64urldec(smp->data.u.str.area, smp->data.u.str.data, + trash->area, trash->size); + if (bin_len < 0) + return 0; + + trash->data = bin_len; + smp->data.u.str = *trash; + smp->data.type = SMP_T_BIN; + smp->flags &= ~SMP_F_CONST; + return 1; +} + static int sample_conv_bin2base64(const struct arg *arg_p, struct sample *smp, void *private) { struct buffer *trash = get_trash_chunk(); @@ -1585,6 +1603,24 @@ static int sample_conv_bin2base64(const struct arg *arg_p, struct sample *smp, v return 1; } +static int sample_conv_bin2base64url(const struct arg *arg_p, struct sample *smp, void *private) +{ + struct buffer *trash = get_trash_chunk(); + int b64_len; + + trash->data = 0; + b64_len = a2base64url(smp->data.u.str.area, smp->data.u.str.data, + trash->area, trash->size); + if (b64_len < 0) + return 0; + + trash->data = b64_len; + smp->data.u.str = *trash; + smp->data.type = SMP_T_STR; + smp->flags &= ~SMP_F_CONST; + return 1; +} + static int sample_conv_sha1(const struct arg *arg_p, struct sample *smp, void *private) { blk_SHA_CTX ctx; @@ -4096,6 +4132,8 @@ static struct sample_conv_kw_list sample_conv_kws = {ILH, { { "debug", sample_conv_debug, ARG2(0,STR,STR), smp_check_debug, SMP_T_ANY, SMP_T_ANY }, { "b64dec", sample_conv_base642bin,0, NULL, SMP_T_STR, SMP_T_BIN }, { "base64", sample_conv_bin2base64,0, NULL, SMP_T_BIN, SMP_T_STR }, + { "ub64dec", sample_conv_base64url2bin,0, NULL, SMP_T_STR, SMP_T_BIN }, + { "ub64enc", sample_conv_bin2base64url,0, NULL, SMP_T_BIN, SMP_T_STR }, { "upper", sample_conv_str2upper, 0, NULL, SMP_T_STR, SMP_T_STR }, { "lower", sample_conv_str2lower, 0, NULL, SMP_T_STR, SMP_T_STR }, { "length", sample_conv_length, 0, NULL, SMP_T_STR, SMP_T_SINT },