Repository navigation
Conversation
`ASN1UTF8String` accepted any bytes, so `0C 02 C3 28` decoded even though `C3` must be followed by one continuation byte and `28` is not one. X.690 §8.23.10 defines the contents as the UTF-8 encoding of an ISO/IEC 10646 string, so those octets are not a value of the type. `ASN1PrintableString`, `ASN1VisibleString` and `ASN1IA5String` already validate from their decoding initializers; this brings UTF8String in line with them. `init(contentBytes:)` and `bytes` are left alone: validating there would change a signature or trap in existing code, which is a separate decision from what decoding accepts.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Validate UTF8String contents when decoding
Motivation
ASN1UTF8Stringaccepts any bytes. Decoding0C 02 C3 28succeeds, eventhough
C3must be followed by one continuation byte and28is not one — sothe resulting
bytesare not UTF-8.X.690 §8.23.10 defines a UTF8String's contents as the UTF-8 encoding of an
ISO/IEC 10646 string, so those octets are not a value of the type.
This is also inconsistent within the module.
ASN1PrintableString,ASN1VisibleStringandASN1IA5Stringeach callSelf._isValid(self.bytes)from their decoding initializers and throw
ASN1Error.invalidStringRepresentationwhen it fails.ASN1UTF8Stringhas no_isValidand assignsASN1OctetString(derEncoded:).bytesdirectly.There is a second-order effect:
String(_ utf8String:)builds itsStringwith
String(decoding:as:), which substitutes U+FFFD for invalid sequences. Acaller who goes through
Stringtherefore gets characters that were never inthe input, with nothing having reported a problem.
What this changes
ASN1UTF8String.init(derEncoded:withIdentifier:)andinit(berEncoded:withIdentifier:)validate the contents and throwASN1Error.invalidStringRepresentationwhen they are not UTF-8, matching thethree types that already do this.
_isValidis added in the same@inlinable static funcshape, decoding throughUTF8()so it stays free ofFoundation.
Compatibility
This does not change any API signature — both initializers already
throws—but it intentionally changes decoding behaviour for input that was previously
accepted.
Out of scope here, so the change stays limited to what decoding accepts:
init(contentBytes:)is non-throwing, so validating there would change itssignature. (
ASN1PrintableString.init(contentBytes:)doesthrows, so thereis a precedent for aligning them, but that seems like a separate decision.)
bytesis a publicvar; adidSetprecondition would keep the signaturebut could trap in existing code, which is a different trade-off.
Tests
Rejected: an invalid continuation sequence, an overlong encoding, an encoded
surrogate, a scalar above U+10FFFF, a truncated sequence, and a lone
continuation byte.
Accepted: empty, ASCII, and two-, three- and four-byte scalars, so the change
does not narrow what a valid UTF8String may hold.