Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix SRP #5925

Closed
wants to merge 9 commits into from
Closed

Fix SRP #5925

wants to merge 9 commits into from

Conversation

mattcaswell
Copy link
Member

Historically we used to implement standalone base64 code for SRP. This
was replaced by commit 3d3f21a with the standard base64 processing code.

SRP has been broken since that time. The immediate issue is that the code was changed to use the low level EVP_EncodeBlock() and EVP_DecodeBlock() functions. Since these are low level functions they do not handle padding. This is done in the higher level EVP_(Encode|Decode)(Update|Final)() functions. This means that when decoding data the binary value does not match the originally encoded value causing SRP connection failures.

This problem caused me to take a deeper look at the SRP base64 code and revealed some more fundamental problems. 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.

This PR expands the standard base64 processing code with the capability to be able to read and generate the SRP base64 variant.

The fact that this problem went undetected for so long is indicative of poor (i.e. non-existent) tests in this area. Therefore I have also added some SRP tests which would have caught these issues.

  • tests are added or updated

Previously they were using EVP_EncodeBlock/EVP_DecodeBlock. These are low
level functions that do not handle padding characters. This was causing
the SRP code to fail. One side effect of using EVP_EncodeUpdate is that
it inserts newlines which is not what we need in SRP so we add a flag to
avoid that.
Historically we used to implement standalone base64 code for SRP. This
was replaced by commit 3d3f21a 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.
@kroeckx
Copy link
Member

kroeckx commented Apr 10, 2018

There are at least warnings that you want to fix.

@mattcaswell
Copy link
Member Author

Fixup commit pushed to address the warning picked up by Travis. The Appveyor failure appear unrelated.

@dot-asm
Copy link
Contributor

dot-asm commented Apr 11, 2018

Appveyor failure appear unrelated.

It's being addressed in #5907.

EVP_ENCODE_CTX *ctx;
int outl = 0, outl2 = 0;
size_t size, padsize;
unsigned char *pad = (unsigned char *)"00";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's possible and it would be only appropriate to declare this const. After all, literals can and customarily do reside in read-only segment.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

size_t size, padsize;
unsigned char *pad = (unsigned char *)"00";

while (*src != '\0' && (*src == ' ' || *src == '\t' || *src == '\n'))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On side note, formally speaking non-zero check is redundant. This means that some compilers might issue warning. Not that we actually honour such warnings, as we do consciously choose to have constant conditions at occasions...

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I removed the zero check.

size = strlen(src);
padsize = 4 - (size & 3);
if (padsize == 4)
padsize = 0;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not just reapply mask, i.e. padsize &= 3? [One can as well (0 - size) & 3, but that would be less readable.]

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done


/* Four bytes in src become three bytes output. */
if (size > INT_MAX || (size / 4) * 3 > alen)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So alen is not longer used? Then why pass it at all? All occurrences are sizeof(something_on_stack), 2500, pretty high, but is there guarantee that it won't overflow?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmmm...I think this removing this was over-zealous. I have put this test back.

if ((int)padsize >= outl)
return -1;
memmove(a, a + padsize, outl - padsize);
outl -= padsize;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is utterly counter-intuitive. I mean the fact that padsize comes out as same value in encoded and decoded. Maybe a commentary would be appropriate...

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added a comment to explain this.

evp_encode_ctx_set_flags(ctx, EVP_ENCODE_CTX_NO_NEWLINES
| EVP_ENCODE_CTX_USE_SRP_ALPHABET);

/* We pad at the front with zero bytes as required */
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd appreciate more explicit note here. I mean instead of "as required" something like "till length is divisible by 3 so that encoded string needs no ="...

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I expanded out the comment.

void evp_encode_ctx_set_flags(EVP_ENCODE_CTX *ctx, uint64_t flags)
{
ctx->flags = flags;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not impossible to imagine that this might go public at some point, and in such case I'd argue in favour of using types that don't require additional headers such as unsigned int. In other words I'd like to suggest to consider declaring flags as unsigned int, here and in structure...

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed to unsigned int.

void EVP_DecodeInit(EVP_ENCODE_CTX *ctx)
{
/* Only ctx->num is used during decoding. */
ctx->num = 0;
ctx->length = 0;
ctx->line_num = 0;
ctx->expect_nl = 0;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So flags don't need initialization? If so how come? I mean if you rely on structure being memset to 0, then you wouldn't need any of initializations. But if you zero them explicitly, then flags deserve it as much, don't they?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The structure is zalloc'd so none of these need initialisation. I removed them all.

@mattcaswell
Copy link
Member Author

Fix up commits pushed addressing the feedback so far.

EVP_ENCODE_CTX *ctx;
int outl = 0, outl2 = 0;
size_t size, padsize;
const unsigned char *pad = (unsigned char *)"00";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor nit, (const unsigned char *)"00".

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

++src;
size = strlen(src);
padsize = 4 - (size & 3);
padsize &= 3;

/* Four bytes in src become three bytes output. */
if (size > INT_MAX || (size / 4) * 3 > alen)
Copy link
Contributor

@dot-asm dot-asm Apr 11, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmmm, one can actually wonder if (size/4)*3 is actually accurate. After all, we'll pre-pad the string, so we do need space for ((size + padsize) / 4) * 3.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

ctx->num = 0;
ctx->length = 0;
ctx->line_num = 0;
ctx->expect_nl = 0;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While you are at it, would you leave a commentary that everything is zalloc-ed [so that no member zeroing is required]?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, I changed my mind and put it back to zeroing everything explicitly and added a line to additionally zero the flags. The reason is that it occurred to me that some applications might be reusing an EVP_ENCODE_CTX. This would have worked before but not after I removed the explicit zeroing.

@mattcaswell
Copy link
Member Author

New fixup commits pushed addressing latest feedback.

@dot-asm
Copy link
Contributor

dot-asm commented Apr 12, 2018

osx build complains about memory leak...

@mattcaswell
Copy link
Member Author

I can't recreate it locally but I think I've figured out the problem. Fixup commit pushed. Let's see if the CIs think I've fixed it too.

@mattcaswell
Copy link
Member Author

Travis is now passing.

@mattcaswell
Copy link
Member Author

Pushed. Thanks.

levitte pushed a commit that referenced this pull request Apr 13, 2018
Previously they were using EVP_EncodeBlock/EVP_DecodeBlock. These are low
level functions that do not handle padding characters. This was causing
the SRP code to fail. One side effect of using EVP_EncodeUpdate is that
it inserts newlines which is not what we need in SRP so we add a flag to
avoid that.

Reviewed-by: Andy Polyakov <appro@openssl.org>
(Merged from #5925)
levitte pushed a commit that referenced this pull request Apr 13, 2018
Historically we used to implement standalone base64 code for SRP. This
was replaced by commit 3d3f21a 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 <appro@openssl.org>
(Merged from #5925)
levitte pushed a commit that referenced this pull request Apr 13, 2018
Reviewed-by: Andy Polyakov <appro@openssl.org>
(Merged from #5925)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants