From 96d18a92ed339ef9c007fc075276e444b7b06ca8 Mon Sep 17 00:00:00 2001
From: Guillaume Dallenne <guillaume.dallenne@stormshield.eu>
Date: Mon, 19 Dec 2022 13:47:23 +0100
Subject: [PATCH 1/2] const-oid+der+spki+x509-cert: add feature gated Arbitrary
 impl for Certificate

---
 .github/workflows/const-oid.yml  |  2 +-
 .github/workflows/der.yml        |  2 +-
 .github/workflows/spki.yml       |  2 +-
 .github/workflows/x509-cert.yml  |  2 +-
 Cargo.lock                       | 24 ++++++++++++++++++++++
 const-oid/Cargo.toml             |  8 ++++++--
 const-oid/README.md              |  4 ++--
 const-oid/src/lib.rs             | 26 ++++++++++++++++++++++++
 der/Cargo.toml                   |  2 ++
 der/src/asn1/any.rs              |  3 +++
 der/src/asn1/bit_string.rs       | 17 ++++++++++++++++
 der/src/asn1/generalized_time.rs |  2 ++
 der/src/asn1/integer/bigint.rs   |  4 ++--
 der/src/asn1/set_of.rs           | 20 +++++++++++++++++++
 der/src/asn1/utc_time.rs         | 34 ++++++++++++++++++++++++++++++++
 der/src/byte_slice.rs            | 17 ++++++++++++++++
 der/src/bytes.rs                 | 17 ++++++++++++++++
 der/src/datetime.rs              | 17 ++++++++++++++++
 der/src/length.rs                | 13 ++++++++++++
 der/src/tag.rs                   |  2 ++
 der/src/tag/number.rs            | 13 ++++++++++++
 spki/Cargo.toml                  |  4 +++-
 spki/src/algorithm.rs            |  1 +
 spki/src/spki.rs                 |  1 +
 x509-cert/Cargo.toml             |  4 +++-
 x509-cert/src/attr.rs            |  1 +
 x509-cert/src/certificate.rs     |  3 +++
 x509-cert/src/ext.rs             |  1 +
 x509-cert/src/name.rs            |  2 ++
 x509-cert/src/serial_number.rs   | 15 ++++++++++++++
 x509-cert/src/time.rs            |  2 ++
 31 files changed, 253 insertions(+), 12 deletions(-)

diff --git a/.github/workflows/const-oid.yml b/.github/workflows/const-oid.yml
index 64676a646..e0b5979e4 100644
--- a/.github/workflows/const-oid.yml
+++ b/.github/workflows/const-oid.yml
@@ -37,7 +37,7 @@ jobs:
           target: ${{ matrix.target }}
           override: true
       - uses: RustCrypto/actions/cargo-hack-install@master
-      - run: cargo hack build --target ${{ matrix.target }} --feature-powerset --exclude-features std
+      - run: cargo hack build --target ${{ matrix.target }} --feature-powerset --exclude-features std,arbitrary
 
   minimal-versions:
     uses: RustCrypto/actions/.github/workflows/minimal-versions.yml@master
diff --git a/.github/workflows/der.yml b/.github/workflows/der.yml
index f99fe2cad..3f3dfe10b 100644
--- a/.github/workflows/der.yml
+++ b/.github/workflows/der.yml
@@ -38,7 +38,7 @@ jobs:
           target: ${{ matrix.target }}
           override: true
       - uses: RustCrypto/actions/cargo-hack-install@master
-      - run: cargo hack build --target ${{ matrix.target }} --feature-powerset --exclude-features std
+      - run: cargo hack build --target ${{ matrix.target }} --feature-powerset --exclude-features std,arbitrary
 
   minimal-versions:
     uses: RustCrypto/actions/.github/workflows/minimal-versions.yml@master
diff --git a/.github/workflows/spki.yml b/.github/workflows/spki.yml
index bf0bc4419..39477d46b 100644
--- a/.github/workflows/spki.yml
+++ b/.github/workflows/spki.yml
@@ -40,7 +40,7 @@ jobs:
           target: ${{ matrix.target }}
           override: true
       - uses: RustCrypto/actions/cargo-hack-install@master
-      - run: cargo hack build --target ${{ matrix.target }} --feature-powerset --exclude-features std
+      - run: cargo hack build --target ${{ matrix.target }} --feature-powerset --exclude-features std,arbitrary
 
   minimal-versions:
     uses: RustCrypto/actions/.github/workflows/minimal-versions.yml@master
diff --git a/.github/workflows/x509-cert.yml b/.github/workflows/x509-cert.yml
index 70cacf336..ef7784c52 100644
--- a/.github/workflows/x509-cert.yml
+++ b/.github/workflows/x509-cert.yml
@@ -40,7 +40,7 @@ jobs:
           target: ${{ matrix.target }}
           override: true
       - uses: RustCrypto/actions/cargo-hack-install@master
