/*
 * openvpn.c
 *
 * Copyright (C) 2011-22 - ntop.org
 *
  *
 * nDPI is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * nDPI is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with nDPI.  If not, see <http://www.gnu.org/licenses/>.
 *
 */



#include <stdint.h>
#include <arpa/inet.h>

#include "app_l7_protocol.h"
/*
 * OpenVPN TCP / UDP Detection - 128/160 hmac
 *
 * Detection based upon these openvpn protocol properties:
 *   - opcode
 *   - packet ID
 *   - session ID
 *
 * Two (good) packets are needed to perform detection.
 *  - First packet from client: save session ID
 *  - Second packet from server: report saved session ID
 *
 * TODO
 *  - Support PSK only mode (instead of TLS)
 *  - Support PSK + TLS mode (PSK used for early authentication)
 *  - TLS certificate extraction
 *
 */

#define P_CONTROL_HARD_RESET_CLIENT_V1  (0x01 << 3)
#define P_CONTROL_HARD_RESET_CLIENT_V2  (0x07 << 3)
#define P_CONTROL_HARD_RESET_SERVER_V1  (0x02 << 3)
#define P_CONTROL_HARD_RESET_SERVER_V2  (0x08 << 3)
#define P_ACK_V1  (0x05 << 3)
#define P_CONTROL_V1 (0x04 << 3)
#define P_OPCODE_MASK 0xF8
#define P_SHA1_HMAC_SIZE 20
#define P_HMAC_128 16                            // (RSA-)MD5, (RSA-)MD4, ..others
#define P_HMAC_160 20                            // (RSA-|DSA-)SHA(1), ..others, SHA1 is openvpn default
#define P_HMAC_NONE 0
#define P_HARD_RESET_PACKET_ID_OFFSET(hmac_size) (9 + hmac_size)
#define P_PACKET_ID_ARRAY_LEN_OFFSET(hmac_size)  (P_HARD_RESET_PACKET_ID_OFFSET(hmac_size) + 8)
#define P_HARD_RESET_CLIENT_MAX_COUNT  5

static  inline uint32_t get_packet_id(const uint8_t * payload, uint8_t hms) {
  return(ntohl(*(uint32_t*)(payload + P_HARD_RESET_PACKET_ID_OFFSET(hms))));
}

static inline int8_t check_pkid_and_detect_hmac_size(const uint8_t * payload) {
  // try to guess
  if((int)get_packet_id(payload, P_HMAC_160) != 0)
    return P_HMAC_160;
  
  if((int)get_packet_id(payload, P_HMAC_128) != 0 )    
    return P_HMAC_128;

  if((int)get_packet_id(payload, P_HMAC_NONE) != 0)    
    return P_HMAC_NONE;
  
  return(-1);
}

int app_identify_guess_openvpn(const unsigned char *payload, int payload_len, int c2s_pkt_cnt, int s2c_pkt_cnt, struct ovpn_ctx *ovpn, int l4_is_tcp, int curdir_is_c2s) 
{
  const uint8_t * session_remote;
  uint8_t opcode;
  uint8_t alen;
  int8_t hmac_size;
  int8_t failed = 0;

  if (c2s_pkt_cnt > 5)
  {
    if (ovpn->contain_local_session_id_pkt_num == P_HARD_RESET_CLIENT_MAX_COUNT - 1 && ovpn->valid_opcode_cnt == P_HARD_RESET_CLIENT_MAX_COUNT)
      return 1;
    return -1;
  }

  if (payload_len >= 40)
  {
    // skip openvpn TCP transport packet size
    if (l4_is_tcp)
      payload += 2, payload_len -= 2;

    opcode = payload[0] & P_OPCODE_MASK;
    if (curdir_is_c2s == 1 &&
        (opcode == P_CONTROL_HARD_RESET_CLIENT_V1 ||
         opcode == P_CONTROL_HARD_RESET_CLIENT_V2 ||
         opcode == P_ACK_V1 ||
         opcode == P_CONTROL_V1))
    {
      ovpn->valid_opcode_cnt++;
    }
    // for UDP, trying to identify by opcode and first packet length
    if (l4_is_tcp == 0)
    {
      if ((c2s_pkt_cnt == 1 || s2c_pkt_cnt == 1) && (((payload_len == 112) && ((opcode == 168) || (opcode == 192))) || ((payload_len == 80) && ((opcode == 184) || (opcode == 88) || (opcode == 160) || (opcode == 168) || (opcode == 200)))))
      {
        return 1;
      }
    }

    if(s2c_pkt_cnt >= 1 && curdir_is_c2s == 0)return 0;

    if (curdir_is_c2s == 1 && c2s_pkt_cnt <= P_HARD_RESET_CLIENT_MAX_COUNT && (opcode == P_CONTROL_HARD_RESET_CLIENT_V1 || opcode == P_CONTROL_HARD_RESET_CLIENT_V2 || opcode == P_ACK_V1 || opcode == P_CONTROL_V1))
    {
      if (check_pkid_and_detect_hmac_size(payload) >= 0)
      {
        if (c2s_pkt_cnt > 1)
        {
          if (memcmp(&ovpn->local_session_id, payload + 1, 8) == 0)
          {
            ovpn->contain_local_session_id_pkt_num++;
          }
        }
        memcpy(&ovpn->local_session_id, payload + 1, 8);
      }
    }
    else if (c2s_pkt_cnt >= 1 && c2s_pkt_cnt <= P_HARD_RESET_CLIENT_MAX_COUNT &&
             (opcode == P_CONTROL_HARD_RESET_SERVER_V1 || opcode == P_CONTROL_HARD_RESET_SERVER_V2))
    {

      hmac_size = check_pkid_and_detect_hmac_size(payload);

      if (hmac_size >= 0)
      {
        u_int16_t offset = P_PACKET_ID_ARRAY_LEN_OFFSET(hmac_size);

        alen = payload[offset];

        if (alen > 0)
        {
          offset += 1 + alen * 4;

          if ((offset + 8) <= payload_len)
          {
            session_remote = &payload[offset];

            if (memcmp(&ovpn->local_session_id, session_remote, 8) == 0)
            {

              return 1;
            }
            else
            {

              failed = 1;
            }
          }
          else
            failed = 1;
        }
        else
          failed = 1;
      }
      else
        failed = 1;
    }
    else
      failed = 1;

    if (failed)
      return -1;
  }

  return 0;
}