<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Lee HeeJun</title>
    <description>The latest articles on DEV Community by Lee HeeJun (@jakkrow).</description>
    <link>https://dev.to/jakkrow</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3979832%2F6db8365f-4a36-4454-9c4b-c56311931833.png</url>
      <title>DEV Community: Lee HeeJun</title>
      <link>https://dev.to/jakkrow</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jakkrow"/>
    <language>en</language>
    <item>
      <title>Building RFC 3161 Layer 2 Verification for AI Decision Evidence</title>
      <dc:creator>Lee HeeJun</dc:creator>
      <pubDate>Thu, 11 Jun 2026 16:06:04 +0000</pubDate>
      <link>https://dev.to/jakkrow/building-rfc-3161-layer-2-verification-for-ai-decision-evidence-3986</link>
      <guid>https://dev.to/jakkrow/building-rfc-3161-layer-2-verification-for-ai-decision-evidence-3986</guid>
      <description>&lt;p&gt;Most developers think timestamping means sending a request to a Time Stamping Authority and storing the response.&lt;/p&gt;

&lt;p&gt;Something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;POST /tsa
Content-Type: application/timestamp-query

&amp;lt;binary timestamp request&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But that is not verification.&lt;/p&gt;

&lt;p&gt;That is only asking for a timestamp.&lt;/p&gt;

&lt;p&gt;If you want to use a timestamp as evidence, you need to verify what came back.&lt;/p&gt;

&lt;p&gt;And in RFC 3161, that means dealing with CMS SignedData.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem
&lt;/h2&gt;

&lt;p&gt;An RFC 3161 timestamp response is not just a date.&lt;/p&gt;

&lt;p&gt;It is a cryptographic structure.&lt;/p&gt;

&lt;p&gt;Inside the response, there is a timestamp token. That token is usually a CMS SignedData object containing TSTInfo, signer information, signed attributes, certificates, and a signature.&lt;/p&gt;

&lt;p&gt;If you want to treat the timestamp as evidence, you need to answer several questions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Does the timestamp token refer to the payload I actually submitted?&lt;/li&gt;
&lt;li&gt;Does the message imprint match my payload digest?&lt;/li&gt;
&lt;li&gt;Which certificate signed the timestamp token?&lt;/li&gt;
&lt;li&gt;Is the CMS signature valid?&lt;/li&gt;
&lt;li&gt;Was the signer certificate valid for timestamping?&lt;/li&gt;
&lt;li&gt;Can this verification be repeated later by another party?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Most basic integrations stop too early.&lt;/p&gt;

&lt;p&gt;They parse the response, extract the generated time, store the token, and assume that is enough.&lt;/p&gt;

&lt;p&gt;For a normal application timestamp, that may be acceptable.&lt;/p&gt;

&lt;p&gt;For an evidence system, it is not.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I am building
&lt;/h2&gt;

&lt;p&gt;I am building AURORA, a cryptographic evidence layer for AI decisions.&lt;/p&gt;

&lt;p&gt;The goal is not to decide whether an AI decision was fair, legal, or correct.&lt;/p&gt;

&lt;p&gt;The goal is narrower:&lt;/p&gt;

&lt;p&gt;preserve verifiable evidence of what was recorded.&lt;/p&gt;

&lt;p&gt;A sealed AI decision record can include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SHA-256 record hash&lt;/li&gt;
&lt;li&gt;RSA digital signature&lt;/li&gt;
&lt;li&gt;RFC 3161 timestamp token&lt;/li&gt;
&lt;li&gt;public verification URL&lt;/li&gt;
&lt;li&gt;downloadable evidence bundle&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The timestamp layer forced me to go deeper into RFC 3161 and CMS verification than I originally expected.&lt;/p&gt;

&lt;p&gt;The code snippets below are simplified for explanation, but the verification boundaries are the same.&lt;/p&gt;