-      - run: cargo hack build --target ${{ matrix.target }} --feature-powerset --exclude-features std
+      - run: cargo hack build --target ${{ matrix.target }} --feature-powerset --exclude-features std,arbitrary
 
   minimal-versions:
     uses: RustCrypto/actions/.github/workflows/minimal-versions.yml@master
diff --git a/Cargo.lock b/Cargo.lock
index d40ea0977..aed11cea7 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -28,6 +28,15 @@ version = "0.1.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299"
 
+[[package]]
+name = "arbitrary"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "29d47fbf90d5149a107494b15a7dc8d69b351be2db3bb9691740e88ec17fd880"
+dependencies = [
+ "derive_arbitrary",
+]
+
 [[package]]
 name = "atty"
 version = "0.2.14"
@@ -210,6 +219,7 @@ dependencies = [
 name = "const-oid"
 version = "0.10.0-pre"
 dependencies = [
+ "arbitrary",
  "hex-literal",
 ]
 
@@ -315,6 +325,7 @@ dependencies = [
 name = "der"
 version = "0.7.0-pre"
 dependencies = [
+ "arbitrary",
  "const-oid",
  "der_derive",
  "flagset",
@@ -335,6 +346,17 @@ dependencies = [
  "syn",
 ]
 
+[[package]]
+name = "derive_arbitrary"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f8a16495aeb28047bb1185fca837baf755e7d71ed3aeed7f8504654ffa927208"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
 [[package]]
 name = "des"
 version = "0.8.1"
@@ -1165,6 +1187,7 @@ dependencies = [
 name = "spki"
 version = "0.7.0-pre"
 dependencies = [
+ "arbitrary",
  "base64ct",
  "der",
  "hex-literal",
@@ -1435,6 +1458,7 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
 name = "x509-cert"
 version = "0.2.0-pre"
 dependencies = [
+ "arbitrary",
  "const-oid",
  "der",
  "flagset",
diff --git a/const-oid/Cargo.toml b/const-oid/Cargo.toml
index e9b439a0c..29ac7bf2b 100644
--- a/const-oid/Cargo.toml
+++ b/const-oid/Cargo.toml
@@ -14,14 +14,18 @@ categories = ["cryptography", "data-structures", "encoding", "no-std", "parser-i
 keywords = ["iso", "iec", "itu", "oid"]
 readme = "README.md"
 edition = "2021"
-rust-version = "1.60"
+rust-version = "1.65"
+
+[dependencies]
+arbitrary = { version = "1.2.0", features = ["derive"], optional = true }
 
 [dev-dependencies]
 hex-literal = "0.3"
 
 [features]
-std = []
+arbitrary = ["std", "dep:arbitrary"]
 db = []
+std = []
 
 [package.metadata.docs.rs]
 all-features = true
diff --git a/const-oid/README.md b/const-oid/README.md
index 466eaff0a..cf7e5ffac 100644
--- a/const-oid/README.md
+++ b/const-oid/README.md
@@ -57,7 +57,7 @@ well as a runtime OID library.
 
 ## Minimum Supported Rust Version
 
-This crate requires **Rust 1.60** at a minimum.
+This crate requires **Rust 1.65** at a minimum.
 
 We may change the MSRV in the future, but it will be accompanied by a minor
 version bump.
@@ -84,7 +84,7 @@ dual licensed as above, without any additional terms or conditions.
 [docs-image]: https://docs.rs/const-oid/badge.svg
 [docs-link]: https://docs.rs/const-oid/
 [license-image]: https://img.shields.io/badge/license-Apache2.0/MIT-blue.svg
-[rustc-image]: https://img.shields.io/badge/rustc-1.60+-blue.svg
+[rustc-image]: https://img.shields.io/badge/rustc-1.65+-blue.svg
 [chat-image]: https://img.shields.io/badge/zulip-join_chat-blue.svg
 [chat-link]: https://rustcrypto.zulipchat.com/#narrow/stream/300570-formats
 [build-image]: https://github.com/RustCrypto/formats/workflows/const-oid/badge.svg?branch=master&event=push
diff --git a/const-oid/src/lib.rs b/const-oid/src/lib.rs
index 3212ef3e9..3bccf218c 100644
--- a/const-oid/src/lib.rs
+++ b/const-oid/src/lib.rs
@@ -271,3 +271,29 @@ impl fmt::Display for ObjectIdentifier {
         Ok(())
     }
 }
+
+// Implement by hand because the derive would create invalid values.
+// Use the constructor to create a valid oid with at least 3 arcs.
+#[cfg(feature = "arbitrary")]
+impl<'a> arbitrary::Arbitrary<'a> for ObjectIdentifier {
+    fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
+        let first = u.int_in_range(0..=arcs::ARC_MAX_FIRST)?;
+        let second = u.int_in_range(0..=arcs::ARC_MAX_SECOND)?;
+        let third = u.arbitrary()?;
+
+        let mut oid = Self::from_arcs([first, second, third])
+            .map_err(|_| arbitrary::Error::IncorrectFormat)?;
+
+        for arc in u.arbitrary_iter()? {
+            oid = oid
+                .push_arc(arc?)
+                .map_err(|_| arbitrary::Error::IncorrectFormat)?;
+        }
+
+        Ok(oid)
+    }
+
+    fn size_hint(depth: usize) -> (usize, Option<usize>) {
+        (Arc::size_hint(depth).0.saturating_mul(3), None)
+    }
+}
diff --git a/der/Cargo.toml b/der/Cargo.toml
index c0da5cf68..52c8fc03e 100644
--- a/der/Cargo.toml
+++ b/der/Cargo.toml
@@ -16,6 +16,7 @@ edition = "2021"
 rust-version = "1.65"
 
 [dependencies]
+arbitrary = { version = "1.2.0", features = ["derive"], optional = true }
 const-oid = { version = "=0.10.0-pre", optional = true, path = "../const-oid" }
 der_derive = { version = "=0.7.0-pre", optional = true, path = "derive" }
 flagset = { version = "0.4.3", optional = true }
@@ -29,6 +30,7 @@ proptest = "1"
 
 [features]
 alloc = []
+arbitrary = ["std", "dep:arbitrary", "const-oid?/arbitrary"]
 derive = ["der_derive"]
 oid = ["const-oid"]
 pem = ["alloc", "pem-rfc7468/alloc", "zeroize"]
diff --git a/der/src/asn1/any.rs b/der/src/asn1/any.rs
index 94157a56b..b9b88ad7e 100644
--- a/der/src/asn1/any.rs
+++ b/der/src/asn1/any.rs
@@ -1,4 +1,5 @@
 //! ASN.1 `ANY` type.
+#![cfg_attr(feature = "arbitrary", allow(clippy::integer_arithmetic))]
 
 use crate::{
     asn1::*, ByteSlice, Choice, Decode, DecodeValue, DerOrd, EncodeValue, Error, ErrorKind,
@@ -23,6 +24,7 @@ use crate::asn1::ObjectIdentifier;
 /// Nevertheless, this crate defines an `ANY` type as it remains a familiar
 /// and useful concept which is still extensively used in things like
 /// PKI-related RFCs.
+#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
 #[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
 pub struct AnyRef<'a> {
     /// Tag representing the type of the encoded value.
@@ -202,6 +204,7 @@ impl<'a> TryFrom<&'a [u8]> for AnyRef<'a> {
 /// backing data.
 #[cfg(feature = "alloc")]
 #[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
+#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
 #[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
 pub struct Any {
     /// Tag representing the type of the encoded value.
diff --git a/der/src/asn1/bit_string.rs b/der/src/asn1/bit_string.rs
index 800f5f9d6..7839ca42f 100644
--- a/der/src/asn1/bit_string.rs
+++ b/der/src/asn1/bit_string.rs
@@ -198,6 +198,23 @@ impl<'a> FixedTag for BitStringRef<'a> {
     const TAG: Tag = Tag::BitString;
 }
 
+// Implement by hand because the derive would create invalid values.
+// Use the constructor to create a valid value.
+#[cfg(feature = "arbitrary")]
+impl<'a> arbitrary::Arbitrary<'a> for BitStringRef<'a> {
+    fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
+        Self::new(
+            u.int_in_range(0..=Self::MAX_UNUSED_BITS)?,
+            ByteSlice::arbitrary(u)?.as_slice(),
+        )
+        .map_err(|_| arbitrary::Error::IncorrectFormat)
+    }
+
+    fn size_hint(depth: usize) -> (usize, Option<usize>) {
+        arbitrary::size_hint::and(u8::size_hint(depth), ByteSlice::size_hint(depth))
+    }
+}
+
 /// Owned form of ASN.1 `BIT STRING` type.
 ///
 /// This type provides the same functionality as [`BitStringRef`] but owns the
diff --git a/der/src/asn1/generalized_time.rs b/der/src/asn1/generalized_time.rs
index 9950e368e..9e148147c 100644
--- a/der/src/asn1/generalized_time.rs
+++ b/der/src/asn1/generalized_time.rs
@@ -1,4 +1,5 @@
 //! ASN.1 `GeneralizedTime` support.
+#![cfg_attr(feature = "arbitrary", allow(clippy::integer_arithmetic))]
 
 use crate::{
     asn1::AnyRef,
@@ -26,6 +27,7 @@ use time::PrimitiveDateTime;
 /// > is zero.  GeneralizedTime values MUST NOT include fractional seconds.
 ///
 /// [1]: https://tools.ietf.org/html/rfc5280#section-4.1.2.5.2
+#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
 #[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
 pub struct GeneralizedTime(DateTime);
 
diff --git a/der/src/asn1/integer/bigint.rs b/der/src/asn1/integer/bigint.rs
index 79dd55e5a..1d5eda0a1 100644
--- a/der/src/asn1/integer/bigint.rs
+++ b/der/src/asn1/integer/bigint.rs
@@ -122,7 +122,7 @@ mod allocating {
     }
 
     impl Uint {
-        /// Create a new [`UintRef`] from a byte slice.
+        /// Create a new [`Uint`] from a byte slice.
         pub fn new(bytes: &[u8]) -> Result<Self> {
             let inner = Bytes::new(uint::strip_leading_zeroes(bytes))
                 .map_err(|_| ErrorKind::Length { tag: Self::TAG })?;
@@ -136,7 +136,7 @@ mod allocating {
             self.inner.as_slice()
         }
 
-        /// Get the length of this [`UintRef`] in bytes.
+        /// Get the length of this [`Uint`] in bytes.
         pub fn len(&self) -> Length {
             self.inner.len()
         }
diff --git a/der/src/asn1/set_of.rs b/der/src/asn1/set_of.rs
index b8c4b0da4..bc9dc566b 100644
--- a/der/src/asn1/set_of.rs
+++ b/der/src/asn1/set_of.rs
@@ -375,6 +375,26 @@ where
     }
 }
 
+// Implement by hand because the derive would create invalid values.
+// Use the conversion from Vec to create a valid value.
+#[cfg(feature = "arbitrary")]
+impl<'a, T> arbitrary::Arbitrary<'a> for SetOfVec<T>
+where
+    T: DerOrd + arbitrary::Arbitrary<'a>,
+{
+    fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
+        Self::try_from(
+            u.arbitrary_iter()?
+                .collect::<std::result::Result<Vec<_>, _>>()?,
+        )
+        .map_err(|_| arbitrary::Error::IncorrectFormat)
+    }
+
+    fn size_hint(_depth: usize) -> (usize, Option<usize>) {
+        (0, None)
+    }
+}
+
 /// Sort a mut slice according to its [`DerOrd`], returning any errors which
 /// might occur during the comparison.
 ///
diff --git a/der/src/asn1/utc_time.rs b/der/src/asn1/utc_time.rs
index 7c2381155..69e818071 100644
--- a/der/src/asn1/utc_time.rs
+++ b/der/src/asn1/utc_time.rs
@@ -195,6 +195,40 @@ impl TryFrom<AnyRef<'_>> for UtcTime {
     }
 }
 
+// Implement by hand because the derive would create invalid values.
+// Use the conversion from DateTime to create a valid value.
+// The DateTime type has a way bigger range of valid years than UtcTime,
+// so the DateTime year is mapped into a valid range to throw away less inputs.
+#[cfg(feature = "arbitrary")]
+impl<'a> arbitrary::Arbitrary<'a> for UtcTime {
+    fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
+        const MIN_YEAR: u16 = 1970;
+        const VALID_YEAR_COUNT: u16 = MAX_YEAR - MIN_YEAR + 1;
+        const AVERAGE_SECONDS_IN_YEAR: u64 = 31_556_952;
+
+        let datetime = DateTime::arbitrary(u)?;
+        let year = datetime.year();
+        let duration = datetime.unix_duration();
+
+        // Clamp the year into a valid range to not throw away too much input
+        let valid_year = (year.saturating_sub(MIN_YEAR))
+            .rem_euclid(VALID_YEAR_COUNT)
+            .saturating_add(MIN_YEAR);
+        let year_to_remove = year.saturating_sub(valid_year);
+        let valid_duration = duration
+            - Duration::from_secs(
+                u64::from(year_to_remove).saturating_mul(AVERAGE_SECONDS_IN_YEAR),
+            );
+
+        Self::from_date_time(DateTime::from_unix_duration(valid_duration).expect("supported range"))
+            .map_err(|_| arbitrary::Error::IncorrectFormat)
+    }
+
+    fn size_hint(depth: usize) -> (usize, Option<usize>) {
+        DateTime::size_hint(depth)
+    }
+}
+
 #[cfg(test)]
 mod tests {
     use super::UtcTime;
diff --git a/der/src/byte_slice.rs b/der/src/byte_slice.rs
index f5edddded..7a462496d 100644
--- a/der/src/byte_slice.rs
+++ b/der/src/byte_slice.rs
@@ -106,6 +106,23 @@ impl<'a> TryFrom<&'a [u8]> for ByteSlice<'a> {
     }
 }
 
+// Implement by hand because the derive would create invalid values.
+// Make sure the length and the inner.len matches.
+#[cfg(feature = "arbitrary")]
+impl<'a> arbitrary::Arbitrary<'a> for ByteSlice<'a> {
+    fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
+        let length = u.arbitrary()?;
+        Ok(Self {
+            length,
+            inner: u.bytes(u32::from(length) as usize)?,
+        })
+    }
+
+    fn size_hint(depth: usize) -> (usize, Option<usize>) {
+        arbitrary::size_hint::and(Length::size_hint(depth), (0, None))
+    }
+}
+
 #[cfg(feature = "alloc")]
 mod allocating {
     use super::ByteSlice;
diff --git a/der/src/bytes.rs b/der/src/bytes.rs
index 5f9167c90..ea261373a 100644
--- a/der/src/bytes.rs
+++ b/der/src/bytes.rs
@@ -121,3 +121,20 @@ impl TryFrom<&[u8]> for Bytes {
         Self::new(slice)
     }
 }
