<?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>Forem: Matt Nelson-White</title>
    <description>The latest articles on Forem by Matt Nelson-White (@mnelsonwhite).</description>
    <link>https://forem.com/mnelsonwhite</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%2F534875%2F66dbb307-b3ec-48e4-86ec-9a60472085d3.jpeg</url>
      <title>Forem: Matt Nelson-White</title>
      <link>https://forem.com/mnelsonwhite</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/mnelsonwhite"/>
    <language>en</language>
    <item>
      <title>Implementing Apple's Device Check App Attest Protocol</title>
      <dc:creator>Matt Nelson-White</dc:creator>
      <pubDate>Wed, 12 Jul 2023 13:15:01 +0000</pubDate>
      <link>https://forem.com/mnelsonwhite/implementing-apples-device-check-app-attest-protocol-4p2g</link>
      <guid>https://forem.com/mnelsonwhite/implementing-apples-device-check-app-attest-protocol-4p2g</guid>
      <description>&lt;p&gt;When creating a back-end for your App, you may want to secure your API so that you can ensure that the App cannot be used by agents other than legitimate APP installs running on legitimate devices. This lowers the API attack surface by limiting exposure to only legitimate APP installs.&lt;/p&gt;

&lt;p&gt;As developers, APIs are often protected by user authentication and authorisation; but in instances where there is no user authentication your API is left unprotected.&lt;/p&gt;

&lt;p&gt;Suggestions on many forums, that APIs can be protected by configuring your APP with a secret "API Key", are sub-optimal since it is essentially &lt;em&gt;"security by obfuscation"&lt;/em&gt; (the secret can be retrieved by extracting it from your APP package).&lt;/p&gt;

&lt;p&gt;The answer to both, "how can I secure my API from illegitimate clients? ", and "how should I protect unauthenticated clients?", is &lt;strong&gt;"App Attestation"&lt;/strong&gt;! 🎉🥳&lt;/p&gt;

&lt;p&gt;Apple and Android have solutions for App Attestation, but this article will be focused on Apple's &lt;a href="https://developer.apple.com/documentation/devicecheck/establishing_your_app_s_integrity" rel="noopener noreferrer"&gt;Device Check App Attest&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Basic Flow
&lt;/h2&gt;

&lt;p&gt;The figure taken from the App Attest "Establishing Your App's Integrity" document shows there are three high-level components involved in App Attest;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;your app,&lt;/li&gt;
&lt;li&gt;your server,&lt;/li&gt;
&lt;li&gt;and the app attest service.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There is a little more involved than that, and a little more that is useful to know about the high-level components.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgkdxhkwwp7g1osm9p6gi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgkdxhkwwp7g1osm9p6gi.png" title="High-Level App Attest Architecture" alt="High-Level App Attest Architecture" width="800" height="371"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Critically, the "App Attest" service, as it is shown in the figure above, is an internet-exposed service, which may fail or timeout from time to time.&lt;/p&gt;

&lt;p&gt;Furthermore, the &lt;a href="https://support.apple.com/en-au/guide/security/sec59b0b31ff/web" rel="noopener noreferrer"&gt;Secure Enclave&lt;/a&gt; on the device is used to prevent "Your App" from direct access to the private key generated for the use of App Attest.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmuijp0b0urbuzou7oo8z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmuijp0b0urbuzou7oo8z.png" title="Basic Protocol Flow" alt="Basic Protocol Flow" width="800" height="570"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Your server delivers a string of random bytes which represents a challenge to your app.&lt;/li&gt;
&lt;li&gt;Your app creates a cryptographic key-pair using the device check API. The key resides in the device's "Secure Enclave" and the operation responds with a reference to that public/private key pair with an identifier string (the key ID string is a SHA256 hash of the public key).&lt;/li&gt;
&lt;li&gt;A hash of the challenge data along with the key identifier is sent to the &lt;em&gt;Apple App Attest&lt;/em&gt; service over the internet. The service responds with the attestation data as a string of bytes.&lt;/li&gt;
&lt;li&gt;The attestation data is provided to your server, and the server validates the attestation data.&lt;/li&gt;
&lt;li&gt;Subsequence requests to your server are accompanied by assertion data which are generated on the device with the key identifier and the request body.&lt;/li&gt;
&lt;li&gt;The assertion data are then validated and the request is processed.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Let's break down how each of these things work. Then iterate on the process to try and take into account HTTP standards for authentication and security practices.&lt;/p&gt;

&lt;h3&gt;
  
  
  Warning: Check that App Attest is Available
&lt;/h3&gt;

&lt;p&gt;A "step zero" not mentioned above is that you should check your App's platform &lt;a href="https://developer.apple.com/documentation/devicecheck/establishing_your_app_s_integrity#3576028" rel="noopener noreferrer"&gt;supports the App Attest service&lt;/a&gt; before continuing the process.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;service&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;DCAppAttestService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shared&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isSupported&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Perform key generation and attestation.&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="c1"&gt;// Continue with server access.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The annoying fact about &lt;code&gt;DCAppAttestService&lt;/code&gt; is that it doesn't work on the simulator. 😣&lt;/p&gt;

&lt;p&gt;The documentation states it is available on the following platform versions:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;iOS&lt;/th&gt;
&lt;th&gt;iPadOS&lt;/th&gt;
&lt;th&gt;macOS&lt;/th&gt;
&lt;th&gt;tvOS&lt;/th&gt;
&lt;th&gt;watchOS&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;14.0+&lt;/td&gt;
&lt;td&gt;14.0+&lt;/td&gt;
&lt;td&gt;11.0+.&lt;/td&gt;
&lt;td&gt;15.0+&lt;/td&gt;
&lt;td&gt;9.0+&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Generating the Challenge (.NET)
&lt;/h3&gt;

&lt;p&gt;In response to some request from your app, your server responds with a string of random bytes, which are referred to as the "challenge".&lt;/p&gt;

&lt;p&gt;In cases of security and cryptography, random is just not just &lt;em&gt;any&lt;/em&gt; PRNG, it &lt;em&gt;should&lt;/em&gt; be cryptographically random so that the sequence cannot be easily anticipated by an attacker.&lt;/p&gt;

&lt;p&gt;In .NET, we can use the &lt;a href="https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.randomnumbergenerator?view=net-7.0" rel="noopener noreferrer"&gt;&lt;code&gt;RandomNumberGenerator&lt;/code&gt;&lt;/a&gt; found in the &lt;code&gt;System.Security.Cryptography&lt;/code&gt; namespace and we simply call &lt;code&gt;GetBytes(length)&lt;/code&gt; on the static instance of the class to get a cryptographically random sequence of bytes at the desired length.&lt;/p&gt;

&lt;h3&gt;
  
  
  Generating a Public/Private Key Pair and ID (Swift)
&lt;/h3&gt;

