diff --git a/const-oid/src/lib.rs b/const-oid/src/lib.rs index 679389eec..373d92f89 100644 --- a/const-oid/src/lib.rs +++ b/const-oid/src/lib.rs @@ -42,10 +42,20 @@ pub use crate::{ use crate::encoder::Encoder; use core::{borrow::Borrow, fmt, ops::Deref, str::FromStr}; -/// Default maximum size. +/// Default maximum size: the largest BER/DER contents RFC 5280 Appendix B +/// requires implementations to accept. /// -/// Makes `ObjectIdentifier` 40-bytes total w\ 1-byte length. -const DEFAULT_MAX_SIZE: usize = 39; +/// Appendix B mandates support for OIDs with at most 20 arcs, every arc below +/// 2^28, whose dotted-decimal string is at most 100 bytes. The 100-byte string +/// is the binding limit: the leading `1.2.` packs the first two arcs into a +/// single octet (the second arc is 0..=39 under X.660), and every further +/// 4-octet arc costs at least 8 string bytes to spell (values in +/// 2^21..=2^28-1 start at 7 digits), so 96 remaining bytes buy at most +/// twelve of them: 1 + 12 * 4 = 49 contents octets, reached by twelve arcs +/// of 9999999 below 1.2 (a 99-byte string). +/// +/// Makes `ObjectIdentifier` 50-bytes total w\ 1-byte length. +const DEFAULT_MAX_SIZE: usize = 49; /// Object identifier (OID). /// @@ -60,7 +70,7 @@ const DEFAULT_MAX_SIZE: usize = 39; /// - The OID MUST have at least 3 arcs /// - The first arc MUST be within the range 0-2 /// - The second arc MUST be within the range 0-39 -/// - The BER/DER encoding of the OID MUST be shorter than +/// - The BER/DER encoding of the OID MUST be no longer than /// [`ObjectIdentifier::MAX_SIZE`] #[derive(Clone, Copy, Eq, Hash, PartialEq, PartialOrd, Ord)] pub struct ObjectIdentifier { diff --git a/const-oid/tests/oid.rs b/const-oid/tests/oid.rs index 5e6005376..97754ddfc 100644 --- a/const-oid/tests/oid.rs +++ b/const-oid/tests/oid.rs @@ -316,3 +316,45 @@ fn starts_with() { assert!(!child.starts_with(oid("2.2.3"))); assert!(!child.starts_with(oid("1.2.3.4.5.6"))); } + +/// Worst case RFC 5280 Appendix B mandates support for: at most 20 arcs, +/// every arc below 2^28, dotted-decimal string at most 100 bytes. +/// Twelve 7-digit arcs below 1.2 spell a 99-byte string that encodes to +/// 49 contents octets, the ceiling for any Appendix B-conformant OID. +#[test] +fn rfc5280_appendix_b_worst_case_fits() { + let oid_str = "1.2.9999999.9999999.9999999.9999999.9999999.9999999.9999999.9999999.9999999.9999999.9999999.9999999"; + assert_eq!(oid_str.len(), 99); + + let oid = ObjectIdentifier::new(oid_str).unwrap(); + assert_eq!(oid.as_bytes().len(), 49); + assert_eq!(oid.to_string(), oid_str); + + let round_trip = ObjectIdentifier::from_bytes(oid.as_bytes()).unwrap(); + assert_eq!(round_trip, oid); +} + +/// 100-byte dotted string from #2485 encoding to 40 contents octets. +#[test] +fn rfc5280_appendix_b_100_byte_40_octet_oid_fits() { + let oid_str = "1.3.268435455.268435455.268435455.268435455.268435455.268435455.268435455.268435455.268435455.123456"; + assert_eq!(oid_str.len(), 100); + + let oid = ObjectIdentifier::new(oid_str).unwrap(); + assert_eq!(oid.as_bytes().len(), 40); + assert_eq!(oid.to_string(), oid_str); + + let round_trip = ObjectIdentifier::from_bytes(oid.as_bytes()).unwrap(); + assert_eq!(round_trip, oid); +} + +/// The default capacity is what grows with this change: an explicitly +/// smaller capacity still rejects OIDs that do not fit it. +#[test] +fn explicit_smaller_capacity_still_rejects() { + let oid_str = "1.2.9999999.9999999.9999999.9999999.9999999.9999999.9999999.9999999.9999999.9999999.9999999.9999999"; + let oid = ObjectIdentifier::new(oid_str).unwrap(); + let ber = oid.as_bytes(); + assert_eq!(ber.len(), 49); + assert!(ObjectIdentifier::<39>::from_bytes_sized(ber).is_err()); +}