From 3fd59700357072c567785a1fb1430a55ef7bd45b Mon Sep 17 00:00:00 2001 From: Matt Caswell Date: Mon, 9 Apr 2018 15:50:20 +0100 Subject: [PATCH] Add support for the SRP base64 alphabet Historically we used to implement standalone base64 code for SRP. This was replaced by commit 3d3f21aa with the standard base64 processing code. However, the SRP base64 code was designed to be compatible with other SRP libraries (notably libsrp, but also others) that use a variant of standard base64. Specifically a different alphabet is used and no padding '=' characters are used. Instead 0 padding is added to the front of the string. By changing to standard base64 we change the behaviour of the API which may impact interoperability. It also means that SRP verifier files created prior to 1.1.1 would not be readable in 1.1.1 and vice versa. Instead we expand our standard base64 processing with the capability to be able to read and generate the SRP base64 variant. Reviewed-by: Andy Polyakov (Merged from https://github.com/openssl/openssl/pull/5925) --- crypto/evp/encode.c | 122 ++++++++++++++++++++++-------- crypto/include/internal/evp_int.h | 5 +- crypto/srp/srp_vfy.c | 101 +++++++++++++++++++++++-- 3 files changed, 190 insertions(+), 38 deletions(-) diff --git a/crypto/evp/encode.c b/crypto/evp/encode.c index a43755ab17..88e5a17767 100644 --- a/crypto/evp/encode.c +++ b/crypto/evp/encode.c @@ -14,9 +14,15 @@ #include "evp_locl.h" #include "internal/evp_int.h" -static unsigned char conv_ascii2bin(unsigned char a); +static unsigned char conv_ascii2bin(unsigned char a, + const unsigned char *table); +static int evp_encodeblock_int(EVP_ENCODE_CTX *ctx, unsigned char *t, + const unsigned char *f, int dlen); +static int evp_decodeblock_int(EVP_ENCODE_CTX *ctx, unsigned char *t, + const unsigned char *f, int n); + #ifndef CHARSET_EBCDIC -# define conv_bin2ascii(a) (data_bin2ascii[(a)&0x3f]) +# define conv_bin2ascii(a, table) ((table)[(a)&0x3f]) #else /* * We assume that PEM encoded files are EBCDIC files (i.e., printable text @@ -24,7 +30,7 @@ static unsigned char conv_ascii2bin(unsigned char a); * (text) format again. (No need for conversion in the conv_bin2ascii macro, * as the underlying textstring data_bin2ascii[] is already EBCDIC) */ -# define conv_bin2ascii(a) (data_bin2ascii[(a)&0x3f]) +# define conv_bin2ascii(a, table) ((table)[(a)&0x3f]) #endif /*- @@ -39,8 +45,13 @@ static unsigned char conv_ascii2bin(unsigned char a); #define CHUNKS_PER_LINE (64/4) #define CHAR_PER_LINE (64+1) -static const unsigned char data_bin2ascii[65] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ\ -abcdefghijklmnopqrstuvwxyz0123456789+/"; +static const unsigned char data_bin2ascii[65] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +/* SRP uses a different base64 alphabet */ +static const unsigned char srpdata_bin2ascii[65] = + "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz./"; + /*- * 0xF0 is a EOLN @@ -77,20 +88,39 @@ static const unsigned char data_ascii2bin[128] = { 0x31, 0x32, 0x33, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, }; +static const unsigned char srpdata_ascii2bin[128] = { + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xE0, 0xF0, 0xFF, 0xFF, 0xF1, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xE0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF2, 0x3E, 0x3F, + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0xFF, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, + 0xFF, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, + 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, + 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20, + 0x21, 0x22, 0x23, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, + 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 0x30, 0x31, 0x32, + 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, + 0x3B, 0x3C, 0x3D, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, +}; + #ifndef CHARSET_EBCDIC -static unsigned char conv_ascii2bin(unsigned char a) +static unsigned char conv_ascii2bin(unsigned char a, const unsigned char *table) { if (a & 0x80) return B64_ERROR; - return data_ascii2bin[a]; + return table[a]; } #else -static unsigned char conv_ascii2bin(unsigned char a) +static unsigned char conv_ascii2bin(unsigned char a, const unsigned char *table) { a = os_toascii[a]; if (a & 0x80) return B64_ERROR; - return data_ascii2bin[a]; + return table[a]; } #endif @@ -149,7 +179,7 @@ int EVP_EncodeUpdate(EVP_ENCODE_CTX *ctx, unsigned char *out, int *outl, memcpy(&(ctx->enc_data[ctx->num]), in, i); in += i; inl -= i; - j = EVP_EncodeBlock(out, ctx->enc_data, ctx->length); + j = evp_encodeblock_int(ctx, out, ctx->enc_data, ctx->length); ctx->num = 0; out += j; total = j; @@ -160,7 +190,7 @@ int EVP_EncodeUpdate(EVP_ENCODE_CTX *ctx, unsigned char *out, int *outl, *out = '\0'; } while (inl >= ctx->length && total <= INT_MAX) { - j = EVP_EncodeBlock(out, in, ctx->length); + j = evp_encodeblock_int(ctx, out, in, ctx->length); in += ctx->length; inl -= ctx->length; out += j; @@ -189,7 +219,7 @@ void EVP_EncodeFinal(EVP_ENCODE_CTX *ctx, unsigned char *out, int *outl) unsigned int ret = 0; if (ctx->num != 0) { - ret = EVP_EncodeBlock(out, ctx->enc_data, ctx->num); + ret = evp_encodeblock_int(ctx, out, ctx->enc_data, ctx->num); if ((ctx->flags & EVP_ENCODE_CTX_NO_NEWLINES) == 0) out[ret++] = '\n'; out[ret] = '\0'; @@ -198,27 +228,34 @@ void EVP_EncodeFinal(EVP_ENCODE_CTX *ctx, unsigned char *out, int *outl) *outl = ret; } -int EVP_EncodeBlock(unsigned char *t, const unsigned char *f, int dlen) +static int evp_encodeblock_int(EVP_ENCODE_CTX *ctx, unsigned char *t, + const unsigned char *f, int dlen) { int i, ret = 0; unsigned long l; + const unsigned char *table; + + if (ctx != NULL && (ctx->flags & EVP_ENCODE_CTX_USE_SRP_ALPHABET) != 0) + table = srpdata_bin2ascii; + else + table = data_bin2ascii; for (i = dlen; i > 0; i -= 3) { if (i >= 3) { l = (((unsigned long)f[0]) << 16L) | (((unsigned long)f[1]) << 8L) | f[2]; - *(t++) = conv_bin2ascii(l >> 18L); - *(t++) = conv_bin2ascii(l >> 12L); - *(t++) = conv_bin2ascii(l >> 6L); - *(t++) = conv_bin2ascii(l); + *(t++) = conv_bin2ascii(l >> 18L, table); + *(t++) = conv_bin2ascii(l >> 12L, table); + *(t++) = conv_bin2ascii(l >> 6L, table); + *(t++) = conv_bin2ascii(l, table); } else { l = ((unsigned long)f[0]) << 16L; if (i == 2) l |= ((unsigned long)f[1] << 8L); - *(t++) = conv_bin2ascii(l >> 18L); - *(t++) = conv_bin2ascii(l >> 12L); - *(t++) = (i == 1) ? '=' : conv_bin2ascii(l >> 6L); + *(t++) = conv_bin2ascii(l >> 18L, table); + *(t++) = conv_bin2ascii(l >> 12L, table); + *(t++) = (i == 1) ? '=' : conv_bin2ascii(l >> 6L, table); *(t++) = '='; } ret += 4; @@ -229,6 +266,11 @@ int EVP_EncodeBlock(unsigned char *t, const unsigned char *f, int dlen) return ret; } +int EVP_EncodeBlock(unsigned char *t, const unsigned char *f, int dlen) +{ + return evp_encodeblock_int(NULL, t, f, dlen); +} + void EVP_DecodeInit(EVP_ENCODE_CTX *ctx) { /* Only ctx->num and ctx->flags are used during decoding. */ @@ -263,6 +305,7 @@ int EVP_DecodeUpdate(EVP_ENCODE_CTX *ctx, unsigned char *out, int *outl, { int seof = 0, eof = 0, rv = -1, ret = 0, i, v, tmp, n, decoded_len; unsigned char *d; + const unsigned char *table; n = ctx->num; d = ctx->enc_data; @@ -279,9 +322,14 @@ int EVP_DecodeUpdate(EVP_ENCODE_CTX *ctx, unsigned char *out, int *outl, goto end; } + if ((ctx->flags & EVP_ENCODE_CTX_USE_SRP_ALPHABET) != 0) + table = srpdata_ascii2bin; + else + table = data_ascii2bin; + for (i = 0; i < inl; i++) { tmp = *(in++); - v = conv_ascii2bin(tmp); + v = conv_ascii2bin(tmp, table); if (v == B64_ERROR) { rv = -1; goto end; @@ -321,7 +369,7 @@ int EVP_DecodeUpdate(EVP_ENCODE_CTX *ctx, unsigned char *out, int *outl, } if (n == 64) { - decoded_len = EVP_DecodeBlock(out, d, n); + decoded_len = evp_decodeblock_int(ctx, out, d, n); n = 0; if (decoded_len < 0 || eof > decoded_len) { rv = -1; @@ -340,7 +388,7 @@ int EVP_DecodeUpdate(EVP_ENCODE_CTX *ctx, unsigned char *out, int *outl, tail: if (n > 0) { if ((n & 3) == 0) { - decoded_len = EVP_DecodeBlock(out, d, n); + decoded_len = evp_decodeblock_int(ctx, out, d, n); n = 0; if (decoded_len < 0 || eof > decoded_len) { rv = -1; @@ -362,13 +410,20 @@ end: return rv; } -int EVP_DecodeBlock(unsigned char *t, const unsigned char *f, int n) +static int evp_decodeblock_int(EVP_ENCODE_CTX *ctx, unsigned char *t, + const unsigned char *f, int n) { int i, ret = 0, a, b, c, d; unsigned long l; + const unsigned char *table; + + if (ctx != NULL && (ctx->flags & EVP_ENCODE_CTX_USE_SRP_ALPHABET) != 0) + table = srpdata_ascii2bin; + else + table = data_ascii2bin; /* trim white space from the start of the line. */ - while ((conv_ascii2bin(*f) == B64_WS) && (n > 0)) { + while ((conv_ascii2bin(*f, table) == B64_WS) && (n > 0)) { f++; n--; } @@ -377,17 +432,17 @@ int EVP_DecodeBlock(unsigned char *t, const unsigned char *f, int n) * strip off stuff at the end of the line ascii2bin values B64_WS, * B64_EOLN, B64_EOLN and B64_EOF */ - while ((n > 3) && (B64_NOT_BASE64(conv_ascii2bin(f[n - 1])))) + while ((n > 3) && (B64_NOT_BASE64(conv_ascii2bin(f[n - 1], table)))) n--; if (n % 4 != 0) return -1; for (i = 0; i < n; i += 4) { - a = conv_ascii2bin(*(f++)); - b = conv_ascii2bin(*(f++)); - c = conv_ascii2bin(*(f++)); - d = conv_ascii2bin(*(f++)); + a = conv_ascii2bin(*(f++), table); + b = conv_ascii2bin(*(f++), table); + c = conv_ascii2bin(*(f++), table); + d = conv_ascii2bin(*(f++), table); if ((a & 0x80) || (b & 0x80) || (c & 0x80) || (d & 0x80)) return -1; l = ((((unsigned long)a) << 18L) | @@ -401,13 +456,18 @@ int EVP_DecodeBlock(unsigned char *t, const unsigned char *f, int n) return ret; } +int EVP_DecodeBlock(unsigned char *t, const unsigned char *f, int n) +{ + return evp_decodeblock_int(NULL, t, f, n); +} + int EVP_DecodeFinal(EVP_ENCODE_CTX *ctx, unsigned char *out, int *outl) { int i; *outl = 0; if (ctx->num != 0) { - i = EVP_DecodeBlock(out, ctx->enc_data, ctx->num); + i = evp_decodeblock_int(ctx, out, ctx->enc_data, ctx->num); if (i < 0) return -1; ctx->num = 0; diff --git a/crypto/include/internal/evp_int.h b/crypto/include/internal/evp_int.h index a766a2c994..691b28b3b4 100644 --- a/crypto/include/internal/evp_int.h +++ b/crypto/include/internal/evp_int.h @@ -427,4 +427,7 @@ void evp_app_cleanup_int(void); void evp_encode_ctx_set_flags(EVP_ENCODE_CTX *ctx, unsigned int flags); /* EVP_ENCODE_CTX flags */ -#define EVP_ENCODE_CTX_NO_NEWLINES 1 +/* Don't generate new lines when encoding */ +#define EVP_ENCODE_CTX_NO_NEWLINES 1 +/* Use the SRP base64 alphabet instead of the standard one */ +#define EVP_ENCODE_CTX_USE_SRP_ALPHABET 2 diff --git a/crypto/srp/srp_vfy.c b/crypto/srp/srp_vfy.c index 1eba5a7cb1..1bf2f267ed 100644 --- a/crypto/srp/srp_vfy.c +++ b/crypto/srp/srp_vfy.c @@ -25,6 +25,15 @@ # define SRP_RANDOM_SALT_LEN 20 # define MAX_LEN 2500 +/* + * Note that SRP uses its own variant of base 64 encoding. A different base64 + * alphabet is used and no padding '=' characters are added. Instead we pad to + * the front with 0 bytes and subsequently strip off leading encoded padding. + * This variant is used for compatibility with other SRP implementations - + * notably libsrp, but also others. It is also required for backwards + * compatibility in order to load verifier files from other OpenSSL versions. + */ + /* * Convert a base64 string into raw byte array representation. * Returns the length of the decoded data, or -1 on error. @@ -33,24 +42,79 @@ static int t_fromb64(unsigned char *a, size_t alen, const char *src) { EVP_ENCODE_CTX *ctx; int outl = 0, outl2 = 0; - size_t size = strlen(src); + size_t size, padsize; + const unsigned char *pad = (const unsigned char *)"00"; - if (size > INT_MAX) + while (*src == ' ' || *src == '\t' || *src == '\n') + ++src; + size = strlen(src); + padsize = 4 - (size & 3); + padsize &= 3; + + /* Four bytes in src become three bytes output. */ + if (size > INT_MAX || ((size + padsize) / 4) * 3 > alen) return -1; ctx = EVP_ENCODE_CTX_new(); if (ctx == NULL) return -1; + /* + * This should never occur because 1 byte of data always requires 2 bytes of + * encoding, i.e. + * 0 bytes unencoded = 0 bytes encoded + * 1 byte unencoded = 2 bytes encoded + * 2 bytes unencoded = 3 bytes encoded + * 3 bytes unencoded = 4 bytes encoded + * 4 bytes unencoded = 6 bytes encoded + * etc + */ + if (padsize == 3) + return -1; + + /* Valid padsize values are now 0, 1 or 2 */ + EVP_DecodeInit(ctx); - if (EVP_DecodeUpdate(ctx, a, &outl, (const unsigned char *)src, size) < 0) { + evp_encode_ctx_set_flags(ctx, EVP_ENCODE_CTX_USE_SRP_ALPHABET); + + /* Add any encoded padding that is required */ + if (padsize != 0 + && EVP_DecodeUpdate(ctx, a, &outl, pad, padsize) < 0) { EVP_ENCODE_CTX_free(ctx); return -1; } + if (EVP_DecodeUpdate(ctx, a, &outl2, (const unsigned char *)src, size) < 0) { + EVP_ENCODE_CTX_free(ctx); + return -1; + } + outl += outl2; EVP_DecodeFinal(ctx, a + outl, &outl2); + outl += outl2; + + /* Strip off the leading padding */ + if (padsize != 0) { + if ((int)padsize >= outl) + return -1; + /* + * If we added 1 byte of padding prior to encoding then we have 2 bytes + * of "real" data which gets spread across 4 encoded bytes like this: + * (6 bits pad)(2 bits pad | 4 bits data)(6 bits data)(6 bits data) + * So 1 byte of pre-encoding padding results in 1 full byte of encoded + * padding. + * If we added 2 bytes of padding prior to encoding this gets encoded + * as: + * (6 bits pad)(6 bits pad)(4 bits pad | 2 bits data)(6 bits data) + * So 2 bytes of pre-encoding padding results in 2 full bytes of encoded + * padding, i.e. we have to strip the same number of bytes of padding + * from the encoded data as we added to the pre-encoded data. + */ + memmove(a, a + padsize, outl - padsize); + outl -= padsize; + } EVP_ENCODE_CTX_free(ctx); - return outl + outl2; + + return outl; } /* @@ -61,18 +125,43 @@ static int t_tob64(char *dst, const unsigned char *src, int size) { EVP_ENCODE_CTX *ctx = EVP_ENCODE_CTX_new(); int outl = 0, outl2 = 0; + unsigned char pad[2] = {0, 0}; + size_t leadz = 0; if (ctx == NULL) return 0; EVP_EncodeInit(ctx); - evp_encode_ctx_set_flags(ctx, EVP_ENCODE_CTX_NO_NEWLINES); + evp_encode_ctx_set_flags(ctx, EVP_ENCODE_CTX_NO_NEWLINES + | EVP_ENCODE_CTX_USE_SRP_ALPHABET); - if (!EVP_EncodeUpdate(ctx, (unsigned char *)dst, &outl, src, size)) { + /* + * We pad at the front with zero bytes until the length is a multiple of 3 + * so that EVP_EncodeUpdate/EVP_EncodeFinal does not add any of its own "=" + * padding + */ + leadz = 3 - (size % 3); + if (leadz != 3 + && !EVP_EncodeUpdate(ctx, (unsigned char *)dst, &outl, pad, + leadz)) { EVP_ENCODE_CTX_free(ctx); return 0; } + + if (!EVP_EncodeUpdate(ctx, (unsigned char *)dst + outl, &outl2, src, + size)) { + EVP_ENCODE_CTX_free(ctx); + return 0; + } + outl += outl2; EVP_EncodeFinal(ctx, (unsigned char *)dst + outl, &outl2); + outl += outl2; + + /* Strip the encoded padding at the front */ + if (leadz != 3) { + memmove(dst, dst + leadz, outl - leadz); + dst[outl - leadz] = '\0'; + } EVP_ENCODE_CTX_free(ctx); return 1; -- 2.25.1