&lt;p&gt;The Apple docs give detail on this, you will want to persist the key identifier response in a local cache or storage so it may be used later in the process.&lt;/p&gt;

&lt;p&gt;The generated key pair and identifier are unique for each user account on each device running your app. So you only need to store one key identifier in your code.&lt;/p&gt;

&lt;p&gt;The basic code for generating the key pair is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;generateKey&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;keyId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
    &lt;span class="k"&gt;guard&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* Handle the error. */&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Cache keyId for subsequent operations.&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I prefer to use &lt;code&gt;async&lt;/code&gt;/&lt;code&gt;await&lt;/code&gt; rather than callbacks, so here it is refactored:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;getKeyId&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;throws&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;keyId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="n"&gt;_keyId&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&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;keyId&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;withCheckedThrowingContinuation&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;continuation&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
        &lt;span class="n"&gt;_service&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;generateKey&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;keyId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;keyIdValue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;keyId&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_keyId&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;keyIdValue&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="n"&gt;continuation&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resume&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;returning&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;keyIdValue&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="n"&gt;continuation&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resume&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;throwing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;AppAttestError&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;GetKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;continuation&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resume&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;throwing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;AppAttestError&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;GetKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&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;The above code makes use of an instance of &lt;code&gt;self._keyId&lt;/code&gt; to manage the reuse of a generated key pair. The instance is of a type that serves as an interface to the &lt;a href="https://developer.apple.com/documentation/security/keychain_services" rel="noopener noreferrer"&gt;device keychain secret storage&lt;/a&gt;, the implementation of that will be shown later.&lt;/p&gt;

&lt;p&gt;It also uses &lt;code&gt;AppAttestError&lt;/code&gt;, and again, I will share this code later.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Try and get the stored &lt;code&gt;keyId&lt;/code&gt; value and return early.&lt;/li&gt;
&lt;li&gt;Asynchronously generate the key pair and handle errors with either generation or persisting the key identifier&lt;/li&gt;
&lt;li&gt;Capture the key identifier response (or error) with the continuation.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Generate Attestation (Swift)
&lt;/h3&gt;

&lt;p&gt;After retrieving the challenge and obtaining the key identifier we should generate the attestation data. Keep in mind this operation is made against an Apple ran web service; from my experimentation, this service frequently times out or is temporarily unavailable, so make sure to consider this in your design.&lt;/p&gt;

&lt;p&gt;When generating the attestation data we need to make use of both the challenge we received earlier &lt;em&gt;and&lt;/em&gt; the key identifier.&lt;/p&gt;

&lt;p&gt;Apple's instructions for implementing this step are shown under &lt;a href="https://developer.apple.com/documentation/devicecheck/establishing_your_app_s_integrity#3561588" rel="noopener noreferrer"&gt;Certify the key pairs as valid&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;import&lt;/span&gt; &lt;span class="kt"&gt;CryptoKit&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;challenge&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt; &lt;span class="kt"&gt;Data&lt;/span&gt; &lt;span class="n"&gt;from&lt;/span&gt; &lt;span class="n"&gt;your&lt;/span&gt; &lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt; &lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;hash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;SHA256&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;challenge&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;attestKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;keyId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;clientDataHash&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;attestation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
    &lt;span class="k"&gt;guard&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* Handle error and return. */&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Send the attestation object to your server for verification.&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As before, here is a sensible implementation of this function using &lt;code&gt;async&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;generateAttestation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;keyId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;challenge&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;throws&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;Data&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;challengeHash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;SHA256&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;challenge&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;withCheckedThrowingContinuation&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;continuation&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
        &lt;span class="n"&gt;_service&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;attestKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;keyId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;clientDataHash&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;challengeHash&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;attestation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;attestationValue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;attestation&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;continuation&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resume&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;returning&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;attestationValue&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;dcError&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt; &lt;span class="k"&gt;as?&lt;/span&gt; &lt;span class="kt"&gt;DCError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dcError&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;invalidKey&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_keyId&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                    &lt;span class="n"&gt;continuation&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resume&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;throwing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;AppAttestError&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;GenerateAttestation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="n"&gt;continuation&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resume&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;throwing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;AppAttestError&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;GenerateAttestation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;continuation&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resume&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;throwing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;AppAttestError&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;GenerateAttestation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&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;The above code handles the case of the "invalid key" App Attest error. In this scenario, we should invalidate the local key identifier cache so that it will get recreated.&lt;/p&gt;

&lt;h3&gt;
  
  
  Validating Attestation Statement
&lt;/h3&gt;

&lt;p&gt;The steps to validating the attestation statement are outlined &lt;a href="https://developer.apple.com/documentation/devicecheck/validating_apps_that_connect_to_your_server#3576643" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This represents the bulk of the protocol implementation, there are lots of steps and lots of concepts to understand. Let's try and break it down to make it digestible.&lt;/p&gt;

&lt;h4&gt;
  
  
  High-Level Steps
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;Decode the attestation data&lt;/li&gt;
&lt;li&gt;Verify statement certificates&lt;/li&gt;
&lt;li&gt;Verify cryptographic nonce&lt;/li&gt;
&lt;li&gt;Verify key identifier against statement certificate&lt;/li&gt;
&lt;li&gt;Verify app identifier&lt;/li&gt;
&lt;li&gt;Verify the signing counter&lt;/li&gt;
&lt;li&gt;Verify development or production environment&lt;/li&gt;
&lt;li&gt;Verify key identifier against credential identifier&lt;/li&gt;
&lt;li&gt;Store attestation data for assertion data verification&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;... yikes 😅&lt;/p&gt;

&lt;p&gt;I don't expect all of that to make sense at this point, so please continue reading...&lt;/p&gt;

&lt;h4&gt;
  
  
  The Attestation Object Structure
&lt;/h4&gt;

&lt;p&gt;To decode the attestation statement we need to cover:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;concepts and structures detailed within the &lt;a href="https://www.w3.org/TR/webauthn/#sec-authenticator-data" rel="noopener noreferrer"&gt;Web Authentication&lt;/a&gt; specification&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://cbor.io" rel="noopener noreferrer"&gt;CBOR&lt;/a&gt; &amp;amp; &lt;a href="https://datatracker.ietf.org/doc/rfc8152/" rel="noopener noreferrer"&gt;COSE&lt;/a&gt; encoding&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.w3.org/Protocols/HTTP-NG/asn1.html" rel="noopener noreferrer"&gt;ASN.1&lt;/a&gt; encoding&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://en.wikipedia.org/wiki/Endianness" rel="noopener noreferrer"&gt;big/little endian&lt;/a&gt; encoding&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/dotnet/framework/interop/marshalling-classes-structures-and-unions" rel="noopener noreferrer"&gt;struct marshalling&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;... yikes 😅&lt;/p&gt;

