diff --git a/lib/roken/base64.c b/lib/roken/base64.c index 1391c8280..58f37e1ca 100644 --- a/lib/roken/base64.c +++ b/lib/roken/base64.c @@ -167,8 +167,132 @@ rk_base64_decode(const char *str, void *data) return q - (unsigned char *) data; } +/* + * Base64URL encoding/decoding (RFC 4648 Section 5) + * + * Uses '-' and '_' instead of '+' and '/', and omits padding. + * This is the encoding used by JWT/JWS (RFC 7515). + */ + +#define base64url_chars "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_" + +ROKEN_LIB_FUNCTION int ROKEN_LIB_CALL +rk_base64url_encode(const void *data, int size, char **str) +{ + char *s, *p; + int i; + int c; + const unsigned char *q; + + if (size > INT_MAX/4 || size < 0) { + *str = NULL; + errno = ERANGE; + return -1; + } + + p = s = (char *) malloc(size * 4 / 3 + 4); + if (p == NULL) { + *str = NULL; + return -1; + } + q = (const unsigned char *) data; + + for (i = 0; i < size;) { + c = q[i++]; + c *= 256; + if (i < size) + c += q[i]; + i++; + c *= 256; + if (i < size) + c += q[i]; + i++; + p[0] = base64url_chars[(c & 0x00fc0000) >> 18]; + p[1] = base64url_chars[(c & 0x0003f000) >> 12]; + /* Omit padding characters */ + if (i > size + 1) { + p += 2; + break; + } + p[2] = base64url_chars[(c & 0x00000fc0) >> 6]; + if (i > size) { + p += 3; + break; + } + p[3] = base64url_chars[(c & 0x0000003f) >> 0]; + p += 4; + } + *p = 0; + *str = s; + return (int) strlen(s); +} + +static int +pos_url(char c) +{ + if (c >= 'A' && c <= 'Z') + return c - 'A'; + if (c >= 'a' && c <= 'z') + return ('Z' + 1 - 'A') + c - 'a'; + if (c >= '0' && c <= '9') + return ('Z' + 1 - 'A') + ('z' + 1 - 'a') + c - '0'; + if (c == '-') + return 62; + if (c == '_') + return 63; + return -1; +} + +ROKEN_LIB_FUNCTION int ROKEN_LIB_CALL +rk_base64url_decode(const char *str, void *data) +{ + const char *p; + unsigned char *q; + int i; + unsigned int val; + int marker; + + q = data; + + for (p = str; *p && pos_url(*p) != -1; ) { + val = 0; + marker = 0; + + /* Decode up to 4 characters, handling missing padding */ + for (i = 0; i < 4; i++) { + val *= 64; + if (*p && pos_url(*p) != -1) { + val += pos_url(*p); + p++; + } else { + /* Implicit padding */ + marker++; + } + } + + *q++ = (val >> 16) & 0xff; + if (marker < 2) + *q++ = (val >> 8) & 0xff; + if (marker < 1) + *q++ = val & 0xff; + } + + /* Check for invalid trailing characters */ + if (*p != '\0') { + errno = EINVAL; + return -1; + } + + if (q - (unsigned char *) data > INT_MAX) { + errno = EOVERFLOW; + return -1; + } + return q - (unsigned char *) data; +} + #ifdef TEST static int decode_flag; +static int url_safe_flag; static int help_flag; /* @@ -177,6 +301,7 @@ static int help_flag; */ static struct getargs args[] = { { "decode", 'd', arg_flag, &decode_flag, "Decode", NULL }, + { "url-safe", 'u', arg_flag, &url_safe_flag, "Use URL-safe alphabet", NULL }, { "help", 'h', arg_flag, &help_flag, "Print help message", NULL }, }; static size_t num_args = sizeof(args)/sizeof(args[0]); @@ -248,16 +373,24 @@ main(int argc, char **argv) if ((d = malloc(buflen * 3 / 4 + 4)) == NULL) err(1, "Could not decode data"); - if ((ret = rk_base64_decode((const char *)buf, d)) < 0) + if (url_safe_flag) { + if ((ret = rk_base64url_decode((const char *)buf, d)) < 0) + err(1, "Could not decode data"); + } else if ((ret = rk_base64_decode((const char *)buf, d)) < 0) { err(1, "Could not decode data"); + } if (fwrite(d, ret, 1, stdout) != 1) err(1, "Could not write decoded data"); free(d); } else if (buf) { /* buf can be NULL if we read from an empty file */ char *e; - if ((ret = rk_base64_encode(buf, buflen, &e)) < 0) + if (url_safe_flag) { + if ((ret = rk_base64url_encode(buf, buflen, &e)) < 0) + err(1, "Could not encode data"); + } else if ((ret = rk_base64_encode(buf, buflen, &e)) < 0) { err(1, "Could not encode data"); + } if (fwrite(e, ret, 1, stdout) != 1) err(1, "Could not write decoded data"); free(e); diff --git a/lib/roken/base64.h b/lib/roken/base64.h index 734a6b5d9..2b522e250 100644 --- a/lib/roken/base64.h +++ b/lib/roken/base64.h @@ -52,4 +52,15 @@ rk_base64_encode(const void *, int, char **); ROKEN_LIB_FUNCTION int ROKEN_LIB_CALL rk_base64_decode(const char *, void *); +/* + * Base64URL encoding/decoding (RFC 4648 Section 5) + * Uses '-' and '_' instead of '+' and '/', omits padding. + * Used by JWT/JWS (RFC 7515). + */ +ROKEN_LIB_FUNCTION int ROKEN_LIB_CALL +rk_base64url_encode(const void *, int, char **); + +ROKEN_LIB_FUNCTION int ROKEN_LIB_CALL +rk_base64url_decode(const char *, void *); + #endif diff --git a/lib/roken/version-script.map b/lib/roken/version-script.map index 61c0a4b05..76dbedad8 100644 --- a/lib/roken/version-script.map +++ b/lib/roken/version-script.map @@ -38,6 +38,8 @@ HEIMDAL_ROKEN_2.0 { rk_base32_encode; rk_base64_decode; rk_base64_encode; + rk_base64url_decode; + rk_base64url_encode; rk_bswap16; rk_bswap32; rk_bswap64;