Skip to content
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
nopad in the unpadded case
  • Loading branch information
emilypi committed May 29, 2020
commit d8dc4c36de4fbf15281ac189e9f14c249098bb99
2 changes: 1 addition & 1 deletion Data/ByteString/Base64/Internal.hs
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ decodeWithTable padding decodeFP bs =
Unpadded
| r == 0 -> validateLastPad bs noPad $ go bs
| r == 2 -> validateLastPad bs noPad $ go (B.append bs (B.replicate 2 0x3d))
| r == 3 -> validateLastPad bs invalidPad $ go (B.append bs (B.replicate 1 0x3d))
| r == 3 -> validateLastPad bs noPad $ go (B.append bs (B.replicate 1 0x3d))
Copy link
Member Author

@emilypi emilypi May 29, 2020

Choose a reason for hiding this comment

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

It's all written down in the $Validation note, but just to recap:

Let bs be a bytestring of length l. Then the following properties hold:

  • l == 0 mod 4: The input bytestring is assumed to be well-formed. This will always be the expected case for padded Base64 and Base64url values, or for unpadded Base64url values that happen to have a pre-encoded length multiple of 6. In any case, these will go through the standard decode routine, and any existing padding chars will be validated in the final quanta (see: finalChunk).

  • l == 1 mod 4: This is never a valid length for Base64 or Base64url-encoded values. The specification requires that the unpadded length of the encoded string be l == 0 mod 4, l == 2 mod 4, or l == 3 mod 4. There will never be a valid unpadded input of length l == 1 mod 4 as a result. This can be rejected outright.

  • l == 2 mod 4: In this case, two padding chars must appear in the final quanta. If any additional padding chars exist in the string, then they will fail as final quanta, as we require the final four bytes (say, (a b '=' '=')) to have that a /= '=' and b /= '='. Additional pads will fail that clause of finalChunk. Thus, it's safe to add 2 padding chars to the end of a supposedly unpadded input of length l == 2 mod 4, since the addition will never form a well-formed input if the unpadded string is already malformed.

  • l == 3 mod 4: This is the only tricky case. When inputs have this length, then we expect that that adding padding chars will result in the form (a b c '='). However, if the unpadded input has '=' in the c position, it is possible that adding padding chars to the string "completes" the input in the sense that it forms a valid input where the unpadded fragment can be seen as a bytestring of length l == 2 mod 4. This could potentially be an attack vector, and constitutes a security risk. Thankfully, this is also easy to check, since, we only need to validate that the last char of an unpadded bytestring of length l == 3 mod 4 is not '='. If any additional padding chars are present, then there is no risk that they will contribute to a well-formed input, since they will fail as final quanta in the a and b positions. So really, the requirement with padding bytestrings of length l == 3 mod 4 is that they are of the form (a b c '='), c /= '=' after padding.

| otherwise -> Left "Base64-encoded bytestring has invalid size"
where
(!q, !r) = (B.length bs) `divMod` 4
Expand Down