/* LibTomCrypt, modular cryptographic library -- Tom St Denis */
/* SPDX-License-Identifier: Unlicense */

#include "tomcrypt_private.h"

#ifdef LTC_MECC

typedef struct {
   ltc_asn1_type t;
   ltc_asn1_list **pp;
} der_flexi_check;

#define LTC_SET_DER_FLEXI_CHECK(list, index, Type, P)    \
   do {                                         \
      int LTC_SDFC_temp##__LINE__ = (index);   \
      list[LTC_SDFC_temp##__LINE__].t = Type;  \
      list[LTC_SDFC_temp##__LINE__].pp = P;    \
   } while (0)

static int s_der_flexi_sequence_cmp(const ltc_asn1_list *flexi, der_flexi_check *check)
{
   const ltc_asn1_list *cur;
   if (flexi->type != LTC_ASN1_SEQUENCE) {
      return CRYPT_INVALID_PACKET;
   }
   cur = flexi->child;
   while(check->t != LTC_ASN1_EOL) {
      if (!LTC_ASN1_IS_TYPE(cur, check->t)) {
         return CRYPT_INVALID_PACKET;
      }
      if (check->pp != NULL) *check->pp = (ltc_asn1_list*)cur;
      cur = cur->next;
      check++;
   }
   return CRYPT_OK;
}

/* NOTE: s_der_decode_pkcs8_flexi & related stuff can be shared with rsa_import_pkcs8() */

int ecc_import_pkcs8(const unsigned char *in, unsigned long inlen,
                     const void *pwd, unsigned long pwdlen,
                     ecc_key *key)
{
   void          *a, *b, *gx, *gy;
   unsigned long len, cofactor, n;
   const char    *pka_ec_oid;
   int           err;
   char          OID[256];
   const ltc_ecc_curve *curve;
   ltc_asn1_list *p = NULL, *l = NULL;
   der_flexi_check flexi_should[7];
   ltc_asn1_list *seq, *priv_key;

   LTC_ARGCHK(in          != NULL);
   LTC_ARGCHK(key         != NULL);
   LTC_ARGCHK(ltc_mp.name != NULL);

   /* get EC alg oid */
   err = pk_get_oid(PKA_EC, &pka_ec_oid);
   if (err != CRYPT_OK) return err;

   /* init key */
   err = mp_init_multi(&a, &b, &gx, &gy, NULL);
   if (err != CRYPT_OK) return err;


   if ((err = pkcs8_decode_flexi(in, inlen, pwd, pwdlen, &l)) == CRYPT_OK) {

      /* Setup for basic structure */
      n=0;
      LTC_SET_DER_FLEXI_CHECK(flexi_should, n++, LTC_ASN1_INTEGER, NULL);
      LTC_SET_DER_FLEXI_CHECK(flexi_should, n++, LTC_ASN1_SEQUENCE, &seq);
      LTC_SET_DER_FLEXI_CHECK(flexi_should, n++, LTC_ASN1_OCTET_STRING, &priv_key);
      LTC_SET_DER_FLEXI_CHECK(flexi_should, n, LTC_ASN1_EOL, NULL);

      if (((err = s_der_flexi_sequence_cmp(l, flexi_should)) == CRYPT_OK) &&
            (pk_oid_cmp_with_asn1(pka_ec_oid, seq->child) == CRYPT_OK)) {
         ltc_asn1_list *version, *field, *point, *point_g, *order, *p_cofactor;

         /* Setup for CASE 2 */
         n=0;
         LTC_SET_DER_FLEXI_CHECK(flexi_should, n++, LTC_ASN1_INTEGER, &version);
         LTC_SET_DER_FLEXI_CHECK(flexi_should, n++, LTC_ASN1_SEQUENCE, &field);
         LTC_SET_DER_FLEXI_CHECK(flexi_should, n++, LTC_ASN1_SEQUENCE, &point);
         LTC_SET_DER_FLEXI_CHECK(flexi_should, n++, LTC_ASN1_OCTET_STRING, &point_g);
         LTC_SET_DER_FLEXI_CHECK(flexi_should, n++, LTC_ASN1_INTEGER, &order);
         LTC_SET_DER_FLEXI_CHECK(flexi_should, n++, LTC_ASN1_INTEGER, &p_cofactor);
         LTC_SET_DER_FLEXI_CHECK(flexi_should, n, LTC_ASN1_EOL, NULL);

         if (LTC_ASN1_IS_TYPE(seq->child->next, LTC_ASN1_OBJECT_IDENTIFIER)) {
            /* CASE 1: curve by OID (AKA short variant):
             *   0:d=0  hl=2 l= 100 cons: SEQUENCE
             *   2:d=1  hl=2 l=   1 prim:   INTEGER        :00
             *   5:d=1  hl=2 l=  16 cons:   SEQUENCE       (== *seq)
             *   7:d=2  hl=2 l=   7 prim:     OBJECT       :id-ecPublicKey
             *  16:d=2  hl=2 l=   5 prim:     OBJECT       :(== *curve_oid (e.g. secp256k1 (== 1.3.132.0.10)))
             *  23:d=1  hl=2 l=  77 prim:   OCTET STRING   :bytes (== *priv_key)
             */
            ltc_asn1_list *curve_oid = seq->child->next;
            len = sizeof(OID);
            if ((err = pk_oid_num_to_str(curve_oid->data, curve_oid->size, OID, &len)) != CRYPT_OK) { goto LBL_DONE; }
            if ((err = ecc_find_curve(OID, &curve)) != CRYPT_OK)                          { goto LBL_DONE; }
            if ((err = ecc_set_curve(curve, key)) != CRYPT_OK)                            { goto LBL_DONE; }
         }
         else if ((err = s_der_flexi_sequence_cmp(seq->child->next, flexi_should)) == CRYPT_OK) {
            /* CASE 2: explicit curve parameters (AKA long variant):
             *   0:d=0  hl=3 l= 227 cons: SEQUENCE
             *   3:d=1  hl=2 l=   1 prim:   INTEGER              :00
             *   6:d=1  hl=3 l= 142 cons:   SEQUENCE             (== *seq)
             *   9:d=2  hl=2 l=   7 prim:     OBJECT             :id-ecPublicKey
             *  18:d=2  hl=3 l= 130 cons:     SEQUENCE
             *  21:d=3  hl=2 l=   1 prim:       INTEGER          :01
             *  24:d=3  hl=2 l=  44 cons:       SEQUENCE         (== *field)
             *  26:d=4  hl=2 l=   7 prim:         OBJECT         :prime-field
             *  35:d=4  hl=2 l=  33 prim:         INTEGER        :(== *prime / curve.prime)
             *  70:d=3  hl=2 l=   6 cons:       SEQUENCE         (== *point)
             *  72:d=4  hl=2 l=   1 prim:         OCTET STRING   :bytes (== curve.A)
             *  75:d=4  hl=2 l=   1 prim:         OCTET STRING   :bytes (== curve.B)
             *  78:d=3  hl=2 l=  33 prim:       OCTET STRING     :bytes (== *g_point / curve.G-point)
             * 113:d=3  hl=2 l=  33 prim:       INTEGER          :(== *order / curve.order)
             * 148:d=3  hl=2 l=   1 prim:       INTEGER          :(== curve.cofactor)
             * 151:d=1  hl=2 l=  77 prim:   OCTET STRING         :bytes (== *priv_key)
             */

            if (mp_get_int(version->data) != 1) {
               goto LBL_DONE;
            }
            cofactor = mp_get_int(p_cofactor->data);

            if (LTC_ASN1_IS_TYPE(field->child, LTC_ASN1_OBJECT_IDENTIFIER) &&
                LTC_ASN1_IS_TYPE(field->child->next, LTC_ASN1_INTEGER) &&
                LTC_ASN1_IS_TYPE(point->child, LTC_ASN1_OCTET_STRING) &&
                LTC_ASN1_IS_TYPE(point->child->next, LTC_ASN1_OCTET_STRING)) {

               ltc_asn1_list *prime = field->child->next;
               if ((err = mp_read_unsigned_bin(a, point->child->data, point->child->size)) != CRYPT_OK) {
                  goto LBL_DONE;
               }
               if ((err = mp_read_unsigned_bin(b, point->child->next->data, point->child->next->size)) != CRYPT_OK) {
                  goto LBL_DONE;
               }
               if ((err = ltc_ecc_import_point(point_g->data, point_g->size, prime->data, a, b, gx, gy)) != CRYPT_OK) {
                  goto LBL_DONE;
               }
               if ((err = ecc_set_curve_from_mpis(a, b, prime->data, order->data, gx, gy, cofactor, key)) != CRYPT_OK) {
                  goto LBL_DONE;
               }
            }
         }
         else {
            err = CRYPT_INVALID_PACKET;
            goto LBL_DONE;
         }

         /* load private key value 'k' */
         len = priv_key->size;
         if ((err = der_decode_sequence_flexi(priv_key->data, &len, &p)) == CRYPT_OK) {
            if (p->type == LTC_ASN1_SEQUENCE &&
                LTC_ASN1_IS_TYPE(p->child, LTC_ASN1_INTEGER) &&
                LTC_ASN1_IS_TYPE(p->child->next, LTC_ASN1_OCTET_STRING)) {
               ltc_asn1_list *lk = p->child->next;
               if (mp_cmp_d(p->child->data, 1) != LTC_MP_EQ) {
                  err = CRYPT_INVALID_PACKET;
                  goto LBL_ECCFREE;
               }
               if ((err = ecc_set_key(lk->data, lk->size, PK_PRIVATE, key)) != CRYPT_OK) {
                  goto LBL_ECCFREE;
               }
               goto LBL_DONE; /* success */
            }
         }
      }
   }
   err = CRYPT_INVALID_PACKET;
   goto LBL_DONE;

LBL_ECCFREE:
   ecc_free(key);
LBL_DONE:
   mp_clear_multi(a, b, gx, gy, NULL);
   if (l) der_free_sequence_flexi(l);
   if (p) der_free_sequence_flexi(p);
   return err;
}

#endif
