Skip to content

Commit cbf8007

Browse files
authored
Merge pull request #5710 from randombit/jack/asn1-hardening
ASN.1 hardenings and improved decoder strictness
2 parents 6372c0c + 5b97122 commit cbf8007

13 files changed

Lines changed: 539 additions & 105 deletions

File tree

src/lib/asn1/asn1_print.cpp

Lines changed: 33 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -21,28 +21,10 @@ namespace Botan {
2121

2222
namespace {
2323

24-
// Printable here means fits into an ASN.1 "PRINTABLE STRING" type
25-
bool is_printable_char(char c) {
26-
if(c >= 'a' && c <= 'z') {
27-
return true;
28-
}
29-
30-
if(c >= 'A' && c <= 'Z') {
31-
return true;
32-
}
33-
34-
if(c >= '0' && c <= '9') {
35-
return true;
36-
}
37-
38-
if(c == '.' || c == ':' || c == '/' || c == '-') {
39-
return true;
40-
}
41-
42-
return false;
43-
}
44-
4524
bool all_printable_chars(const uint8_t bits[], size_t bits_len) {
25+
// Printable here means fits into an ASN.1 "PRINTABLE STRING" type
26+
constexpr auto is_printable_char = CharacterValidityTable::alpha_numeric_plus(".:/-");
27+
4628
for(size_t i = 0; i != bits_len; ++i) {
4729
if(!is_printable_char(bits[i])) {
4830
return false;
@@ -83,7 +65,10 @@ std::string ASN1_Formatter::print(const uint8_t in[], size_t len) const {
8365
}
8466

8567
void ASN1_Formatter::print_to_stream(std::ostream& output, const uint8_t in[], size_t len) const {
86-
const auto decoder_limits = m_require_der ? BER_Decoder::Limits::DER() : BER_Decoder::Limits::BER();
68+
// The pretty printer is a best-effort diagnostic tool, so in BER mode it
69+
// tolerates standalone EOC markers emitted by some BER producers.
70+
const auto decoder_limits =
71+
m_require_der ? BER_Decoder::Limits::DER() : BER_Decoder::Limits::BER().with_standalone_eoc_allowed();
8772
BER_Decoder dec(std::span<const uint8_t>{in, len}, decoder_limits);
8873
decode(output, dec, 0);
8974
}
@@ -98,29 +83,37 @@ void ASN1_Formatter::decode(std::ostream& output, BER_Decoder& decoder, size_t l
9883
const ASN1_Class class_tag = obj.get_class();
9984
const size_t length = obj.length();
10085

101-
/* hack to insert the tag+length back in front of the stuff now
102-
that we've gotten the type info */
103-
std::vector<uint8_t> bits;
104-
DER_Encoder(bits).add_object(type_tag, class_tag, obj.bits(), obj.length());
105-
106-
BER_Decoder data(bits, decoder.limits());
107-
10886
if(intersects(class_tag, ASN1_Class::Constructed)) {
109-
BER_Decoder cons_info(obj, decoder.limits());
110-
11187
if(recurse_deeper) {
11288
output << format(type_tag, class_tag, level, length, "");
89+
// Move (not copy) the content into the sub-decoder; copying at every
90+
// nesting level lets deeply nested input exhaust memory.
91+
BER_Decoder cons_info(std::move(obj), decoder.limits());
11392
decode(output, cons_info, level + 1); // recurse
11493
} else {
94+
std::vector<uint8_t> bits;
95+
DER_Encoder(bits).add_object(type_tag, class_tag, obj.bits(), obj.length());
11596
output << format(type_tag, class_tag, level, length, format_bin(type_tag, class_tag, bits));
11697
}
117-
} else if(intersects(class_tag, ASN1_Class::Application) || intersects(class_tag, ASN1_Class::ContextSpecific)) {
98+
99+
obj = decoder.get_next_object();
100+
continue;
101+
}
102+
103+
/* hack to insert the tag+length back in front of the stuff now
104+
that we've gotten the type info */
105+
std::vector<uint8_t> bits;
106+
DER_Encoder(bits).add_object(type_tag, class_tag, obj.bits(), obj.length());
107+
108+
BER_Decoder data(bits, decoder.limits());
109+
110+
if(intersects(class_tag, ASN1_Class::Application) || intersects(class_tag, ASN1_Class::ContextSpecific)) {
118111
bool success_parsing_cs = false;
119112

120113
if(m_print_context_specific) {
121114
try {
122115
if(possibly_a_general_name(bits.data(), bits.size())) {
123-
output << format(type_tag, class_tag, level, level, bytes_to_string(std::span{bits}.subspan(2)));
116+
output << format(type_tag, class_tag, level, length, bytes_to_string(std::span{bits}.subspan(2)));
124117
success_parsing_cs = true;
125118
} else if(recurse_deeper) {
126119
std::vector<uint8_t> inner_bits;
@@ -269,6 +262,13 @@ std::string ASN1_Pretty_Printer::format(
269262
std::string ASN1_Pretty_Printer::format_bin(ASN1_Type /*type_tag*/,
270263
ASN1_Class /*class_tag*/,
271264
const std::vector<uint8_t>& vec) const {
265+
// A value larger than the binary print limit is suppressed by format(), so
266+
// skip the (potentially large) string/hex conversion entirely. vec.size() is
267+
// a lower bound on the formatted length, so such a value is certainly dropped.
268+
if(vec.size() > m_print_binary_limit) {
269+
return "";
270+
}
271+
272272
if(all_printable_chars(vec.data(), vec.size())) {
273273
return bytes_to_string(vec);
274274
} else {

src/lib/asn1/ber_dec.cpp

Lines changed: 112 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#include <botan/internal/asn1_utils.h>
1313
#include <botan/internal/int_utils.h>
1414
#include <botan/internal/loadstor.h>
15+
#include <algorithm>
1516
#include <memory>
1617

1718
namespace Botan {
@@ -37,6 +38,12 @@ size_t decode_tag(DataSource* ber, ASN1_Type& type_tag, ASN1_Class& class_tag) {
3738
if((*b & 0x1F) != 0x1F) {
3839
type_tag = ASN1_Type(*b & 0x1F);
3940
class_tag = ASN1_Class(*b & 0xE0);
41+
// The EOC marker is primitive; a constructed universal tag 0 has no
42+
// valid meaning and would otherwise bypass the EOC handling, which
43+
// matches on (Eoc, Universal) exactly
44+
if(type_tag == ASN1_Type::Eoc && class_tag == ASN1_Class::Constructed) {
45+
throw BER_Decoding_Error("EOC tag with constructed encoding");
46+
}
4047
return 1;
4148
}
4249

@@ -49,7 +56,8 @@ size_t decode_tag(DataSource* ber, ASN1_Type& type_tag, ASN1_Class& class_tag) {
4956
if(!b) {
5057
throw BER_Decoding_Error("Long-form tag truncated");
5158
}
52-
if((tag_buf >> 24) != 0) {
59+
// Reject if shifting in another 7 bits would overflow the uint32_t tag
60+
if((tag_buf >> 25) != 0) {
5361
throw BER_Decoding_Error("Long-form tag overflowed 32 bits");
5462
}
5563
// This is required even by BER (see X.690 section 8.1.2.4.2 sentence c).
@@ -197,6 +205,12 @@ size_t peek_tag(DataSource* src, size_t offset, ASN1_Type& type_tag, ASN1_Class&
197205
if((b & 0x1F) != 0x1F) {
198206
type_tag = ASN1_Type(b & 0x1F);
199207
class_tag = ASN1_Class(b & 0xE0);
208+
// The EOC marker is primitive; a constructed universal tag 0 has no
209+
// valid meaning and would otherwise bypass the EOC handling, which
210+
// matches on (Eoc, Universal) exactly
211+
if(type_tag == ASN1_Type::Eoc && class_tag == ASN1_Class::Constructed) {
212+
throw BER_Decoding_Error("EOC tag with constructed encoding");
213+
}
200214
return 1;
201215
}
202216

@@ -208,7 +222,8 @@ size_t peek_tag(DataSource* src, size_t offset, ASN1_Type& type_tag, ASN1_Class&
208222
if(src->peek(&b, 1, offset + tag_bytes) == 0) {
209223
throw BER_Decoding_Error("Long-form tag truncated");
210224
}
211-
if((tag_buf >> 24) != 0) {
225+
// Reject if shifting in another 7 bits would overflow the uint32_t tag
226+
if((tag_buf >> 25) != 0) {
212227
throw BER_Decoding_Error("Long-form tag overflowed 32 bits");
213228
}
214229
// Required even by BER (X.690 section 8.1.2.4.2 sentence c).
@@ -246,7 +261,8 @@ size_t peek_tag(DataSource* src, size_t offset, ASN1_Type& type_tag, ASN1_Class&
246261
* Returns the decoded length and sets field_size to the number of bytes consumed.
247262
* For indefinite-length encoding, recursively scans ahead to find the EOC marker.
248263
*/
249-
size_t peek_length(DataSource* src, size_t offset, size_t& field_size, size_t allow_indef, bool constructed) {
264+
size_t peek_length(
265+
DataSource* src, size_t offset, size_t& field_size, size_t allow_indef, bool constructed, bool der_mode) {
250266
uint8_t b = 0;
251267
if(src->peek(&b, 1, offset) == 0) {
252268
throw BER_Decoding_Error("Length field not found");
@@ -264,6 +280,10 @@ size_t peek_length(DataSource* src, size_t offset, size_t& field_size, size_t al
264280
}
265281

266282
if(num_length_bytes == 0) {
283+
// Indefinite length is not allowed in DER
284+
if(der_mode) {
285+
throw BER_Decoding_Error("Detected indefinite-length encoding in DER structure");
286+
}
267287
// Indefinite length is only valid for constructed types (X.690 8.1.3.2)
268288
if(!constructed) {
269289
throw BER_Decoding_Error("Indefinite-length encoding used with non-constructed type");
@@ -303,7 +323,8 @@ size_t find_eoc(DataSource* src, size_t base_offset, size_t allow_indef) {
303323
}
304324

305325
size_t length_size = 0;
306-
const size_t item_size = peek_length(src, offset + tag_size, length_size, allow_indef, is_constructed(class_tag));
326+
const size_t item_size =
327+
peek_length(src, offset + tag_size, length_size, allow_indef, is_constructed(class_tag), false);
307328

308329
if(auto new_offset = checked_add(offset, tag_size, length_size, item_size)) {
309330
offset = new_offset.value();
@@ -397,6 +418,39 @@ class DataSource_Span final : public DataSource {
397418
size_t m_offset = 0;
398419
};
399420

421+
/*
422+
* Verify that the elements of a SET appear in sorted order, comparing the full
423+
* encoding of each element as an octet string. DER requires this canonical
424+
* ordering. Throws if the elements are not sorted.
425+
*/
426+
void verify_set_is_sorted(std::span<const uint8_t> content) {
427+
DataSource_Span src(content);
428+
size_t offset = 0;
429+
std::optional<std::span<const uint8_t>> prev;
430+
431+
while(offset < content.size()) {
432+
ASN1_Type type_tag = ASN1_Type::NoObject;
433+
ASN1_Class class_tag = ASN1_Class::NoObject;
434+
const size_t tag_size = peek_tag(&src, offset, type_tag, class_tag);
435+
436+
size_t length_size = 0;
437+
const size_t item_size =
438+
peek_length(&src, offset + tag_size, length_size, /*allow_indef=*/0, is_constructed(class_tag), true);
439+
440+
const auto end = checked_add(offset, tag_size, length_size, item_size);
441+
if(!end || *end > content.size()) {
442+
throw BER_Decoding_Error("SET element exceeds available data");
443+
}
444+
445+
const auto elem = content.subspan(offset, *end - offset);
446+
if(prev && std::lexicographical_compare(elem.begin(), elem.end(), prev->begin(), prev->end())) {
447+
throw BER_Decoding_Error("Detected unsorted SET in DER structure");
448+
}
449+
prev = elem;
450+
offset = *end;
451+
}
452+
}
453+
400454
} // namespace
401455

402456
BER_Decoder::~BER_Decoder() = default;
@@ -488,6 +542,10 @@ BER_Object BER_Decoder::get_next_object() {
488542
throw BER_Decoding_Error("EOC marker with non-zero length");
489543
}
490544

545+
if(const auto max_size = m_limits.max_object_size(); max_size && dl.content_length() > *max_size) {
546+
throw BER_Decoding_Error("Encoded object exceeds maximum size");
547+
}
548+
491549
if(!m_source->check_available(dl.total_length())) {
492550
throw BER_Decoding_Error("Value truncated");
493551
}
@@ -509,10 +567,16 @@ BER_Object BER_Decoder::get_next_object() {
509567
if(m_limits.require_der_encoding()) {
510568
throw BER_Decoding_Error("Detected EOC marker in DER structure");
511569
}
512-
continue;
513-
} else {
514-
break;
570+
// An EOC marker is only valid as an indefinite-length terminator, which
571+
// is consumed above when reading the indefinite-length object. A
572+
// standalone EOC is rejected unless the caller opted to tolerate it.
573+
if(m_limits.allow_standalone_eoc()) {
574+
continue;
575+
}
576+
throw BER_Decoding_Error("Encountered EOC marker outside of indefinite-length encoding");
515577
}
578+
579+
break;
516580
}
517581

518582
return next;
@@ -550,6 +614,12 @@ void BER_Decoder::push_back(BER_Object&& obj) {
550614
BER_Decoder BER_Decoder::start_cons(ASN1_Type type_tag, ASN1_Class class_tag) {
551615
BER_Object obj = get_next_object();
552616
obj.assert_is_a(type_tag, class_tag | ASN1_Class::Constructed);
617+
618+
// In DER mode the elements of a universal SET must appear in sorted order
619+
if(m_limits.require_der_encoding() && type_tag == ASN1_Type::Set && class_tag == ASN1_Class::Universal) {
620+
verify_set_is_sorted(std::span<const uint8_t>{obj.bits(), obj.length()});
621+
}
622+
553623
BER_Decoder child(std::move(obj), this);
554624
return child;
555625
}
@@ -573,6 +643,11 @@ BER_Decoder::BER_Decoder(BER_Object&& obj, BER_Decoder* parent) :
573643
m_source = m_data_src.get();
574644
}
575645

646+
BER_Decoder::BER_Decoder(BER_Object&& obj, Limits limits) : m_limits(limits) {
647+
m_data_src = std::make_unique<DataSource_BERObject>(std::move(obj));
648+
m_source = m_data_src.get();
649+
}
650+
576651
/*
577652
* BER_Decoder Constructor
578653
*/
@@ -697,11 +772,13 @@ BER_Decoder& BER_Decoder::decode(BigInt& out, ASN1_Type type_tag, ASN1_Class cla
697772
const BER_Object obj = get_next_object();
698773
obj.assert_is_a(type_tag, class_tag);
699774

775+
// An INTEGER must have at least one content octet (X.690 section 8.3.1)
776+
if(obj.length() == 0) {
777+
throw BER_Decoding_Error("INTEGER encoding has no content octets");
778+
}
779+
700780
// DER requires minimal INTEGER encoding (X.690 section 8.3.2)
701781
if(m_limits.require_der_encoding()) {
702-
if(obj.length() == 0) {
703-
throw BER_Decoding_Error("Detected empty INTEGER encoding in DER structure");
704-
}
705782
if(obj.length() > 1) {
706783
if(obj.bits()[0] == 0x00 && (obj.bits()[1] & 0x80) == 0) {
707784
throw BER_Decoding_Error("Detected non-minimal INTEGER encoding in DER structure");
@@ -712,29 +789,25 @@ BER_Decoder& BER_Decoder::decode(BigInt& out, ASN1_Type type_tag, ASN1_Class cla
712789
}
713790
}
714791

715-
if(obj.length() == 0) {
716-
out.clear();
717-
} else {
718-
const uint8_t first = obj.bits()[0];
719-
const bool negative = (first & 0x80) == 0x80;
720-
721-
if(negative) {
722-
secure_vector<uint8_t> vec(obj.bits(), obj.bits() + obj.length());
723-
for(size_t i = obj.length(); i > 0; --i) {
724-
const bool gt0 = (vec[i - 1] > 0);
725-
vec[i - 1] -= 1;
726-
if(gt0) {
727-
break;
728-
}
729-
}
730-
for(size_t i = 0; i != obj.length(); ++i) {
731-
vec[i] = ~vec[i];
792+
const uint8_t first = obj.bits()[0];
793+
const bool negative = (first & 0x80) == 0x80;
794+
795+
if(negative) {
796+
secure_vector<uint8_t> vec(obj.bits(), obj.bits() + obj.length());
797+
for(size_t i = obj.length(); i > 0; --i) {
798+
const bool gt0 = (vec[i - 1] > 0);
799+
vec[i - 1] -= 1;
800+
if(gt0) {
801+
break;
732802
}
733-
out._assign_from_bytes(vec);
734-
out.flip_sign();
735-
} else {
736-
out._assign_from_bytes(obj.data());
737803
}
804+
for(size_t i = 0; i != obj.length(); ++i) {
805+
vec[i] = ~vec[i];
806+
}
807+
out._assign_from_bytes(vec);
808+
out.flip_sign();
809+
} else {
810+
out._assign_from_bytes(obj.data());
738811
}
739812

740813
return (*this);
@@ -755,9 +828,10 @@ void asn1_decode_binary_string(std::vector<uint8_t, Alloc>& buffer,
755828
bool require_der) {
756829
obj.assert_is_a(type_tag, class_tag);
757830

758-
// DER requires BIT STRING and OCTET STRING to use primitive encoding
759-
if(require_der && is_constructed(obj)) {
760-
throw BER_Decoding_Error("Detected constructed string encoding in DER structure");
831+
// Only primitive OCTET STRING/BIT STRING are supported; constructed
832+
// (fragmented) BER forms are rejected rather than concatenated.
833+
if(is_constructed(obj)) {
834+
throw BER_Decoding_Error("Constructed OCTET STRING/BIT STRING is not supported");
761835
}
762836

763837
if(real_type == ASN1_Type::OctetString) {
@@ -797,8 +871,10 @@ void asn1_decode_binary_string(std::vector<uint8_t, Alloc>& buffer,
797871
uint8_t asn1_bitstring_unused_bits(const BER_Object& obj, ASN1_Type type_tag, ASN1_Class class_tag, bool require_der) {
798872
obj.assert_is_a(type_tag, class_tag);
799873

800-
if(require_der && is_constructed(obj)) {
801-
throw BER_Decoding_Error("Detected constructed string encoding in DER structure");
874+
// Only primitive BIT STRING is supported; constructed (fragmented) BER
875+
// forms are rejected rather than concatenated.
876+
if(is_constructed(obj)) {
877+
throw BER_Decoding_Error("Constructed OCTET STRING/BIT STRING is not supported");
802878
}
803879

804880
if(obj.length() == 0) {

0 commit comments

Comments
 (0)