title: Biometric login (webauthn) in Go, how to verify signature
body: <p>With the very recent Windows Anniversary update, Edge now supports biometric authentication using Windows Hello (cf. <a href="https://developer.microsoft.com/en-us/microsoft-edge/platform/documentation/dev-guide/device/web-authentication/">https://developer.microsoft.com/en-us/microsoft-edge/platform/documentation/dev-guide/device/web-authentication/</a> , <a href="https://blogs.windows.com/msedgedev/2016/04/12/a-world-without-passwords-windows-hello-in-microsoft-edge/">https://blogs.windows.com/msedgedev/2016/04/12/a-world-without-passwords-windows-hello-in-microsoft-edge/</a> )</p>
  
  <p>I have some samples in C#, PHP and Node.js, and am trying to make it work in Go.</p>
  
  <p>The following works in JS (I have hardcoded in the challenge and the key):</p>
  
  <pre class="lang-js prettyprint-override"><code>function parseBase64(s) {
      s = s.replace(/-/g, "+").replace(/_/g, "/").replace(/\s/g, '');  
      return new Uint8Array(Array.prototype.map.call(atob(s), function (c) { return c.charCodeAt(0) }));  
  }
  
  function concatUint8Array(a1,a2) {
      var d = new Uint8Array(a1.length + a2.length);
      d.set(a1);
      d.set(a2,a1.length);
      return d;
  }
  
  var credAlgorithm = "RSASSA-PKCS1-v1_5";
  var id,authenticatorData,signature,hash;
  webauthn.getAssertion("chalenge").then(function(assertion) {
      id = assertion.credential.id;
      authenticatorData = assertion.authenticatorData;
      signature = assertion.signature;
      return crypto.subtle.digest("SHA-256",parseBase64(assertion.clientData));
  }).then(function(h) {
      hash = new Uint8Array(h);
      var publicKey = "{\"kty\":\"RSA\",\"alg\":\"RS256\",\"ext\":false,\"n\":\"mEqGJwp0GL1oVwjRikkNfzd-Rkpb7vIbGodwQkTDsZT4_UE02WDaRa-PjxzL4lPZ4rUpV5SqVxM25aEIeGkEOR_8Xoqx7lpNKNOQs3E_o8hGBzQKpGcA7de678LeAUZdJZcnnQxXYjNf8St3aOIay7QrPoK8wQHEvv8Jqg7O1-pKEKCIwSKikCFHTxLhDDRo31KFG4XLWtLllCfEO6vmQTseT-_8OZPBSHOxR9VhIbY7VBhPq-PeAWURn3G52tQX-802waGmKBZ4B87YtEEPxCNbyyvlk8jRKP1KIrI49bgJhAe5Mow3yycQEnGuPDwLzmJ1lU6I4zgkyL1jI3Ghsw\",\"e\":\"AQAB\"}";
      return crypto.subtle.importKey("jwk",JSON.parse(publicKey),credAlgorithm,false,["verify"]);
  }).then(function(key) {
      return crypto.subtle.verify({name:credAlgorithm, hash: { name: "SHA-256" }},key,parseBase64(signature),concatUint8Array(parseBase64(authenticatorData),hash));
  }).then(function(result) {
      console.log("ID=" + id + "\r\n" + result);
  }).catch(function(err) {
      console.log('got err: ', err);
  });
  </code></pre>
  
  <p>In go I have the following code, meant to match the above JS code (req is a struct with strings from a JSON request body):</p>
  
  <pre class="lang-go prettyprint-override"><code>func webauthnSigninConversion(g string) ([]byte, error) {
      g = strings.Replace(g, "-", "+", -1)
      g = strings.Replace(g, "_", "/", -1)
      switch(len(g) % 4) { // Pad with trailing '='s
      case 0:
          // No pad chars in this case
      case 2:
          // Two pad chars
          g = g + "=="
      case 3:
          // One pad char
          g = g + "=";
      default:
          return nil, fmt.Errorf("invalid string in public key")
      }
      b, err := base64.StdEncoding.DecodeString(g)
      if err != nil {
          return nil, err
      }
      return b, nil
  }
  
  
  clientData, err := webauthnSigninConversion(req.ClientData)
  if err != nil {
      return err
  }
  
  authenticatorData, err := webauthnSigninConversion(req.AuthenticatorData)
  if err != nil {
      return err
  }
  
  signature, err := webauthnSigninConversion(req.Signature)
  if err != nil {
      return err
  }
  
  publicKey := "{\"kty\":\"RSA\",\"alg\":\"RS256\",\"ext\":false,\"n\":\"mEqGJwp0GL1oVwjRikkNfzd-Rkpb7vIbGodwQkTDsZT4_UE02WDaRa-PjxzL4lPZ4rUpV5SqVxM25aEIeGkEOR_8Xoqx7lpNKNOQs3E_o8hGBzQKpGcA7de678LeAUZdJZcnnQxXYjNf8St3aOIay7QrPoK8wQHEvv8Jqg7O1-pKEKCIwSKikCFHTxLhDDRo31KFG4XLWtLllCfEO6vmQTseT-_8OZPBSHOxR9VhIbY7VBhPq-PeAWURn3G52tQX-802waGmKBZ4B87YtEEPxCNbyyvlk8jRKP1KIrI49bgJhAe5Mow3yycQEnGuPDwLzmJ1lU6I4zgkyL1jI3Ghsw\",\"e\":\"AQAB\"}" // this is really from a db, not hardcoded
  // load json from public key, extract modulus and public exponent
  obj := strings.Replace(publicKey, "\\", "", -1) // remove escapes
  var k struct {
      N string `json:"n"`
      E string `json:"e"`
  }
  if err = json.Unmarshal([]byte(obj), &amp;k); err != nil {
      return err
  }
  n, err := webauthnSigninConversion(k.N)
  if err != nil {
      return err
  }
  e, err := webauthnSigninConversion(k.E)
  if err != nil {
      return err
  }
  pk := &amp;rsa.PublicKey{
      N: new(big.Int).SetBytes(n), // modulus
      E: int(new(big.Int).SetBytes(e).Uint64()), // public exponent
  }
   
  hash := sha256.Sum256(clientData)
  
  // Create data buffer to verify signature over
  b := append(authenticatorData, hash[:]...)
   
  if err = rsa.VerifyPKCS1v15(pk, crypto.SHA256, b, signature); err != nil {
      return err
  }
  
  // if no error, signature matches
  </code></pre>
  
  <p>This code fails with <code>crypto/rsa: input must be hashed message</code>. If I change to using <code>hash[:]</code> instead of <code>b</code> in <code>rsa.VerifyPKCS1v15</code>, it fails with <code>crypto/rsa: verification error</code>. The reason I believe I need to combine <code>authenticatorData</code> and <code>hash</code> is because that is what happens in the C# and PHP sample codes (cf,  <a href="https://github.com/adrianba/fido-snippets/blob/master/csharp/app.cs">https://github.com/adrianba/fido-snippets/blob/master/csharp/app.cs</a> , <a href="https://github.com/adrianba/fido-snippets/blob/master/php/fido-authenticator.php">https://github.com/adrianba/fido-snippets/blob/master/php/fido-authenticator.php</a> ). </p>
  
  <p>Maybe Go does it a different way? </p>
  
  <p>I have printed the byte arrays in JS and Go, and verified that <code>clientData</code>, <code>signatureData</code>, <code>authenticatorData</code> and <code>hash</code> (and the combined array of the latter two) have the exact same values. I have not been able to extract the n and e fields from JS after creating the public key, so there might be a problem in how I create the public key.</p>