&lt;p&gt;I think probably the best way to break down this problem is to show the hierarchy of types within the attestation data and then detail their encoding and semantics.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgkh4jblv9f2wrdg8wu83.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgkh4jblv9f2wrdg8wu83.png" alt="Image description" width="800" height="448"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;attestation&lt;/code&gt;: Represents the CBOR encoded map represented by the attestation data return from the App Attest service.

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;fmt&lt;/code&gt;: CBOR encoded string that identifies the statement format. For Apple's Device Check App Attest, it is always "apple-appattest".&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;attStmt&lt;/code&gt;: The attestation "statement", encoded as a CBOR map.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;x5c&lt;/code&gt;: CBOR encoded array of byte arrays, each byte array is encoded as an X509 certificate.

&lt;ul&gt;
&lt;li&gt;Index &lt;code&gt;0&lt;/code&gt;: The public certificate of the key pair generated by the app.&lt;/li&gt;
&lt;li&gt;Index &lt;code&gt;1&lt;/code&gt;: Intermediate signing certificate. The chain is represented by &lt;a href="https://www.apple.com/certificateauthority/private/" rel="noopener noreferrer"&gt;&lt;code&gt;root&lt;/code&gt;&lt;/a&gt; -&amp;gt; &lt;code&gt;intermediate&lt;/code&gt; -&amp;gt; &lt;code&gt;auth cert&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;
&lt;code&gt;receipt&lt;/code&gt;: CBOR encoded byte array can be sent to the App App Attest service to get information on the risk metric associated with the attestation.&lt;/li&gt;

&lt;li&gt;
&lt;code&gt;authData&lt;/code&gt;: Or &lt;a href="https://www.w3.org/TR/webauthn/#sctn-authenticator-data" rel="noopener noreferrer"&gt;"Authenticator Data"&lt;/a&gt; is defined within the web authentication spec. For the most part, data in this structure is fixed width, so we can marshal the bytes directly to a struct (mostly 😅).&lt;/li&gt;

&lt;li&gt;
&lt;code&gt;rpidHash&lt;/code&gt;: As the name suggests, this is a hash of the RPID, or &lt;a href="https://www.w3.org/TR/webauthn/#relying-party-identifier" rel="noopener noreferrer"&gt;relying party identifier&lt;/a&gt;. Within App Attest, the RP is always your app's &lt;a href="https://developer.apple.com/library/archive/documentation/General/Conceptual/DevPedia-CocoaCore/AppID.html" rel="noopener noreferrer"&gt;"App ID"&lt;/a&gt;.&lt;/li&gt;

&lt;li&gt;
&lt;code&gt;flags&lt;/code&gt;: bitflags to describe the auth data; does it include attest data? Is the user verified? Is there extensions on this auth data? Stuff like that...&lt;/li&gt;

&lt;li&gt;
&lt;code&gt;signCount&lt;/code&gt;: A counter for how many times the signature has signed content. When parsing the attestation statement, this figure is zero.&lt;/li&gt;

&lt;li&gt;
&lt;code&gt;attestedCredentialData&lt;/code&gt;: Contains the &lt;a href="https://www.w3.org/TR/webauthn/#sctn-attested-credential-data" rel="noopener noreferrer"&gt;credential data&lt;/a&gt; for the corresponding attestation. App Attest uses the key identifier as the credential identifier

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;aaguid&lt;/code&gt;: Determines if the app attest environment is development or production.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;credentialIdLength&lt;/code&gt;: A big-endian &lt;code&gt;UInt16&lt;/code&gt; value for the length &lt;code&gt;credentialId&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;credentialId&lt;/code&gt;: The attested credential identifier. For the purposes of app attest, the key identifier and the credential identifier are the same value.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;credentialPublicKey&lt;/code&gt;: &lt;a href="https://datatracker.ietf.org/doc/rfc8152/" rel="noopener noreferrer"&gt;COSE&lt;/a&gt; key, the encoding of which is a whole thing, but since the apple App Attest protocol does not make use of this field, I won't spend time going into it.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;/li&gt;

&lt;/ul&gt;

&lt;h4&gt;
  
  
  Decoding the Attestation Object
&lt;/h4&gt;

&lt;p&gt;The first level of decoding the attestation object required CBOR decoding. To decode a CBOR encoded object you can pull in a library like &lt;a href="https://github.com/dahomey-technologies/Dahomey.Cbor" rel="noopener noreferrer"&gt;Dahomey.Cbor&lt;/a&gt;, or write your own. Somewhere in the middle is using what Microsoft provides with &lt;a href="https://learn.microsoft.com/en-us/dotnet/api/system.formats.cbor.cborreader?view=dotnet-plat-ext-7.0" rel="noopener noreferrer"&gt;&lt;code&gt;System.Formats.Cbor.CborReader&lt;/code&gt;&lt;/a&gt;. You still need to do a lot of the lifting and write a deserialiser, but the class can help.&lt;/p&gt;

&lt;p&gt;To understand CBOR encoding better, take a look at &lt;a href="https://dev.to/mnelsonwhite/deserialising-cbor-encoded-data-in-net-5cgo"&gt;my article here&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>ios</category>
      <category>security</category>
      <category>swift</category>
      <category>csharp</category>
    </item>
    <item>
      <title>Understanding CBOR Encoded Data</title>
      <dc:creator>Matt Nelson-White</dc:creator>
      <pubDate>Tue, 24 Jan 2023 11:29:33 +0000</pubDate>
      <link>https://forem.com/mnelsonwhite/deserialising-cbor-encoded-data-in-net-5cgo</link>
      <guid>https://forem.com/mnelsonwhite/deserialising-cbor-encoded-data-in-net-5cgo</guid>
      <description>&lt;p&gt;&lt;a href="https://www.rfc-editor.org/rfc/rfc8949.html" rel="noopener noreferrer"&gt;CBOR&lt;/a&gt;, or &lt;em&gt;Concise Binary Object Representation&lt;/em&gt;, is an efficient way of serializing data objects, similar to what you see in the &lt;a href="https://developers.google.com/protocol-buffers/" rel="noopener noreferrer"&gt;protobuf&lt;/a&gt; standard.&lt;/p&gt;

&lt;p&gt;The reason I tried to understand this is because it was used throughout the &lt;a href="https://www.w3.org/TR/webauthn" rel="noopener noreferrer"&gt;web authentication&lt;/a&gt; security protocol.&lt;/p&gt;

&lt;p&gt;Why may it be of interest to you?&lt;br&gt;
Maybe you want to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Read dense and highly technical articles 🤷‍♀️.&lt;/li&gt;
&lt;li&gt;Understand a byte encoding standard which is becoming more relevant for web security.&lt;/li&gt;
&lt;li&gt;Write your own serialiser to avoid adding a dependency to some of the existing libraries which do it well.&lt;/li&gt;
&lt;li&gt;Amaze your friends and family!&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  The Encoding Rules
&lt;/h2&gt;

