Free AI web copilot to create summaries, insights and extended knowledge, download it at here
3096
Abstract
0aa4.js" allowfullscreen="" frameborder="0" height="undefined" width="undefined">
</div>
</div>
</figure></iframe></div></div></figure><p id="3faf">JWT(pronounced as JOT) is stands for JSON Web Token. Not going to deep technical details, its basically base64url concatenation of signed header and payload with signature, separated by a full stop(.). You can play with my JTW by pasting at <a href="https://jwt.io/">https://jwt.io/</a>.</p><figure id="c52f"><img src="https://cdn-images-1.readmedium.com/v2/resize:fit:800/1*J8oBqWzdbGH5oeuJG-mCuw.png"><figcaption></figcaption></figure><p id="eef2">The steps to verify FIDO2 SafetyNet attestation are:</p><ol><li>Verify payload</li><li>Verify header</li><li>Verify signature over the concatenation of the payload and header joined by a full stop</li></ol><h1 id="b35e">Verifying payload</h1><p id="0b24">The payload is the second base64url encoded string</p><blockquote id="2864"><p><b>ey</b>Jub25jZSI6IlhQQjdWVGRSWGJEM01mMENvTFF5ZVJQclA5ZjIzYW9mVHBtVWd6cmlrbzA9IiwidGltZXN0YW1wTXMiOjE1NDA2NTE2ODQwOTMsImFwa1BhY2thZ2VOYW1lIjoiY29tLmdvb2dsZS5hbmRyb2lkLmdtcyIsImFwa0RpZ2VzdFNoYTI1NiI6ImVRYyt2elVjZHgwRlZOTHZYSHVHcEQwK1I4MDdzVUV2cCtKZWxlWVpzaUE9IiwiY3RzUHJvZmlsZU1hdGNoIjp0cnVlLCJhcGtDZXJ0aWZpY2F0ZURpZ2VzdFNoYTI1NiI6WyI4UDFzVzBFUEpjc2x3N1V6UnNpWEw2NHcrTzUwRWQrUkJJQ3RheTFnMjRNPSJdLCJiYXNpY0ludGVncml0eSI6dHJ1ZX0</p></blockquote><p id="cb85">Fun fact: if you see string that start with “<b>ey</b>”, it’s most likely JSON in Base64. If we decode it to UTF8 and JSON decode it, we will get this:</p>
<figure id="3691">
<div>
<div>
<iframe class="gist-iframe" src="/gist/herrjemand/62f03f5695a2749031c933d00cbfba07.js" allowfullscreen="" frameborder="0" height="undefined" width="undefined">
</div>
</div>
</figure></iframe></div></div></figure><p id="c50b">To verify the payload you need:</p><ol><li>Hash <b>clientDataJSON</b> using SHA256, to create <b>clientDataHash</b></li><li>Concatenate <b>authData</b> with <b>clientDataHash</b> to create <b>nonceBase</b></li><li>Hash <b>nonceBase</b> using SHA256 to create <b>nonceBuffer</b>.</li><li>Base64 encode <b>nonceBuffer</b> to create <b>expectedNonce</b></li><li>Check that “<b>nonce</b>” is set to <b>expectedNonce</b></li><li>Check that “<b>ctsProfileMatch</b>” is set to true. If its not set to true, that means that device has been rooted and so can not be trusted to provide trustworthy attestation.</li></ol><p id="1fad">I won’t be discussing what other field are used for. If you are planing to implement your own SafetyNet authenticator, you should watch Collin’s video above.</p><h1 id="07ca">Verifying header</h1><p id="7b13">The next step would be to verify header. Header is the first Base64url encoded string. When decoded:</p>
<figure id="dddd">
<div>
<div>
<iframe class="gist-iframe" src="/gist/herrjemand/be2377ef410ec9ba0bac0dedb1c099ae.js" allowfullscreen="" frameborder="0" hei
Options
ght="undefined" width="undefined">
</div>
</div>
</figure></iframe></div></div></figure><p id="137c">To verify header we need to:</p><ol><li>If you are implementing Metadata Statement, or Metadata Service support: Verify that “alg” field is corresponds to the authenticationAlgorithm in the Metadata Statement.</li><li>Get leaf certificate of x5c certificate chain, decode it, and check that it was issued for “attest.android.com”</li><li>If you are using MDS or Metadata Statements, for each attestationRoot in attestationRootCertificates: append attestation root to the end of the header.x5c, and try verifying certificate chain. If none succeed, throw an error</li><li>If you are not using MDS or Metadata Statements, then download <a href="https://pki.goog/gsr2/GSR2.crt">“<i>GlobalSign Root CA — R2”</i></a><i> </i>from <a href="https://pki.goog/">Google PKI directory</a>. Attach it to the end of header.x5c and try to verify it</li></ol><h1 id="fc08">Verifying JWT</h1><p id="20fb">If you have successfully verified header and payload, then you can finally verify the JWT.</p><ol><li>Concatenate Base64URL encoded header and payload with full stop, to create signatureBase</li><li>Extract public key from leaf certificate</li><li>Verify signature over signatureBase using the public key extracted from leaf certificate</li></ol><h1 id="5068">Best practices</h1><ul><li>Use well established libraries to verify JWT. <a href="https://jwt.io/">jwt.io</a> has a great list of libraries for basically every popular programming language there are.</li></ul><h1 id="caa2">References</h1><ul><li><a href="https://w3c.github.io/webauthn/#android-safetynet-attestation">https://w3c.github.io/webauthn/#android-safetynet-attestation</a></li><li><a href="https://jwt.io/">https://jwt.io/</a></li><li><a href="https://lapo.it/asn1js/">https://lapo.it/asn1js/</a></li><li><a href="https://developer.android.com/training/safetynet/">https://developer.android.com/training/safetynet/</a></li><li><a href="https://www.youtube.com/watch?v=8lv_9mydrjg">https://www.youtube.com/watch?v=8lv_9mydrjg</a></li></ul><h1 id="53d0">Snippets</h1>
<figure id="bdac">
<div>
<div>
<iframe class="gist-iframe" src="/gist/herrjemand/4c7850e53ba4a04cc9e000b41b8e6f8f.js" allowfullscreen="" frameborder="0" height="undefined" width="undefined">
</div>
</div>
</figure></iframe></div></div></figure><h1 id="e082">License</h1><p id="dbc8">This article is licensed under <a href="https://creativecommons.org/licenses/by-nc-nd/4.0/">Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International (CC BY-NC-ND 4.0)</a>. So you are free to read, share, etc. If you are interested in commercial use of this article, or wish to translate it to a different language, please contact ackermann(dot)yuriy(at)gmail(dot)com.</p><p id="2701">The code samples are licensed under <a href="https://gist.github.com/herrjemand/09492b2c6fc6c4ebc0d49b5942d4ec30">MIT license</a>.</p></article></body>