1use prism::operation::TermValue;
25use prism::pipeline::{
26 ConstrainedTypeShape, ConstraintRef, IntoBindingValue, PartitionProductFields, ShapeViolation,
27 ViolationKind,
28};
29
30use crate::asn1::shapes::bounds::MAX_ASN1_DEPTH;
31
32pub(crate) const TAG_BOOLEAN: u8 = 0x01;
35pub(crate) const TAG_INTEGER: u8 = 0x02;
36pub(crate) const TAG_BIT_STRING: u8 = 0x03;
37pub(crate) const TAG_OCTET_STRING: u8 = 0x04;
38pub(crate) const TAG_NULL: u8 = 0x05;
39pub(crate) const TAG_OID: u8 = 0x06;
40pub(crate) const TAG_UTF8_STRING: u8 = 0x0C;
41pub(crate) const TAG_PRINTABLE_STRING: u8 = 0x13;
42pub(crate) const TAG_IA5_STRING: u8 = 0x16;
43pub(crate) const TAG_UTC_TIME: u8 = 0x17;
44pub(crate) const TAG_GENERALIZED_TIME: u8 = 0x18;
45pub(crate) const TAG_SEQUENCE: u8 = 0x30;
46pub(crate) const TAG_SET: u8 = 0x31;
47
48const INVALID_DER_VIOLATION: ShapeViolation = ShapeViolation {
51 shape_iri: "https://uor.foundation/addr/Asn1Value",
52 constraint_iri: "https://uor.foundation/addr/Asn1Value/validDer",
53 property_iri: "https://uor.foundation/addr/inputBytes",
54 expected_range: "https://uor.foundation/addr/ValidDerBytes",
55 min_count: 0,
56 max_count: 1,
57 kind: ViolationKind::ValueCheck,
58};
59
60const DEPTH_BOUND_VIOLATION: ShapeViolation = ShapeViolation {
61 shape_iri: "https://uor.foundation/addr/Asn1Value",
62 constraint_iri: "https://uor.foundation/addr/Asn1Value/depthBound",
63 property_iri: "https://uor.foundation/addr/Asn1Value/depth",
64 expected_range: "http://www.w3.org/2001/XMLSchema#nonNegativeInteger",
65 min_count: 0,
66 max_count: MAX_ASN1_DEPTH as u32,
67 kind: ViolationKind::CardinalityViolation,
68};
69
70pub fn validate_der(raw: &[u8]) -> Result<(), ShapeViolation> {
83 let mut pos = 0;
84 validate_tlv(raw, &mut pos, 0)?;
85 if pos != raw.len() {
86 return Err(INVALID_DER_VIOLATION);
87 }
88 Ok(())
89}
90
91fn validate_tlv(buf: &[u8], pos: &mut usize, depth: usize) -> Result<(), ShapeViolation> {
92 if depth > MAX_ASN1_DEPTH {
93 return Err(DEPTH_BOUND_VIOLATION);
94 }
95 if *pos >= buf.len() {
96 return Err(INVALID_DER_VIOLATION);
97 }
98 let tag = buf[*pos];
99 *pos += 1;
100 let content_len = decode_length(buf, pos)?;
101 if *pos + content_len > buf.len() {
102 return Err(INVALID_DER_VIOLATION);
103 }
104 let content_end = *pos + content_len;
105 match tag {
106 TAG_BOOLEAN => {
107 if content_len != 1 {
108 return Err(INVALID_DER_VIOLATION);
109 }
110 let b = buf[*pos];
111 if b != 0x00 && b != 0xFF {
112 return Err(INVALID_DER_VIOLATION);
113 }
114 *pos += 1;
115 }
116 TAG_INTEGER => {
117 if content_len == 0 {
118 return Err(INVALID_DER_VIOLATION);
119 }
120 if content_len >= 2 {
121 let b0 = buf[*pos];
122 let b1 = buf[*pos + 1];
123 if b0 == 0x00 && (b1 & 0x80) == 0 {
124 return Err(INVALID_DER_VIOLATION);
125 }
126 if b0 == 0xFF && (b1 & 0x80) != 0 {
127 return Err(INVALID_DER_VIOLATION);
128 }
129 }
130 *pos = content_end;
131 }
132 TAG_OCTET_STRING => {
133 *pos = content_end;
134 }
135 TAG_NULL => {
136 if content_len != 0 {
137 return Err(INVALID_DER_VIOLATION);
138 }
139 }
140 TAG_BIT_STRING => {
141 if content_len == 0 {
142 return Err(INVALID_DER_VIOLATION);
143 }
144 let unused = buf[*pos];
145 if unused > 7 {
146 return Err(INVALID_DER_VIOLATION);
147 }
148 if content_len == 1 && unused != 0 {
149 return Err(INVALID_DER_VIOLATION);
150 }
151 if content_len > 1 && unused > 0 {
152 let last = buf[content_end - 1];
153 let mask = (1u8 << unused) - 1;
154 if last & mask != 0 {
155 return Err(INVALID_DER_VIOLATION);
156 }
157 }
158 *pos = content_end;
159 }
160 TAG_OID => {
161 if content_len == 0 {
162 return Err(INVALID_DER_VIOLATION);
163 }
164 let mut p = *pos;
165 while p < content_end {
166 let sub_start = p;
167 while p < content_end && buf[p] & 0x80 != 0 {
168 p += 1;
169 }
170 if p >= content_end {
171 return Err(INVALID_DER_VIOLATION);
172 }
173 p += 1;
174 if p - sub_start > 1 && buf[sub_start] == 0x80 {
175 return Err(INVALID_DER_VIOLATION);
176 }
177 }
178 if p != content_end {
179 return Err(INVALID_DER_VIOLATION);
180 }
181 *pos = content_end;
182 }
183 TAG_UTF8_STRING => {
184 let bytes = &buf[*pos..content_end];
185 core::str::from_utf8(bytes).map_err(|_| INVALID_DER_VIOLATION)?;
186 *pos = content_end;
187 }
188 TAG_PRINTABLE_STRING => {
189 for &b in &buf[*pos..content_end] {
190 let ok = b.is_ascii_alphanumeric()
191 || matches!(
192 b,
193 b' ' | b'\''
194 | b'('
195 | b')'
196 | b'+'
197 | b','
198 | b'-'
199 | b'.'
200 | b'/'
201 | b':'
202 | b'='
203 | b'?'
204 );
205 if !ok {
206 return Err(INVALID_DER_VIOLATION);
207 }
208 }
209 *pos = content_end;
210 }
211 TAG_IA5_STRING => {
212 for &b in &buf[*pos..content_end] {
213 if b > 127 {
214 return Err(INVALID_DER_VIOLATION);
215 }
216 }
217 *pos = content_end;
218 }
219 TAG_UTC_TIME | TAG_GENERALIZED_TIME => {
220 for &b in &buf[*pos..content_end] {
221 if !b.is_ascii() {
222 return Err(INVALID_DER_VIOLATION);
223 }
224 }
225 *pos = content_end;
226 }
227 TAG_SEQUENCE | TAG_SET => {
228 while *pos < content_end {
229 validate_tlv(buf, pos, depth + 1)?;
230 }
231 if *pos != content_end {
232 return Err(INVALID_DER_VIOLATION);
233 }
234 }
235 _ => return Err(INVALID_DER_VIOLATION),
236 }
237 Ok(())
238}
239
240fn decode_length(buf: &[u8], pos: &mut usize) -> Result<usize, ShapeViolation> {
241 if *pos >= buf.len() {
242 return Err(INVALID_DER_VIOLATION);
243 }
244 let first = buf[*pos];
245 *pos += 1;
246 if first < 0x80 {
247 Ok(first as usize)
248 } else {
249 let nbytes = (first & 0x7F) as usize;
250 if nbytes == 0 {
251 return Err(INVALID_DER_VIOLATION);
252 }
253 if nbytes > core::mem::size_of::<usize>() || *pos + nbytes > buf.len() {
254 return Err(INVALID_DER_VIOLATION);
255 }
256 let mut len: usize = 0;
257 for _ in 0..nbytes {
258 len = (len << 8) | (buf[*pos] as usize);
259 *pos += 1;
260 }
261 if len < 128 {
262 return Err(INVALID_DER_VIOLATION);
263 }
264 Ok(len)
265 }
266}
267
268#[derive(Clone, Copy, Debug)]
274pub struct Asn1Carrier<'a>(&'a [u8]);
275
276impl<'a> Asn1Carrier<'a> {
277 #[must_use]
280 pub fn new(der: &'a [u8]) -> Self {
281 Self(der)
282 }
283
284 #[must_use]
286 pub fn canonical_bytes(&self) -> &'a [u8] {
287 self.0
288 }
289}
290
291impl ConstrainedTypeShape for Asn1Carrier<'_> {
292 const IRI: &'static str = "https://uor.foundation/addr/Asn1Value";
293 const SITE_COUNT: usize = 1;
294 const CONSTRAINTS: &'static [ConstraintRef] = &[];
295 const CYCLE_SIZE: u64 = u64::MAX;
296}
297
298impl prism::uor_foundation::pipeline::__sdk_seal::Sealed for Asn1Carrier<'_> {}
299
300impl<'a> IntoBindingValue<'a> for Asn1Carrier<'a> {
301 fn as_binding_value<const INLINE_BYTES: usize>(&self) -> TermValue<'a, INLINE_BYTES> {
302 TermValue::borrowed(self.0)
304 }
305}
306
307impl PartitionProductFields for Asn1Carrier<'_> {
308 const FIELDS: &'static [(u32, u32)] = &[];
309 const FIELD_NAMES: &'static [&'static str] = &[];
310}
311
312#[cfg(feature = "alloc")]
319#[derive(Clone, PartialEq, Eq)]
320pub struct Asn1Value {
321 bytes: alloc::vec::Vec<u8>,
322}
323
324#[cfg(feature = "alloc")]
325impl core::fmt::Debug for Asn1Value {
326 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
327 f.debug_struct("Asn1Value")
328 .field("len", &self.bytes.len())
329 .finish_non_exhaustive()
330 }
331}
332
333#[cfg(feature = "alloc")]
334impl Asn1Value {
335 fn from_vec(bytes: alloc::vec::Vec<u8>) -> Self {
336 Self { bytes }
337 }
338
339 pub fn parse(raw: &[u8]) -> Result<Self, ShapeViolation> {
345 validate_der(raw)?;
346 Ok(Self::from_vec(raw.to_vec()))
347 }
348
349 #[must_use]
351 pub fn boolean(value: bool) -> Self {
352 Self::from_vec(alloc::vec![TAG_BOOLEAN, 1, if value { 0xFF } else { 0x00 }])
353 }
354
355 #[must_use]
357 pub fn null() -> Self {
358 Self::from_vec(alloc::vec![TAG_NULL, 0])
359 }
360
361 #[must_use]
364 pub fn integer(value: i64) -> Self {
365 let be = value.to_be_bytes();
366 let mut start = 0;
367 if value >= 0 {
368 while start < 7 && be[start] == 0x00 && (be[start + 1] & 0x80) == 0 {
369 start += 1;
370 }
371 } else {
372 while start < 7 && be[start] == 0xFF && (be[start + 1] & 0x80) != 0 {
373 start += 1;
374 }
375 }
376 Self::primitive_vec(TAG_INTEGER, &be[start..])
377 }
378
379 #[must_use]
381 pub fn octet_string(bytes: &[u8]) -> Self {
382 Self::primitive_vec(TAG_OCTET_STRING, bytes)
383 }
384
385 fn primitive_vec(tag: u8, content: &[u8]) -> Self {
386 let mut out = alloc::vec::Vec::new();
387 out.push(tag);
388 push_length(&mut out, content.len());
389 out.extend_from_slice(content);
390 Self::from_vec(out)
391 }
392
393 #[must_use]
395 pub fn sequence(children: &[Asn1Value]) -> Self {
396 Self::constructed(TAG_SEQUENCE, children, false)
397 }
398
399 #[must_use]
402 pub fn set(children: &[Asn1Value]) -> Self {
403 Self::constructed(TAG_SET, children, true)
404 }
405
406 fn constructed(tag: u8, children: &[Asn1Value], sort: bool) -> Self {
407 let mut kids: alloc::vec::Vec<&[u8]> = children.iter().map(|c| c.tagged_bytes()).collect();
408 if sort {
409 kids.sort_unstable();
410 }
411 let total: usize = kids.iter().map(|k| k.len()).sum();
412 let mut out = alloc::vec::Vec::new();
413 out.push(tag);
414 push_length(&mut out, total);
415 for k in kids {
416 out.extend_from_slice(k);
417 }
418 Self::from_vec(out)
419 }
420
421 pub fn bit_string(bits: &[u8], unused_bits: u8) -> Result<Self, ShapeViolation> {
428 if unused_bits > 7 {
429 return Err(INVALID_DER_VIOLATION);
430 }
431 if bits.is_empty() && unused_bits != 0 {
432 return Err(INVALID_DER_VIOLATION);
433 }
434 if !bits.is_empty() && unused_bits > 0 {
435 let last = bits[bits.len() - 1];
436 let mask = (1u8 << unused_bits) - 1;
437 if last & mask != 0 {
438 return Err(INVALID_DER_VIOLATION);
439 }
440 }
441 let mut content = alloc::vec::Vec::with_capacity(1 + bits.len());
442 content.push(unused_bits);
443 content.extend_from_slice(bits);
444 Ok(Self::primitive_vec(TAG_BIT_STRING, &content))
445 }
446
447 pub fn object_identifier(arcs: &[u32]) -> Result<Self, ShapeViolation> {
454 if arcs.len() < 2 {
455 return Err(INVALID_DER_VIOLATION);
456 }
457 let x1 = arcs[0];
458 let x2 = arcs[1];
459 if x1 > 2 {
460 return Err(INVALID_DER_VIOLATION);
461 }
462 if x1 < 2 && x2 >= 40 {
463 return Err(INVALID_DER_VIOLATION);
464 }
465 let mut content = alloc::vec::Vec::new();
466 encode_oid_subid(40 * x1 + x2, &mut content);
467 for &arc in &arcs[2..] {
468 encode_oid_subid(arc, &mut content);
469 }
470 Ok(Self::primitive_vec(TAG_OID, &content))
471 }
472
473 #[must_use]
475 pub fn utf8_string(s: &str) -> Self {
476 Self::primitive_vec(TAG_UTF8_STRING, s.as_bytes())
477 }
478
479 pub fn printable_string(s: &str) -> Result<Self, ShapeViolation> {
486 for c in s.chars() {
487 let ok = c.is_ascii_alphanumeric()
488 || matches!(
489 c,
490 ' ' | '\'' | '(' | ')' | '+' | ',' | '-' | '.' | '/' | ':' | '=' | '?'
491 );
492 if !ok {
493 return Err(INVALID_DER_VIOLATION);
494 }
495 }
496 Ok(Self::primitive_vec(TAG_PRINTABLE_STRING, s.as_bytes()))
497 }
498
499 pub fn ia5_string(s: &str) -> Result<Self, ShapeViolation> {
505 if !s.is_ascii() {
506 return Err(INVALID_DER_VIOLATION);
507 }
508 Ok(Self::primitive_vec(TAG_IA5_STRING, s.as_bytes()))
509 }
510
511 #[must_use]
513 pub fn tagged_bytes(&self) -> &[u8] {
514 &self.bytes
515 }
516}
517
518#[cfg(feature = "alloc")]
520fn encode_oid_subid(mut value: u32, out: &mut alloc::vec::Vec<u8>) {
521 if value == 0 {
522 out.push(0);
523 return;
524 }
525 let mut buf = [0u8; 5];
526 let mut i = 0;
527 while value > 0 {
528 buf[i] = (value & 0x7F) as u8;
529 value >>= 7;
530 i += 1;
531 }
532 for j in (1..i).rev() {
533 out.push(buf[j] | 0x80);
534 }
535 out.push(buf[0]);
536}
537
538#[cfg(feature = "alloc")]
540fn push_length(out: &mut alloc::vec::Vec<u8>, len: usize) {
541 if len < 128 {
542 out.push(len as u8);
543 return;
544 }
545 let mut value = len;
546 let mut bytes = [0u8; 8];
547 let mut count = 0;
548 while value > 0 {
549 bytes[count] = (value & 0xFF) as u8;
550 value >>= 8;
551 count += 1;
552 }
553 out.push(0x80 | (count as u8));
554 for i in 0..count {
555 out.push(bytes[count - 1 - i]);
556 }
557}
558
559#[cfg(feature = "alloc")]
568pub fn canonicalize(raw: &[u8]) -> Result<alloc::vec::Vec<u8>, ShapeViolation> {
569 validate_der(raw)?;
570 Ok(raw.to_vec())
571}
572
573#[cfg(all(test, feature = "alloc"))]
574mod tests {
575 use super::*;
576
577 #[test]
578 fn boolean_der_encoding_matches_x690_8_2_2() {
579 assert_eq!(Asn1Value::boolean(true).tagged_bytes(), &[0x01, 0x01, 0xFF]);
580 assert_eq!(
581 Asn1Value::boolean(false).tagged_bytes(),
582 &[0x01, 0x01, 0x00]
583 );
584 }
585
586 #[test]
587 fn null_der_encoding_matches_x690_8_8() {
588 assert_eq!(Asn1Value::null().tagged_bytes(), &[0x05, 0x00]);
589 }
590
591 #[test]
592 fn integer_der_encoding_minimum_octets() {
593 assert_eq!(Asn1Value::integer(0).tagged_bytes(), &[0x02, 0x01, 0x00]);
594 assert_eq!(Asn1Value::integer(127).tagged_bytes(), &[0x02, 0x01, 0x7F]);
595 assert_eq!(
596 Asn1Value::integer(128).tagged_bytes(),
597 &[0x02, 0x02, 0x00, 0x80]
598 );
599 assert_eq!(Asn1Value::integer(-1).tagged_bytes(), &[0x02, 0x01, 0xFF]);
600 assert_eq!(Asn1Value::integer(-128).tagged_bytes(), &[0x02, 0x01, 0x80]);
601 }
602
603 #[test]
604 fn set_sorts_children_by_encoding() {
605 let sorted = Asn1Value::set(&[Asn1Value::integer(2), Asn1Value::integer(1)]);
606 let direct = Asn1Value::set(&[Asn1Value::integer(1), Asn1Value::integer(2)]);
607 assert_eq!(sorted.tagged_bytes(), direct.tagged_bytes());
608 }
609
610 #[test]
611 fn parse_round_trips_well_formed_der() {
612 let cases: &[Asn1Value] = &[
613 Asn1Value::boolean(true),
614 Asn1Value::null(),
615 Asn1Value::integer(42),
616 Asn1Value::octet_string(b"hello"),
617 Asn1Value::sequence(&[Asn1Value::integer(1), Asn1Value::boolean(true)]),
618 ];
619 for v in cases {
620 let parsed = Asn1Value::parse(v.tagged_bytes()).expect("valid DER");
621 assert_eq!(parsed.tagged_bytes(), v.tagged_bytes());
622 }
623 }
624
625 #[test]
626 fn rejects_non_canonical_boolean_byte() {
627 let err = validate_der(&[0x01, 0x01, 0x01]).expect_err("rejects non-canonical");
628 assert_eq!(err.constraint_iri, INVALID_DER_VIOLATION.constraint_iri);
629 }
630
631 #[test]
632 fn rejects_non_minimum_integer_encoding() {
633 let err = validate_der(&[0x02, 0x02, 0x00, 0x01]).expect_err("non-minimal");
634 assert_eq!(err.constraint_iri, INVALID_DER_VIOLATION.constraint_iri);
635 }
636
637 #[test]
638 fn rejects_long_form_length_under_128() {
639 let err = validate_der(&[0x04, 0x81, 0x05, 0, 0, 0, 0, 0]).expect_err("non-canonical");
640 assert_eq!(err.constraint_iri, INVALID_DER_VIOLATION.constraint_iri);
641 }
642
643 #[test]
644 fn rejects_indefinite_length() {
645 let err = validate_der(&[0x30, 0x80]).expect_err("BER not DER");
646 assert_eq!(err.constraint_iri, INVALID_DER_VIOLATION.constraint_iri);
647 }
648}