&lt;p&gt;CBOR encoding data is structured as a sequence of CBOR header and CBOR data pairs. The header part determines the format and the length of the data segment.&lt;/p&gt;

&lt;p&gt;Table 1 - Header Structure&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;tr&gt;
&lt;th colspan="2"&gt;1 Byte Header&lt;/th&gt;
&lt;th rowspan="2"&gt;Data&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;th&gt;Major Type&lt;/th&gt;
&lt;th&gt;Additional Info&lt;/th&gt;

&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3 Bits&lt;/td&gt;
&lt;td&gt;5 Bits&lt;/td&gt;
&lt;td&gt;n Bytes&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;All possible 3-bit values of the &lt;strong&gt;major type&lt;/strong&gt; are shown in the table below.&lt;/p&gt;

&lt;p&gt;Table 2 - Major Types&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Major Type&lt;/th&gt;
&lt;th&gt;Semantics&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;0x00&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Unsigned Integer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;0x01&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Negative Integer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;0x02&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Byte String&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;0x03&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Text String&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;0x04&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Array&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;0x05&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Map&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;0x06&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Tag&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;0x07&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Simple/Float&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;h3&gt;
  
  
  Unsigned Integer (MT 0)
&lt;/h3&gt;

&lt;p&gt;An unsigned integer may have a value which ranges from &lt;code&gt;0&lt;/code&gt; to &lt;code&gt;2&lt;sup&gt;64&lt;/sup&gt;-1&lt;/code&gt;, but the number of bytes used to express the integer are specified in the additional information part of the header.&lt;/p&gt;

&lt;p&gt;Table 3 - Integer size&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Add. Info.&lt;/th&gt;
&lt;th&gt;Semantics&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;0x18&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;8 bit&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;0x19&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;16 bit&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;0x1A&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;32 bit&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;0x1B&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;64 bit&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;So that an 8 bit unsigned integer has a header of&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(mt) 0b0000_0000 | (add. info.) 0b0001_1000 = (hdr) 0b0001_1000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And an unsigned integer value of &lt;code&gt;24&lt;/code&gt; including the header would read as &lt;code&gt;0b0001_1000_0001_1000&lt;/code&gt; or &lt;code&gt;0x1818&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;A cool trick is that numbers &lt;code&gt;0&lt;/code&gt; to &lt;code&gt;23&lt;/code&gt; can all be represented as a header byte only. This means that the integer value of &lt;code&gt;23&lt;/code&gt; can be represented as &lt;a href="https://www.cbor.me/?bytes=1817" rel="noopener noreferrer"&gt;&lt;code&gt;0x1817&lt;/code&gt;&lt;/a&gt; or just as &lt;a href="https://www.cbor.me/?bytes=17" rel="noopener noreferrer"&gt;&lt;code&gt;0x17&lt;/code&gt;&lt;/a&gt; (&lt;code&gt;0b0001_0111&lt;/code&gt;).&lt;/p&gt;

&lt;h3&gt;
  
  
  Negative Integer (MT 1)
&lt;/h3&gt;

&lt;p&gt;A negative integer may have a value which ranges from &lt;code&gt;2&lt;sup&gt;64&lt;/sup&gt;&lt;/code&gt; to &lt;code&gt;-1&lt;/code&gt;. Where &lt;code&gt;value = -1 - argument&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Negative integers also use the same additional information as unsigned integers as seen in table 3.&lt;/p&gt;

&lt;p&gt;So to represent &lt;code&gt;-500&lt;/code&gt;, the header of &lt;code&gt;0b0011_1001&lt;/code&gt; should be used, which means "negative integer with 16 bits"; and a value of &lt;code&gt;-1 - 500 = 499&lt;/code&gt; (&lt;code&gt;0b0000_0001_1111_0011&lt;/code&gt;); altogether &lt;code&gt;0b0011_1001_0000_0001_1111_0011&lt;/code&gt; or &lt;a href="https://www.cbor.me/?bytes=3901F3" rel="noopener noreferrer"&gt;&lt;code&gt;0x3901F3&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Just as before values of &lt;code&gt;0&lt;/code&gt; to &lt;code&gt;23&lt;/code&gt; can be represented just in the header byte, which means that it would represent negative integers of &lt;code&gt;-1&lt;/code&gt; to &lt;code&gt;-24&lt;/code&gt;, where &lt;code&gt;-24&lt;/code&gt; is &lt;a href="https://www.cbor.me/?bytes=37" rel="noopener noreferrer"&gt;&lt;code&gt;0x37&lt;/code&gt;&lt;/a&gt; (&lt;code&gt;0b0011_0111&lt;/code&gt;).&lt;/p&gt;

&lt;h3&gt;
  
  
  Byte String (MT 2)
&lt;/h3&gt;

&lt;p&gt;Similar to the rules we have established for both unsigned integers and negative integers, we can specify a byte string length just with the header value if the length is from &lt;code&gt;0&lt;/code&gt; to &lt;code&gt;23&lt;/code&gt;, and for longer strings, follow the additional information codes shown in table 3.&lt;/p&gt;

&lt;p&gt;Encoding the byte string &lt;code&gt;0x01020304&lt;/code&gt; results in &lt;a href="https://www.cbor.me/?bytes=4401020304" rel="noopener noreferrer"&gt;&lt;code&gt;0x4401020304&lt;/code&gt;&lt;/a&gt;. The header &lt;code&gt;0x44&lt;/code&gt; or &lt;code&gt;0b0100_0100&lt;/code&gt;, represents major type &lt;code&gt;2&lt;/code&gt; and a length of &lt;code&gt;4&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;A byte string of 500 octets would have the major type of &lt;code&gt;0x02&lt;/code&gt; followed by the additional information of &lt;code&gt;0x19&lt;/code&gt; (16 bits), &lt;code&gt;0x01F4&lt;/code&gt; to specify the length of 500, then 500 bytes for the value.&lt;/p&gt;

&lt;h3&gt;
  
  
  Text String (MT 3)
&lt;/h3&gt;

&lt;p&gt;A CBOR text string is always UTF-8 and the number of bytes in the string is given by the length argument formatted in the same way as a byte string.&lt;/p&gt;

&lt;p&gt;The UTF-8 string represented by the bytes &lt;code&gt;0x6C6F6FC9942073E1B4892073E1B489C9A5CA87&lt;/code&gt; has a length of 19. Since the length is less than 23, we can represent the length with only the header byte of &lt;code&gt;0b0111_0011&lt;/code&gt; or &lt;code&gt;0x73&lt;/code&gt;, encoding the string as &lt;a href="https://www.cbor.me/?bytes=736C6F6FC9942073E1B4892073E1B489C9A5CA87" rel="noopener noreferrer"&gt;&lt;code&gt;0x736C6F6FC9942073E1B4892073E1B489C9A5CA87&lt;/code&gt;&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;73                                      # text(19)
   6C6F6FC9942073E1B4892073E1B489C9A5CA87 # "looɔ sᴉ sᴉɥʇ"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Array (MT 4)