&lt;h2&gt;
  
  
  Layer 1: Parsing the timestamp response
&lt;/h2&gt;

&lt;p&gt;The first layer is relatively straightforward.&lt;/p&gt;

&lt;p&gt;You parse the timestamp response, extract the timestamp token, and read the embedded TSTInfo structure.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;tsr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tsp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TimeStampResp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token_der&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;timestamp_token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tsr&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;time_stamp_token&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;signed_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;timestamp_token&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="n"&gt;tst_info&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tsp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TSTInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;signed_data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;encap_content_info&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;parsed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dump&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then you check the message imprint.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;message_imprint&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tst_info&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;message_imprint&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;hashed_message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;message_imprint&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;hashed_message&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;native&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;hashed_message&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;expected_digest&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Timestamp imprint does not match payload digest&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This confirms that the timestamp token refers to the digest you expected.&lt;/p&gt;

&lt;p&gt;That matters because the TSA does not timestamp your original JSON, PDF, or database record directly.&lt;/p&gt;

&lt;p&gt;It timestamps a digest.&lt;/p&gt;

&lt;p&gt;If the digest in TSTInfo does not match your payload digest, the token is not evidence for your record.&lt;/p&gt;

&lt;p&gt;But Layer 1 is still not enough.&lt;/p&gt;

&lt;p&gt;It proves that the token contains the expected imprint.&lt;/p&gt;

&lt;p&gt;It does not fully prove that the CMS signature is valid.&lt;/p&gt;

&lt;h2&gt;
  
  
  Layer 2: Verifying CMS SignedData
&lt;/h2&gt;

&lt;p&gt;The harder part is verifying the CMS SignedData structure itself.&lt;/p&gt;

&lt;p&gt;This is where the integration becomes less like an API call and more like applied cryptography.&lt;/p&gt;

&lt;p&gt;A timestamp token is a signed object.&lt;/p&gt;

&lt;p&gt;To verify it properly, you need to inspect the signer, the signed attributes, the encapsulated content, and the certificate usage.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Do not assume &lt;code&gt;certs[0]&lt;/code&gt; is the signer
&lt;/h2&gt;

&lt;p&gt;One easy mistake is assuming that the first certificate inside the CMS certificate set is the signer certificate.&lt;/p&gt;

&lt;p&gt;That is unsafe.&lt;/p&gt;

&lt;p&gt;CMS does not guarantee that &lt;code&gt;certs[0]&lt;/code&gt; is the signer.&lt;/p&gt;

&lt;p&gt;The signer must be selected using the SignerInfo identifier.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;select_signer_certificate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;certs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;signer_info&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;signer_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;signer_info&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sid&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;cert&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;certs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;cert_matches_signer_id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cert&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;signer_id&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;cert&lt;/span&gt;

    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Signer certificate not found&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The signer can be identified by issuer and serial number, or by subject key identifier.&lt;/p&gt;

&lt;p&gt;If you choose the wrong certificate, verification fails.&lt;/p&gt;

&lt;p&gt;Worse, you may build an implementation that appears to work only because one provider happens to order certificates in a convenient way.&lt;/p&gt;

&lt;p&gt;That is not a verification strategy.&lt;/p&gt;

&lt;p&gt;That is an accident.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Verify the &lt;code&gt;message-digest&lt;/code&gt; signed attribute
&lt;/h2&gt;

&lt;p&gt;CMS signed attributes usually include a &lt;code&gt;message-digest&lt;/code&gt; attribute.&lt;/p&gt;

&lt;p&gt;This digest must match the digest of the encapsulated TSTInfo content.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;expected_digest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;extract_message_digest_attr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;signed_attrs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;actual_digest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sha256&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tst_info_der&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;expected_digest&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;actual_digest&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CMS message-digest does not match TSTInfo&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This step is easy to misunderstand.&lt;/p&gt;

&lt;p&gt;The signature is not simply over your original payload.&lt;/p&gt;