+
+// Implement by hand because the derive would create invalid values.
+// Make sure the length and the inner.len matches.
+#[cfg(feature = "arbitrary")]
+impl<'a> arbitrary::Arbitrary<'a> for Bytes {
+    fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
+        let length = u.arbitrary()?;
+        Ok(Self {
+            length,
+            inner: Box::from(u.bytes(u32::from(length) as usize)?),
+        })
+    }
+
+    fn size_hint(depth: usize) -> (usize, Option<usize>) {
+        arbitrary::size_hint::and(Length::size_hint(depth), (0, None))
+    }
+}
diff --git a/der/src/datetime.rs b/der/src/datetime.rs
index 2b4c504bf..6e2afcc9c 100644
--- a/der/src/datetime.rs
+++ b/der/src/datetime.rs
@@ -360,6 +360,23 @@ impl TryFrom<PrimitiveDateTime> for DateTime {
     }
 }
 
+// Implement by hand because the derive would create invalid values.
+// Use the conversion from Duration to create a valid value.
+#[cfg(feature = "arbitrary")]
+impl<'a> arbitrary::Arbitrary<'a> for DateTime {
+    fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
+        Self::from_unix_duration(Duration::new(
+            u.int_in_range(0..=MAX_UNIX_DURATION.as_secs().saturating_sub(1))?,
+            u.int_in_range(0..=999_999_999)?,
+        ))
+        .map_err(|_| arbitrary::Error::IncorrectFormat)
+    }
+
+    fn size_hint(depth: usize) -> (usize, Option<usize>) {
+        arbitrary::size_hint::and(u64::size_hint(depth), u32::size_hint(depth))
+    }
+}
+
 /// Decode 2-digit decimal value
 // TODO(tarcieri): checked arithmetic
 #[allow(clippy::integer_arithmetic)]