&lt;/h3&gt;

&lt;p&gt;Arrays have a length, and as expected the length is represented in the same way as all the other major types described so far. Furthermore, and array is more like a tuple, since it is not constrained to encapsulate a single type.&lt;/p&gt;

&lt;p&gt;An array of 2 elements would look like:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;0b100&lt;/code&gt; MT4 bits&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;0b00010&lt;/code&gt; Additional information for length of 2&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;0b0000_0001&lt;/code&gt; Integer value of 1&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;0b0001_1001_0000_0001_1111_0100&lt;/code&gt; Integer value of 500&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The encoded result is &lt;a href="https://www.cbor.me/?bytes=82011901F4" rel="noopener noreferrer"&gt;&lt;code&gt;0x82011901F4&lt;/code&gt;&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;82         # array(2)
   01      # unsigned(1)
   19 01F4 # unsigned(500)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Map (MT 5)
&lt;/h3&gt;

&lt;p&gt;A map (also known as a dictionary) is similar to an array which is read as a sequence of key/value pairs; if we used the array specified above as a map, it would read the first array element as the first key and the second element as the first value. This means that a valid map type always has an even number of elements.&lt;/p&gt;

&lt;p&gt;Since the map's keys are used to lookup the corresponding value, the map should not have any duplicate keys.&lt;/p&gt;

&lt;p&gt;Using the example array above, and changing the major type to &lt;code&gt;5&lt;/code&gt; the encoded result is &lt;a href="https://www.cbor.me/?bytes=A1011901F4" rel="noopener noreferrer"&gt;&lt;code&gt;0xA1011901F4&lt;/code&gt;&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;A1         # map(1)
   01      # unsigned(1)
   19 01F4 # unsigned(500)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Tag (MT 6)
&lt;/h3&gt;

&lt;p&gt;A tag operates as an extension point for an extended number of types (see &lt;a href="https://www.iana.org/assignments/cbor-tags/cbor-tags.xhtml" rel="noopener noreferrer"&gt;IANA CBOR Tags&lt;/a&gt; for full list) where we can specify data structures that represent datetime, big numbers, URLs and much much more. The way it works is that the tag encodes a number which then describes a well known tag data type.&lt;/p&gt;

&lt;p&gt;For example, the tag value of &lt;code&gt;0&lt;/code&gt; corresponds to a &lt;a href="https://www.rfc-editor.org/rfc/rfc3339" rel="noopener noreferrer"&gt;RFC3339&lt;/a&gt; datetime.&lt;br&gt;
Representing the RFC3339 datetime "1985-04-12T23:20:50.52Z" we can first start with the encoded string&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;77                                      # text(23)
   313938352D30342D31325432333A32303A35302E35325A # "1985-04-12T23:20:50.52Z"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and then prefix the encoding with the tag header &lt;code&gt;0b110&lt;/code&gt; (tag) &lt;code&gt;0b00000&lt;/code&gt; (0 value = RFC3339 datetime), or &lt;code&gt;0xC0&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;C0                                      # tag(0)
   77                                   # text(23)
      313938352D30342D31325432333A32303A35302E35325A # "1985-04-12T23:20:50.52Z"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The decoder then knows the following string should be interpreted as a RFC3339 datetime.&lt;/p&gt;

&lt;h3&gt;
  
  
  Simple/Float (MT 7)
&lt;/h3&gt;

&lt;p&gt;A major type &lt;code&gt;7&lt;/code&gt; is either a floating point number or a simple value depending on the additional information part of the header.&lt;/p&gt;

&lt;p&gt;Table 4 - Simple/Float Additional Information&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Add. Info.&lt;/th&gt;
&lt;th&gt;Semantics&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;0x14&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;false&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;0x15&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;true&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;0x16&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;null&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;0x17&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;undefined&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;0x19&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://ieeexplore.ieee.org/document/8766229" rel="noopener noreferrer"&gt;IEEE 754&lt;/a&gt; Half-Precision Float (16 bits follow)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;0x1A&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://ieeexplore.ieee.org/document/8766229" rel="noopener noreferrer"&gt;IEEE 754&lt;/a&gt; Single-Precision Float (32 bits follow)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;0x1B&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://ieeexplore.ieee.org/document/8766229" rel="noopener noreferrer"&gt;IEEE 754&lt;/a&gt; Double-Precision Float (64 bits follow)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;0x1F&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;"break" stop code for indefinite-length items&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A value of &lt;code&gt;3.14159&lt;/code&gt; can be expressed as a 64 bit float as &lt;a href="https://www.cbor.me/?bytes=FB400921F9F01B866E" rel="noopener noreferrer"&gt;&lt;code&gt;0xFB400921F9F01B866E&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Indefinite Length Strings, Arrays &amp;amp; Maps
&lt;/h3&gt;

&lt;p&gt;Byte strings, along with text strings, arrays and maps have another way of defining length. They can use the additional information code &lt;code&gt;0x1F&lt;/code&gt; which signifies that the sequence has an "indefinite length".  This is used for scenarios where the length is unknown, like in streaming scenarios.&lt;/p&gt;

&lt;p&gt;The termination of a indefinite length sequence is the "break" or "stop" code, which is major type &lt;code&gt;7&lt;/code&gt; with a additional information code of &lt;code&gt;0x1F&lt;/code&gt;, together the header is &lt;code&gt;0xFF&lt;/code&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Strings
&lt;/h4&gt;

&lt;p&gt;For byte and text strings of an indefinite length, the data arrives in chunks. Each chunk has a specified length. The sequence of chunks are terminated by the break code &lt;code&gt;0xFF&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The CBOR encoded data &lt;a href="https://www.cbor.me/?bytes=5F440102030443010203FF" rel="noopener noreferrer"&gt;&lt;code&gt;0x5F440102030443010203FF&lt;/code&gt;&lt;/a&gt; describes an indefinite length byte string with two chunks, respectively of length 4 and 3 bytes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;5F             # header for the indefinite length byte string
   44          # the header for a byte string of length 4
      01020304 # the first chunk of 4 bytes
   43          # the header for a byte string of length 3
      010203   # the second chunk of 3 bytes
   FF          # the indefinite length byte string is terminated with the "break" code
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Arrays &amp;amp; Maps
&lt;/h4&gt;