&lt;p&gt;In this structure, the signature is over the signed attributes.&lt;/p&gt;

&lt;p&gt;Those signed attributes include a digest of the encapsulated content.&lt;/p&gt;

&lt;p&gt;So the chain is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;original payload
→ payload digest
→ TSTInfo message imprint
→ TSTInfo DER
→ CMS signedAttrs message-digest
→ CMS signature
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each boundary matters.&lt;/p&gt;

&lt;p&gt;If one layer is skipped, the evidence chain becomes weaker.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Handle the signed attributes encoding correctly
&lt;/h2&gt;

&lt;p&gt;This was one of the most annoying parts.&lt;/p&gt;

&lt;p&gt;CMS signedAttrs are encoded with an IMPLICIT context-specific tag on the wire.&lt;/p&gt;

&lt;p&gt;But for signature verification, the signed attributes need to be verified as a SET OF attributes.&lt;/p&gt;

&lt;p&gt;In simplified form:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;signed_attrs_for_verification&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\x31&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;signed_attrs_wire&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That small encoding detail can break verification for hours.&lt;/p&gt;

&lt;p&gt;The data may look logically correct.&lt;/p&gt;

&lt;p&gt;The parsed object may look correct.&lt;/p&gt;

&lt;p&gt;The certificate may be correct.&lt;/p&gt;

&lt;p&gt;The digest may be correct.&lt;/p&gt;

&lt;p&gt;But if the exact bytes passed into signature verification do not match the expected DER encoding, the signature fails.&lt;/p&gt;

&lt;p&gt;This is one of the places where “I parsed the object” and “I verified the evidence” become very different things.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Verify the CMS signature
&lt;/h2&gt;

&lt;p&gt;Once the signer certificate is selected and the signed attributes are encoded correctly, the signature can be verified using the signer certificate’s public key.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;public_key&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;verify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;signature_bytes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;signed_attrs_for_verification&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;PKCS1v15&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="n"&gt;hash_algorithm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If this succeeds, the CMS signature over the signed attributes is valid.&lt;/p&gt;

&lt;p&gt;If it fails, the timestamp token should not be treated as verified evidence.&lt;/p&gt;

&lt;p&gt;At this stage, you are no longer merely trusting that the TSA returned something that looks valid.&lt;/p&gt;

&lt;p&gt;You are checking the cryptographic structure yourself.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Check certificate usage
&lt;/h2&gt;

&lt;p&gt;A valid signature is still not the entire story.&lt;/p&gt;

&lt;p&gt;The certificate should also be valid for timestamping.&lt;/p&gt;

&lt;p&gt;For RFC 3161 timestamping, that usually means checking for the &lt;code&gt;id-kp-timeStamping&lt;/code&gt; extended key usage.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;eku&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;extensions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_extension_for_class&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;x509&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ExtendedKeyUsage&lt;/span&gt;
&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;ExtendedKeyUsageOID&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TIME_STAMPING&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;eku&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Certificate is not valid for timestamping&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Without this check, you may verify a signature from a certificate that was not intended to issue timestamp tokens.&lt;/p&gt;

&lt;p&gt;For an evidence layer, that distinction matters.&lt;/p&gt;

&lt;p&gt;The question is not only:&lt;/p&gt;

&lt;p&gt;“Was this signed?”&lt;/p&gt;

&lt;p&gt;The better question is:&lt;/p&gt;

&lt;p&gt;“Was this signed by the right kind of certificate, for the right kind of purpose, over the right content?”&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this matters for AI decision evidence
&lt;/h2&gt;

&lt;p&gt;AURORA is built around AI decision records.&lt;/p&gt;

&lt;p&gt;A record might describe a loan decision, an insurance claim decision, a hiring review, a moderation outcome, a risk score, or another system-generated decision event.&lt;/p&gt;

&lt;p&gt;For internal analytics, a normal database log may be enough.&lt;/p&gt;