diff --git a/der/src/length.rs b/der/src/length.rs
index 76ee0e997..ebe0a1cb9 100644
--- a/der/src/length.rs
+++ b/der/src/length.rs
@@ -286,6 +286,19 @@ impl fmt::Display for Length {
     }
 }
 
+// Implement by hand because the derive would create invalid values.
+// Generate a u32 with a valid range.
+#[cfg(feature = "arbitrary")]
+impl<'a> arbitrary::Arbitrary<'a> for Length {
+    fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
+        Ok(Self(u.int_in_range(0..=MAX_U32)?))
+    }
+
+    fn size_hint(depth: usize) -> (usize, Option<usize>) {
+        u32::size_hint(depth)
+    }
+}
+
 #[cfg(test)]
 mod tests {
     use super::Length;
diff --git a/der/src/tag.rs b/der/src/tag.rs
index 0e7bf2013..219d4e5f2 100644
--- a/der/src/tag.rs
+++ b/der/src/tag.rs
@@ -1,4 +1,5 @@
 //! ASN.1 tags.
+#![cfg_attr(feature = "arbitrary", allow(clippy::integer_arithmetic))]
 
 mod class;
 mod mode;
@@ -46,6 +47,7 @@ impl<T: FixedTag> Tagged for T {
 /// - Bits 8/7: [`Class`]
 /// - Bit 6: primitive (0) or constructed (1)
 /// - Bits 5-1: tag number
+#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
 #[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord)]
 #[non_exhaustive]
 pub enum Tag {
diff --git a/der/src/tag/number.rs b/der/src/tag/number.rs
index 1930e9436..6a7eaae22 100644
--- a/der/src/tag/number.rs
+++ b/der/src/tag/number.rs
@@ -186,3 +186,16 @@ impl fmt::Display for TagNumber {
         write!(f, "{}", self.0)
     }
 }
+
+// Implement by hand because the derive would create invalid values.
+// Use the constructor to create a valid value.
+#[cfg(feature = "arbitrary")]
+impl<'a> arbitrary::Arbitrary<'a> for TagNumber {
+    fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
+        Ok(Self::new(u.int_in_range(0..=Self::MAX)?))
+    }
+
+    fn size_hint(depth: usize) -> (usize, Option<usize>) {
+        u8::size_hint(depth)
+    }
+}
diff --git a/spki/Cargo.toml b/spki/Cargo.toml
index 9ac2bd62d..4d2cd8432 100644
--- a/spki/Cargo.toml
+++ b/spki/Cargo.toml
@@ -18,8 +18,9 @@ rust-version = "1.65"
 der = { version = "=0.7.0-pre", features = ["oid"], path = "../der" }
 
 # Optional dependencies
-sha2 = { version = "0.10", optional = true, default-features = false }
+arbitrary = { version = "1.2.0", features = ["derive"], optional = true }
 base64ct = { version = "1", path = "../base64ct", optional = true, default-features = false }
+sha2 = { version = "0.10", optional = true, default-features = false }
 
 [dev-dependencies]
 hex-literal = "0.3"
@@ -27,6 +28,7 @@ tempfile = "3"
 
 [features]
 alloc = ["base64ct?/alloc", "der/alloc"]
+arbitrary = ["std", "dep:arbitrary", "der/arbitrary"]
 fingerprint = ["sha2"]
 pem = ["alloc", "der/pem"]
 std = ["der/std", "alloc"]
diff --git a/spki/src/algorithm.rs b/spki/src/algorithm.rs
index 92a42746f..ed0f98a3f 100644
--- a/spki/src/algorithm.rs
+++ b/spki/src/algorithm.rs
@@ -19,6 +19,7 @@ use der::asn1::Any;
 /// ```
 ///
 /// [RFC 5280 Section 4.1.1.2]: https://tools.ietf.org/html/rfc5280#section-4.1.1.2
+#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
 #[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
 pub struct AlgorithmIdentifier<Params> {
     /// Algorithm OID, i.e. the `algorithm` field in the `AlgorithmIdentifier`
diff --git a/spki/src/spki.rs b/spki/src/spki.rs
index 3ad8aa9cd..bd122e5d1 100644
--- a/spki/src/spki.rs
+++ b/spki/src/spki.rs
@@ -38,6 +38,7 @@ pub type SubjectPublicKeyInfoOwned = SubjectPublicKeyInfo<Any, BitString>;
 /// ```
 ///
 /// [RFC 5280 § 4.1.2.7]: https://tools.ietf.org/html/rfc5280#section-4.1.2.7
+#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
 #[derive(Clone, Debug, Eq, PartialEq)]
 pub struct SubjectPublicKeyInfo<Params, Key> {
     /// X.509 [`AlgorithmIdentifier`] for the public key type
diff --git a/x509-cert/Cargo.toml b/x509-cert/Cargo.toml
index 83ac62943..b169a2296 100644
--- a/x509-cert/Cargo.toml
+++ b/x509-cert/Cargo.toml
@@ -15,6 +15,7 @@ edition = "2021"
 rust-version = "1.65"
 
 [dependencies]
+arbitrary = { version = "1.2.0", features = ["derive"], optional = true }
 const-oid = { version = "=0.10.0-pre", features = ["db"], path = "../const-oid" }
 der = { version = "=0.7.0-pre", features = ["derive", "alloc", "flagset"], path = "../der" }
 flagset = { version = "0.4.3" }
@@ -26,8 +27,9 @@ rstest = "0.16"
 
 [features]
 alloc = ["der/alloc"]
-std = ["der/std", "spki/std"]
+arbitrary = ["std", "dep:arbitrary", "const-oid/arbitrary", "der/arbitrary", "spki/arbitrary"]
 pem = ["alloc", "der/pem"]
+std = ["der/std", "spki/std"]
 
 [package.metadata.docs.rs]
 all-features = true
diff --git a/x509-cert/src/attr.rs b/x509-cert/src/attr.rs
index 20484ea00..4f7eaa77d 100644
--- a/x509-cert/src/attr.rs
+++ b/x509-cert/src/attr.rs
@@ -85,6 +85,7 @@ pub type Attributes = SetOfVec<Attribute>;
 /// ```
 ///
 /// [RFC 5280 Appendix A.1]: https://datatracker.ietf.org/doc/html/rfc5280#appendix-A.1
+#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
 #[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Sequence, ValueOrd)]
 #[allow(missing_docs)]
 pub struct AttributeTypeAndValue {
diff --git a/x509-cert/src/certificate.rs b/x509-cert/src/certificate.rs
index f58817a6d..62a84747f 100644
--- a/x509-cert/src/certificate.rs
+++ b/x509-cert/src/certificate.rs
@@ -20,6 +20,7 @@ use der::pem::PemLabel;
 /// ```
 ///
 /// [RFC 5280 Section 4.1]: https://datatracker.ietf.org/doc/html/rfc5280#section-4.1
+#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
 #[derive(Clone, Debug, Copy, PartialEq, Eq, Enumerated)]
 #[asn1(type = "INTEGER")]
 #[repr(u8)]
@@ -71,6 +72,7 @@ impl Default for Version {
 /// ```
 ///
 /// [RFC 5280 Section 4.1]: https://datatracker.ietf.org/doc/html/rfc5280#section-4.1
+#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
 #[derive(Clone, Debug, Eq, PartialEq, Sequence, ValueOrd)]
 #[allow(missing_docs)]
 pub struct TbsCertificate<'a> {
@@ -145,6 +147,7 @@ impl<'a> TbsCertificate<'a> {
 /// ```
 ///
 /// [RFC 5280 Section 4.1]: https://datatracker.ietf.org/doc/html/rfc5280#section-4.1
+#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
 #[derive(Clone, Debug, Eq, PartialEq, Sequence, ValueOrd)]
 #[allow(missing_docs)]
 pub struct Certificate<'a> {
diff --git a/x509-cert/src/ext.rs b/x509-cert/src/ext.rs
index eb6f6fbb8..5eaaf75d7 100644
--- a/x509-cert/src/ext.rs
+++ b/x509-cert/src/ext.rs
@@ -22,6 +22,7 @@ pub mod pkix;
 /// ```
 ///
 /// [RFC 5280 Section 4.1.2.9]: https://datatracker.ietf.org/doc/html/rfc5280#section-4.1.2.9
+#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
 #[derive(Clone, Debug, Eq, PartialEq, Sequence, ValueOrd)]
 #[allow(missing_docs)]
 pub struct Extension<'a> {
diff --git a/x509-cert/src/name.rs b/x509-cert/src/name.rs
index 35a8a0c6f..f3250bcfe 100644
--- a/x509-cert/src/name.rs
+++ b/x509-cert/src/name.rs
@@ -21,6 +21,7 @@ pub type Name = RdnSequence;
 /// ```
 ///
 /// [RFC 5280 Section 4.1.2.4]: https://datatracker.ietf.org/doc/html/rfc5280#section-4.1.2.4
+#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
 #[derive(Clone, Debug, Default, PartialEq, Eq)]
 pub struct RdnSequence(pub Vec<RelativeDistinguishedName>);
 
@@ -124,6 +125,7 @@ pub type DistinguishedName = RdnSequence;
 /// ```
 ///
 /// [RFC 5280 Section 4.1.2.4]: https://datatracker.ietf.org/doc/html/rfc5280#section-4.1.2.4
+#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
 #[derive(Clone, Debug, Default, PartialEq, Eq)]
 pub struct RelativeDistinguishedName(pub SetOfVec<AttributeTypeAndValue>);
 
diff --git a/x509-cert/src/serial_number.rs b/x509-cert/src/serial_number.rs
index 87120e308..0c46ee1cb 100644
--- a/x509-cert/src/serial_number.rs
+++ b/x509-cert/src/serial_number.rs
@@ -73,3 +73,18 @@ impl<'a> DecodeValue<'a> for SerialNumber {
 impl FixedTag for SerialNumber {
     const TAG: Tag = <Uint as FixedTag>::TAG;
 }
+
+// Implement by hand because the derive would create invalid values.
+// Use the constructor to create a valid value.
+#[cfg(feature = "arbitrary")]
+impl<'a> arbitrary::Arbitrary<'a> for SerialNumber {
+    fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
+        let len = u.int_in_range(0u32..=Self::MAX_LEN.into())?;
+
+        Self::new(u.bytes(len as usize)?).map_err(|_| arbitrary::Error::IncorrectFormat)
+    }
+
+    fn size_hint(depth: usize) -> (usize, Option<usize>) {
+        arbitrary::size_hint::and(u32::size_hint(depth), (0, None))
+    }
+}
diff --git a/x509-cert/src/time.rs b/x509-cert/src/time.rs
index d1f2ec5d5..c5c3bd15c 100644
--- a/x509-cert/src/time.rs
+++ b/x509-cert/src/time.rs
@@ -21,6 +21,7 @@ use std::time::SystemTime;
 ///
 /// [RFC 5280 Section 4.1.2.5]: https://tools.ietf.org/html/rfc5280#section-4.1.2.5
 /// [RFC 5280 Appendix A]: https://tools.ietf.org/html/rfc5280#page-117
+#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
 #[derive(Choice, Copy, Clone, Debug, Eq, PartialEq, ValueOrd)]
 pub enum Time {
     /// Legacy UTC time (has 2-digit year, valid only through 2050).
@@ -113,6 +114,7 @@ impl TryFrom<SystemTime> for Time {
 /// }
 /// ```
 /// [RFC 5280 Section 4.1.2.5]: https://datatracker.ietf.org/doc/html/rfc5280#section-4.1.2.5
+#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
 #[derive(Copy, Clone, Debug, Eq, PartialEq, Sequence, ValueOrd)]
 pub struct Validity {
     /// notBefore value

From 517adb7b82ee3774156a80c256d0b26e14246f74 Mon Sep 17 00:00:00 2001
From: Guillaume Dallenne <guillaume.dallenne@stormshield.eu>
Date: Tue, 20 Dec 2022 10:33:02 +0100
Subject: [PATCH 2/2] const-oid+der+spki+x509-cert: temporarily disable CI
 check until rust-fuzz/arbitrary#134 is fixed

---
 .github/workflows/const-oid.yml | 9 +++++----
 .github/workflows/der.yml       | 9 +++++----
 .github/workflows/spki.yml      | 9 +++++----
 .github/workflows/x509-cert.yml | 9 +++++----
 4 files changed, 20 insertions(+), 16 deletions(-)

diff --git a/.github/workflows/const-oid.yml b/.github/workflows/const-oid.yml
index e0b5979e4..769b8106d 100644
--- a/.github/workflows/const-oid.yml
+++ b/.github/workflows/const-oid.yml
@@ -39,10 +39,11 @@ jobs:
       - uses: RustCrypto/actions/cargo-hack-install@master
       - run: cargo hack build --target ${{ matrix.target }} --feature-powerset --exclude-features std,arbitrary
 
-  minimal-versions:
-    uses: RustCrypto/actions/.github/workflows/minimal-versions.yml@master
-    with:
-        working-directory: ${{ github.workflow }}
+# FIXME: Temporary disabled until https://github.com/rust-fuzz/arbitrary/issues/134 is fixed
+#  minimal-versions:
+#    uses: RustCrypto/actions/.github/workflows/minimal-versions.yml@master
+#    with:
+#        working-directory: ${{ github.workflow }}
 
   test:
     runs-on: ubuntu-latest
diff --git a/.github/workflows/der.yml b/.github/workflows/der.yml
index 3f3dfe10b..5fa9472d4 100644
--- a/.github/workflows/der.yml
+++ b/.github/workflows/der.yml
@@ -40,10 +40,11 @@ jobs:
       - uses: RustCrypto/actions/cargo-hack-install@master
       - run: cargo hack build --target ${{ matrix.target }} --feature-powerset --exclude-features std,arbitrary
 
-  minimal-versions:
-    uses: RustCrypto/actions/.github/workflows/minimal-versions.yml@master
-    with:
-        working-directory: ${{ github.workflow }}
+# FIXME: Temporary disabled until https://github.com/rust-fuzz/arbitrary/issues/134 is fixed
+#  minimal-versions:
+#    uses: RustCrypto/actions/.github/workflows/minimal-versions.yml@master
+#    with:
+#        working-directory: ${{ github.workflow }}
 
   test:
     strategy:
diff --git a/.github/workflows/spki.yml b/.github/workflows/spki.yml
index 39477d46b..0c135fa8f 100644
--- a/.github/workflows/spki.yml
+++ b/.github/workflows/spki.yml
@@ -42,10 +42,11 @@ jobs:
       - uses: RustCrypto/actions/cargo-hack-install@master
       - run: cargo hack build --target ${{ matrix.target }} --feature-powerset --exclude-features std,arbitrary
 
-  minimal-versions:
-    uses: RustCrypto/actions/.github/workflows/minimal-versions.yml@master
-    with:
-        working-directory: ${{ github.workflow }}
+# FIXME: Temporary disabled until https://github.com/rust-fuzz/arbitrary/issues/134 is fixed
+#  minimal-versions:
+#    uses: RustCrypto/actions/.github/workflows/minimal-versions.yml@master
+#    with:
+#        working-directory: ${{ github.workflow }}
 
   test:
     runs-on: ubuntu-latest
diff --git a/.github/workflows/x509-cert.yml b/.github/workflows/x509-cert.yml
index ef7784c52..a0e9d0816 100644
--- a/.github/workflows/x509-cert.yml
+++ b/.github/workflows/x509-cert.yml
@@ -42,10 +42,11 @@ jobs:
       - uses: RustCrypto/actions/cargo-hack-install@master
       - run: cargo hack build --target ${{ matrix.target }} --feature-powerset --exclude-features std,arbitrary
 
-  minimal-versions:
-    uses: RustCrypto/actions/.github/workflows/minimal-versions.yml@master
-    with:
-        working-directory: ${{ github.workflow }}
+# FIXME: Temporary disabled until https://github.com/rust-fuzz/arbitrary/issues/134 is fixed
+#  minimal-versions:
+#    uses: RustCrypto/actions/.github/workflows/minimal-versions.yml@master
+#    with:
+#        working-directory: ${{ github.workflow }}
 
   test:
     runs-on: ubuntu-latest