&lt;p&gt;The CBOR encoded data &lt;a href="https://www.cbor.me/?bytes=9F00203040506070809FF" rel="noopener noreferrer"&gt;&lt;code&gt;0x9F00203040506070809FF&lt;/code&gt;&lt;/a&gt; describes an indefinite length array of unsigned integers.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;9F    # header for the indefinite length array
   00 # unsigned(0)
   01 # unsigned(1)
   02 # unsigned(2)
   03 # unsigned(3)
   04 # unsigned(4)
   05 # unsigned(5)
   06 # unsigned(6)
   07 # unsigned(7)
   08 # unsigned(8)
   09 # unsigned(9)
   0A # unsigned(10)
   FF # the indefinite length array is terminated
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These arrays can be nested with definite length arrays as well. The CBOR data &lt;a href="https://www.cbor.me/?bytes=9F018202039F0405FFFF" rel="noopener noreferrer"&gt;&lt;code&gt;0x9F018202039F0405FFFF&lt;/code&gt;&lt;/a&gt; is an indefinite length array that is comprised of:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;an element&lt;/li&gt;
&lt;li&gt;a definite length array of 2 elements&lt;/li&gt;
&lt;li&gt;an indefinite length array of 3 elements
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;9F       # array(*)
   01    # unsigned(1)
   82    # array(2)
      02 # unsigned(2)
      03 # unsigned(3)
   9F    # array(*)
      04 # unsigned(4)
      05 # unsigned(5)
      FF # primitive(*)
   FF    # primitive(*)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;There are some interesting tricks used in the standard to really make the encoding economical. There are pros and cons using CBOR when compared to other standards like protobuf.&lt;/p&gt;

&lt;p&gt;With protobuf you need to distribute definition files in order to decode the data, which leads to much more efficiently encoded class structures (map keys are often strings). You &lt;em&gt;can&lt;/em&gt; encode your class structures with enums to represent the class properties, and then distribute the enum definitions. So from that point of view, CBOR can be pretty damn close to protobuf but a hell of a lot more readable (once you understand the standard).&lt;/p&gt;

&lt;p&gt;If you are interested, I wrote a &lt;a href="https://github.com/mnelsonwhite/DeviceCheck.AppAttest/blob/main/dotnet/DeviceCheck.AppAttest.Cbor/Cbor.cs" rel="noopener noreferrer"&gt;simple deserialiser in C#&lt;/a&gt;. If you would like to know more about the explanation feel free to reach out.&lt;/p&gt;

&lt;p&gt;CBOR is also extended to include a way of signing and encrypting CBOR encoded objects, called &lt;a href="https://www.rfc-editor.org/rfc/rfc9338" rel="noopener noreferrer"&gt;COSE&lt;/a&gt; or &lt;em&gt;CBOR Object Signing &amp;amp; Encryption&lt;/em&gt; which is used by web authentication protocols like &lt;a href="https://developer.apple.com/documentation/devicecheck/establishing_your_app_s_integrity" rel="noopener noreferrer"&gt;App-Attest&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Check out &lt;a href="https://www.rfc-editor.org/rfc/rfc8610" rel="noopener noreferrer"&gt;CDDL&lt;/a&gt; (Concise Data Definition Language) as a way to define your CBOR structures.&lt;/p&gt;

</description>
      <category>watercooler</category>
    </item>
    <item>
      <title>How To Manually Verify x509 Certificate Chains in .NET</title>
      <dc:creator>Matt Nelson-White</dc:creator>
      <pubDate>Tue, 10 Jan 2023 11:38:58 +0000</pubDate>
      <link>https://forem.com/mnelsonwhite/how-to-verify-x509-certificate-chains-2oj7</link>
      <guid>https://forem.com/mnelsonwhite/how-to-verify-x509-certificate-chains-2oj7</guid>
      <description>&lt;p&gt;I am frequently in a situation where I rely on using certificates to secure my applications, but the methods vary quite a bit. One thing is clear, I prefer to provide root or intermediate certificates for validation purposes, to the runtime by configuration and not using the system certificate store.&lt;/p&gt;

&lt;p&gt;The .NET framework has a &lt;code&gt;X509Chain&lt;/code&gt; class where a x509 certificate chain can verify a certificate. However, the problem with this API is that it uses the system's root certificate store to validate the certificate. In scenarios where the root certificate is only held by the application and is not present in the system's root store, this API does not give reliable results.&lt;/p&gt;

&lt;p&gt;Looking around online there really doesn't appear to be any solution to this other than "use &lt;a href="https://www.bouncycastle.org/csharp/index.html" rel="noopener noreferrer"&gt;bouncycastle&lt;/a&gt;", but for those who don't really want to introduce such a large project dependency to validate a certificate chain then that solution may not be appetising.&lt;/p&gt;

&lt;h2&gt;
  
  
  X509 Certificate Structure
&lt;/h2&gt;

&lt;p&gt;In order to understand how to validate a certificate chain, we need to understand how a X509 certificate is structured and encoded.&lt;/p&gt;

&lt;p&gt;According to &lt;a href="https://www.rfc-editor.org/rfc/rfc3280#section-4.1" rel="noopener noreferrer"&gt;RFC 3280 Section 4.1&lt;/a&gt;, the certificate is a &lt;a href="https://www.itu.int/ITU-T/studygroups/com17/languages/X.691-0207.pdf" rel="noopener noreferrer"&gt;ASN.1 encoded&lt;/a&gt; structure, and at it's base level is comprised of only 3 elements.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Certificate  ::=  SEQUENCE  {
    tbsCertificate       TBSCertificate,
    signatureAlgorithm   AlgorithmIdentifier,
    signatureValue       BIT STRING
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;tbsCertificate&lt;/code&gt;: This is the "&lt;strong&gt;To Be Signed&lt;/strong&gt;" certificate structure which is signed by the the signing certificate. The RFC describes this as:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The field contains the names of the subject and issuer, a public key associated with the subject, a validity period, and other associated information.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So pretty much all the certificate extensions and information.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;signatureAlgorithm&lt;/code&gt;: This is an identifier for the algorithm used to sign the &lt;strong&gt;tbsCertificate&lt;/strong&gt;; this information is used to know which algorithm to validate the certificate signature, using the signing certificate's public key.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;signatureValue&lt;/code&gt;: This is a sequence of bytes which represents the cryptographic proof that the certificate has in fact been signed by some other signing certificate.&lt;/p&gt;

&lt;p&gt;Broadly speaking, to validate the certificate we provide the algorithm identified by the &lt;code&gt;signatureAlgorithm&lt;/code&gt; along with the &lt;code&gt;tbsCertificate&lt;/code&gt; and the &lt;code&gt;signatureValue&lt;/code&gt; and the validation function returns a bool to indicate whether the certificate was signed by the chosen signing certificate.&lt;/p&gt;

&lt;h2&gt;
  
  
  Decoding the Certificate
&lt;/h2&gt;