&lt;p&gt;For audit evidence, it is weaker.&lt;/p&gt;

&lt;p&gt;A database timestamp says:&lt;/p&gt;

&lt;p&gt;“This is what the database currently says.”&lt;/p&gt;

&lt;p&gt;A cryptographic timestamp says:&lt;/p&gt;

&lt;p&gt;“This digest existed at this time, according to an external timestamp authority.”&lt;/p&gt;

&lt;p&gt;A verified cryptographic timestamp says:&lt;/p&gt;

&lt;p&gt;“We checked that the timestamp token, imprint, signature, signer certificate, and evidence chain are internally consistent.”&lt;/p&gt;

&lt;p&gt;That is a stronger evidence boundary.&lt;/p&gt;

&lt;h2&gt;
  
  
  What AURORA does not claim
&lt;/h2&gt;

&lt;p&gt;AURORA does not determine whether an AI decision was fair.&lt;/p&gt;

&lt;p&gt;It does not determine whether a decision was ethical.&lt;/p&gt;

&lt;p&gt;It does not replace legal review.&lt;/p&gt;

&lt;p&gt;It does not guarantee regulatory compliance.&lt;/p&gt;

&lt;p&gt;Those are separate questions.&lt;/p&gt;

&lt;p&gt;AURORA focuses on evidence preservation.&lt;/p&gt;

&lt;p&gt;It tries to answer a narrower but important question:&lt;/p&gt;

&lt;p&gt;“What exactly was recorded, and can that record be verified later?”&lt;/p&gt;

&lt;h2&gt;
  
  
  Key takeaways
&lt;/h2&gt;

&lt;p&gt;The main lessons from implementing this layer:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A timestamp response is not the same as a verified timestamp.&lt;/li&gt;
&lt;li&gt;RFC 3161 timestamp tokens contain CMS SignedData.&lt;/li&gt;
&lt;li&gt;The message imprint must match the payload digest.&lt;/li&gt;
&lt;li&gt;The CMS &lt;code&gt;message-digest&lt;/code&gt; signed attribute must match TSTInfo.&lt;/li&gt;
&lt;li&gt;The signer certificate should be selected by SignerInfo, not by array position.&lt;/li&gt;
&lt;li&gt;signedAttrs encoding details matter.&lt;/li&gt;
&lt;li&gt;The signer certificate should be valid for timestamping.&lt;/li&gt;
&lt;li&gt;Evidence systems should expose verification metadata, not hide it.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The result
&lt;/h2&gt;

&lt;p&gt;AURORA’s verification layer is designed to verify RFC 3161 timestamp evidence instead of blindly trusting that a TSA response is valid.&lt;/p&gt;

&lt;p&gt;That timestamp layer sits alongside:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SHA-256 record hashing&lt;/li&gt;
&lt;li&gt;RSA signatures&lt;/li&gt;
&lt;li&gt;public verification pages&lt;/li&gt;
&lt;li&gt;downloadable evidence bundles&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The broader goal is simple:&lt;/p&gt;

&lt;p&gt;AI decision records should be verifiable later.&lt;/p&gt;

&lt;p&gt;Not just logged.&lt;/p&gt;

&lt;p&gt;Verified.&lt;/p&gt;

&lt;p&gt;Project: &lt;a href="https://aurora-audit.com" rel="noopener noreferrer"&gt;https://aurora-audit.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Sample PDF: &lt;a href="https://aurora-audit.com/sample-audit.pdf" rel="noopener noreferrer"&gt;https://aurora-audit.com/sample-audit.pdf&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Have you implemented RFC 3161, CMS SignedData, or timestamp verification before?&lt;/p&gt;

&lt;p&gt;I would be interested in hearing what edge cases you ran into.&lt;/p&gt;

</description>
      <category>security</category>
      <category>python</category>
      <category>cryptography</category>
      <category>ai</category>
    </item>
  </channel>
</rss>
