/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "cbor-cpp/src/cbor.h"
#include "mozilla/dom/WebAuthnCBORUtil.h"
#include "mozilla/dom/WebAuthnUtil.h"

namespace mozilla::dom {

nsresult CBOREncodePublicKeyObj(const CryptoBuffer& aPubKeyBuf,
                                /* out */ CryptoBuffer& aPubKeyObj) {
  mozilla::dom::CryptoBuffer xBuf, yBuf;
  nsresult rv = U2FDecomposeECKey(aPubKeyBuf, xBuf, yBuf);
  if (NS_FAILED(rv)) {
    return rv;
  }

  // COSE_Key object. See https://tools.ietf.org/html/rfc8152#section-7
  cbor::output_dynamic cborPubKeyOut;
  cbor::encoder encoder(cborPubKeyOut);
  encoder.write_map(5);
  {
    encoder.write_int(1);   // kty
    encoder.write_int(2);   // EC2
    encoder.write_int(3);   // alg
    encoder.write_int(-7);  // ES256

    // See https://tools.ietf.org/html/rfc8152#section-13.1
    encoder.write_int(-1);  // crv
    encoder.write_int(1);   // P-256
    encoder.write_int(-2);  // x
    encoder.write_bytes(xBuf.Elements(), xBuf.Length());
    encoder.write_int(-3);  // y
    encoder.write_bytes(yBuf.Elements(), yBuf.Length());
  }

  if (!aPubKeyObj.Assign(cborPubKeyOut.data(), cborPubKeyOut.size())) {
    return NS_ERROR_OUT_OF_MEMORY;
  }
  return NS_OK;
}

nsresult CBOREncodeFidoU2FAttestationObj(
    const CryptoBuffer& aAuthDataBuf, const CryptoBuffer& aAttestationCertBuf,
    const CryptoBuffer& aSignatureBuf,
    /* out */ CryptoBuffer& aAttestationObj) {
  /*
  Attestation Object, encoded in CBOR (description is CDDL)

  attObj = {
              authData: bytes,
              $$attStmtType
           }
  $$attStmtType //= (
                        fmt: "fido-u2f",
                        attStmt: u2fStmtFormat
                    )
  u2fStmtFormat = {
                      x5c: [ attestnCert: bytes, * (caCert: bytes) ],
                      sig: bytes
                  }
  */
  cbor::output_dynamic cborAttOut;
  cbor::encoder encoder(cborAttOut);
  encoder.write_map(3);
  {
    encoder.write_string("fmt");
    encoder.write_string("fido-u2f");

    encoder.write_string("attStmt");
    encoder.write_map(2);
    {
      encoder.write_string("sig");
      encoder.write_bytes(aSignatureBuf.Elements(), aSignatureBuf.Length());

      encoder.write_string("x5c");
      // U2F wire protocol can only deliver 1 certificate, so it's never a chain
      encoder.write_array(1);
      encoder.write_bytes(aAttestationCertBuf.Elements(),
                          aAttestationCertBuf.Length());
    }

    encoder.write_string("authData");
    encoder.write_bytes(aAuthDataBuf.Elements(), aAuthDataBuf.Length());
  }

  if (!aAttestationObj.Assign(cborAttOut.data(), cborAttOut.size())) {
    return NS_ERROR_OUT_OF_MEMORY;
  }
  return NS_OK;
}

nsresult CBOREncodeNoneAttestationObj(const CryptoBuffer& aAuthDataBuf,
                                      /* out */ CryptoBuffer& aAttestationObj) {
  /*
  Attestation Object, encoded in CBOR (description is CDDL)

  $$attStmtType //= (
                          fmt: "none",
                          attStmt: emptyMap
                      )

  emptyMap = {}
  */
  cbor::output_dynamic cborAttOut;
  cbor::encoder encoder(cborAttOut);
  encoder.write_map(3);
  {
    encoder.write_string("fmt");
    encoder.write_string("none");

    encoder.write_string("attStmt");
    encoder.write_map(0);

    encoder.write_string("authData");
    encoder.write_bytes(aAuthDataBuf.Elements(), aAuthDataBuf.Length());
  }

  if (!aAttestationObj.Assign(cborAttOut.data(), cborAttOut.size())) {
    return NS_ERROR_OUT_OF_MEMORY;
  }
  return NS_OK;
}

}  // namespace mozilla::dom