&lt;p&gt;The certificate data is ASN.1 encoded; but what is ASN.1 encoding? It is essentially a standard for "&lt;em&gt;defining data structures that can be serialised and deserialised in a cross-platform way&lt;/em&gt;" (&lt;a href="https://en.wikipedia.org/wiki/ASN.1" rel="noopener noreferrer"&gt;ASN.1&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;The standard includes different standards of encoding rules, such as &lt;a href="https://en.wikipedia.org/wiki/Basic_Encoding_Rules" rel="noopener noreferrer"&gt;BER&lt;/a&gt;, &lt;a href="https://en.wikipedia.org/wiki/Distinguished_Encoding_Rules" rel="noopener noreferrer"&gt;DER&lt;/a&gt;, &lt;a href="https://en.wikipedia.org/wiki/Canonical_Encoding_Rules" rel="noopener noreferrer"&gt;CER&lt;/a&gt;, and many others.&lt;/p&gt;

&lt;p&gt;For the purposes of decoding a X509 certificate, we only need knowledge of BER and DER (a restricted subset of BER)&lt;/p&gt;

&lt;p&gt;The way BER ASN.1 encoding works is the first set of bytes in the data indicates the &lt;strong&gt;tag&lt;/strong&gt;, which is a ASN.1 term to describe the type of data that is encoded.&lt;/p&gt;

&lt;p&gt;The next bytes are the data length, then the data itself. For example:&lt;/p&gt;

&lt;p&gt;For example, the bytes &lt;code&gt;0304FFA0CB01&lt;/code&gt; can be deconstructed to:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;tag&lt;/th&gt;
&lt;th&gt;length&lt;/th&gt;
&lt;th&gt;value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;03&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;04&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;FFA0CB01&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The tag &lt;code&gt;03&lt;/code&gt; corresponds to the &lt;strong&gt;OCTET STRING&lt;/strong&gt; type. So the deserialiser knows to read &lt;code&gt;4&lt;/code&gt; bytes into an octet string of &lt;code&gt;FFA0CB01&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Looking at the ASN.1 definition for the certificate we can work out the basic structure.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Certificate  ::=  SEQUENCE  {
    tbsCertificate       TBSCertificate,
    signatureAlgorithm   AlgorithmIdentifier,
    signatureValue       BIT STRING
}

TBSCertificate  ::=  SEQUENCE  {
    version         [0]  EXPLICIT Version DEFAULT v1,
    serialNumber         CertificateSerialNumber,
    signature            AlgorithmIdentifier,
    issuer               Name,
    validity             Validity,
    subject              Name,
    subjectPublicKeyInfo SubjectPublicKeyInfo,
    issuerUniqueID  [1]  IMPLICIT UniqueIdentifier OPTIONAL,
    subjectUniqueID [2]  IMPLICIT UniqueIdentifier OPTIONAL,
    extensions      [3]  EXPLICIT Extensions OPTIONAL
}

AlgorithmIdentifier  ::=  SEQUENCE  {
    algorithm            OBJECT IDENTIFIER,
    parameters           ANY DEFINED BY algorithm OPTIONAL
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;The data is nested inside a &lt;strong&gt;SEQUENCE&lt;/strong&gt; type (tag &lt;code&gt;0x10&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;In order to validate the certificate, we don't need to decode the &lt;code&gt;tbsCertificate&lt;/code&gt;, we just need to extract the bytes from the sequence.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;signatureAlgorithm&lt;/code&gt; is another sequence from which we want to extract the &lt;code&gt;algorithm&lt;/code&gt; OID to know which algorithm to use.&lt;/li&gt;
&lt;li&gt;Finally the &lt;code&gt;signatureValue&lt;/code&gt; should not even be in a sequence and is just a BIT STRING (tag &lt;code&gt;0x03&lt;/code&gt;).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Fortunately .NET has &lt;a href="https://learn.microsoft.com/en-us/dotnet/api/system.formats.asn1.asndecoder?view=net-7.0" rel="noopener noreferrer"&gt;AsnDecoder&lt;/a&gt; which can help with decoding ASN.1 structures.&lt;/p&gt;

&lt;h3&gt;
  
  
  Decoding the TBS Certificate
&lt;/h3&gt;

&lt;p&gt;Looking at the methods and properties of the &lt;code&gt;X509Certificate2&lt;/code&gt; type, it does not contain any way to extract the TBS Certificate from it, but we can use what we know to decode the certificate using the &lt;code&gt;AsnDecoder&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;To decode the TBS Certificate from the X509 Certificate, we can write an extension method.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;ReadOnlySpan&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;GetTbsCertificate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt; &lt;span class="n"&gt;X509Certificate2&lt;/span&gt; &lt;span class="n"&gt;certificate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;AsnEncodingRules&lt;/span&gt; &lt;span class="n"&gt;encodingRules&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;AsnEncodingRules&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BER&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;signedData&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;certificate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RawDataMemory&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;AsnDecoder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReadSequence&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;signedData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Span&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;encodingRules&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;length&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;certificateSpan&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;signedData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Span&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;length&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;AsnDecoder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReadSequence&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;certificateSpan&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;encodingRules&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;tbsOffset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;tbsLength&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// include ASN1 4 byte preamble offset to get WHOLE TBS Cert&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;certificateSpan&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tbsOffset&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tbsLength&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="m"&gt;4&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;ol&gt;
&lt;li&gt;The method starts by getting the raw data of the certificate from the &lt;code&gt;X509Certificate2&lt;/code&gt; type.&lt;/li&gt;
&lt;li&gt;From the ASN.1 definitions shown above we know that the certificate data is within an ASN.1 sequence type, &lt;code&gt;AsnDecoder.ReadSequence&lt;/code&gt; is used to find the offset and length of that sequence.&lt;/li&gt;
&lt;li&gt;Because we are good people, we use a span to reduce allocations.&lt;/li&gt;
&lt;li&gt;Now we can start decoding the elements of the structure, the TBS certificate is the first of the ranks. It can be read as a sequence.&lt;/li&gt;
&lt;li&gt;Even though we know the TBS Certificate offset and length, this is not exactly the data we need. After a lot of hair pulling, I discovered the signature is actually based on the data &lt;strong&gt;INCLUDING&lt;/strong&gt; the ASN.1 SEQUENCE preamble. Not including this will give you the wrong data! 😣.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Decoding the Signature Algorithm
&lt;/h3&gt;

&lt;p&gt;In Microsoft's infinite grace, they have already included &lt;a href="https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.x509certificates.x509certificate2.signaturealgorithm?view=net-7.0" rel="noopener noreferrer"&gt;a property&lt;/a&gt; on the &lt;code&gt;X509Certificfate2&lt;/code&gt; type to obtain the signature algorithm.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;SignatureAlgorithm&lt;/code&gt; property returns the OID for the algorithm. Microsoft has &lt;a href="https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-gpnap/a48b02b2-2a10-4eb0-bed4-1807a6d2f5ad" rel="noopener noreferrer"&gt;a table&lt;/a&gt; of signature algorithm OIDs and their corresponding algorithms; we can write our code to accommodate the algorithms we want.&lt;/p&gt;

&lt;p&gt;If you are writing your own library you probably want to support as many as possible, but for the purposes of the article, we only need to support some common variants. The OIDs are often organised hierarchically by encryption algorithm and hashing algorithm; for example, RSA has a prefix of &lt;code&gt;1.2.840.113549.1.1.&lt;/code&gt; and SHA256, SHA384 and SHA512 OIDs are &lt;code&gt;1.2.840.113549.1.1.11&lt;/code&gt;, &lt;code&gt;1.2.840.113549.1.1.12&lt;/code&gt; and &lt;code&gt;1.2.840.113549.1.1.13&lt;/code&gt; respectively.&lt;/p&gt;

&lt;h3&gt;
  
  
  Decoding the Signature
&lt;/h3&gt;

&lt;p&gt;Unfortunately the &lt;code&gt;X509Certificate2&lt;/code&gt; type does not help us extract the signature data, but knowing what we already know it should be pretty trivial. The only annoyance is that we need to know the length of the TBS certificate and the signature algorithm to get the offset to read the signature.&lt;/p&gt;

&lt;p&gt;We can write a separate method to extract the signature, but it needs to read through the other elements.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="nf"&gt;GetSignature&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt; &lt;span class="n"&gt;X509Certificate2&lt;/span&gt; &lt;span class="n"&gt;certificate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;AsnEncodingRules&lt;/span&gt; &lt;span class="n"&gt;encodingRules&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;AsnEncodingRules&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BER&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;signedData&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;certificate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RawDataMemory&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;AsnDecoder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReadSequence&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;signedData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Span&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;encodingRules&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;length&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;certificateSpan&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;signedData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Span&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;length&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;AsnDecoder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReadSequence&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;certificateSpan&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;encodingRules&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;tbsOffset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;tbsLength&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;offsetSpan&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;certificateSpan&lt;/span&gt;&lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="n"&gt;tbsOffset&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="n"&gt;tbsLength&lt;/span&gt;&lt;span class="p"&gt;)..];&lt;/span&gt;
    &lt;span class="n"&gt;AsnDecoder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReadSequence&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;offsetSpan&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;encodingRules&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;algOffset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;algLength&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="n"&gt;_&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;AsnDecoder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReadBitString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;offsetSpan&lt;/span&gt;&lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="n"&gt;algOffset&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="n"&gt;algLength&lt;/span&gt;&lt;span class="p"&gt;)..],&lt;/span&gt;
        &lt;span class="n"&gt;encodingRules&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="n"&gt;_&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;h2&gt;
  
  
  Verifying the Certificate Chain
&lt;/h2&gt;

&lt;p&gt;Now that we have extracted all three elements of the X509 certificate necessary to verify signing by a signing certificate. We can write another extension to determine if a target signing certificate (&lt;code&gt;signedBy&lt;/code&gt;) has signed a particular certificate (&lt;code&gt;signed&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;The overall approach is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Using the signature algorithm OID determine which encryption algorithm is being used (we will write for RSA and ECDsa) and use that information to know what type of public key to extract from the &lt;code&gt;signedBy&lt;/code&gt; certificate.&lt;/li&gt;
&lt;li&gt;Using the &lt;code&gt;VerifyData&lt;/code&gt; method on the public key, we provide the TBS certificate, the signature data, and the hash algorithm, which is obtained from the signature algorithm OID as well.&lt;/li&gt;
&lt;li&gt;We will also need to identify the type of signature padding for RSA, and the DSA signature format for ECDsa. We won't go into determining that from the certificate data, instead we will define them statically.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="nf"&gt;IsSignedBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt; &lt;span class="n"&gt;X509Certificate2&lt;/span&gt; &lt;span class="n"&gt;signed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;X509Certificate2&lt;/span&gt; &lt;span class="n"&gt;signer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;signature&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;signed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetSignature&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;tbs&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;signed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetTbsCertificate&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;alg&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;signed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SignatureAlgorithm&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;alg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;StartsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"1.2.840.113549.1.1."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="k"&gt;true&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;signer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetRSAPublicKey&lt;/span&gt;&lt;span class="p"&gt;()?.&lt;/span&gt;&lt;span class="nf"&gt;VerifyData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;tbs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;signature&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="k"&gt;value&lt;/span&gt; &lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="s"&gt;"1.2.840.113549.1.1.11"&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;HashAlgorithmName&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SHA256&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="s"&gt;"1.2.840.113549.1.1.12"&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;HashAlgorithmName&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SHA384&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="s"&gt;"1.2.840.113549.1.1.13"&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;HashAlgorithmName&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SHA512&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;UnsupportedAlgorithm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;alg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;},&lt;/span&gt;
                &lt;span class="n"&gt;RSASignaturePadding&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Pkcs1&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;StartsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"1.2.840.10045."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="k"&gt;true&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;signer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetECDsaPublicKey&lt;/span&gt;&lt;span class="p"&gt;()?.&lt;/span&gt;&lt;span class="nf"&gt;VerifyData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;tbs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;signature&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="k"&gt;value&lt;/span&gt; &lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="s"&gt;"1.2.840.10045.4.3.2"&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;HashAlgorithmName&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SHA256&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="s"&gt;"1.2.840.10045.4.3.3"&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;HashAlgorithmName&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SHA384&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="s"&gt;"1.2.840.10045.4.3.4"&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;HashAlgorithmName&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SHA512&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;UnsupportedAlgorithm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;alg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;},&lt;/span&gt;
                &lt;span class="n"&gt;DSASignatureFormat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Rfc3279DerSequence&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;UnsupportedAlgorithm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;alg&lt;/span&gt;&lt;span class="p"&gt;);&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;The one thing in this code that took WAY too long to work out was that the ECDsa Verify method has an overload for the &lt;code&gt;DSASignatureFormat&lt;/code&gt; parameter. I have NO IDEA why it defaults to a non-DER format, but it does, and does not work unless you discover this overload.&lt;/p&gt;

&lt;p&gt;The above approach can be expanded and abstracted to support more algorithms, but as is it supports the most common algorithms with very little code.&lt;/p&gt;

&lt;p&gt;Other checks, such as comparing the "not before" and "not after" dates on the certificate extensions can be done elsewhere to separate concerns.&lt;/p&gt;

&lt;h4&gt;
  
  
  Example Usage
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(!&lt;/span&gt;&lt;span class="n"&gt;myCertificate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;IsSignedBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rootCertificate&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;InvalidCertificateException&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;



</description>
      <category>watercooler</category>
    </item>
  </channel>
</rss>
