/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * 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 "nss.h"
#include "pk11func.h"
#include "ssl.h"
#include "sslproto.h"
#include "sslimpl.h"
#include "ssl3exthandle.h"
#include "tls13exthandle.h"
#include "tls13hkdf.h"
#include "tls13psk.h"

SECStatus
SSLExp_AddExternalPsk0Rtt(PRFileDesc *fd, PK11SymKey *key, const PRUint8 *identity,
                          unsigned int identityLen, SSLHashType hash,
                          PRUint16 zeroRttSuite, PRUint32 maxEarlyData)
{

    sslSocket *ss = ssl_FindSocket(fd);
    if (!ss) {
        SSL_DBG(("%d: SSL[%d]: bad socket in SSLExp_SetExternalPsk",
                 SSL_GETPID(), fd));
        return SECFailure;
    }

    if (!key || !identity || !identityLen || identityLen > 0xFFFF ||
        (hash != ssl_hash_sha256 && hash != ssl_hash_sha384)) {
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
        return SECFailure;
    }

    SECItem label = { siBuffer, CONST_CAST(unsigned char, identity), identityLen };
    sslPsk *psk = tls13_MakePsk(PK11_ReferenceSymKey(key), ssl_psk_external,
                                hash, &label);
    if (!psk) {
        PORT_SetError(SEC_ERROR_NO_MEMORY);
        return SECFailure;
    }
    psk->zeroRttSuite = zeroRttSuite;
    psk->maxEarlyData = maxEarlyData;
    SECStatus rv = SECFailure;

    ssl_Get1stHandshakeLock(ss);
    ssl_GetSSL3HandshakeLock(ss);

    if (ss->psk) {
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
        tls13_DestroyPsk(psk);
    } else {
        ss->psk = psk;
        rv = SECSuccess;
        tls13_ResetHandshakePsks(ss, &ss->ssl3.hs.psks);
    }

    ssl_ReleaseSSL3HandshakeLock(ss);
    ssl_Release1stHandshakeLock(ss);

    return rv;
}

SECStatus
SSLExp_AddExternalPsk(PRFileDesc *fd, PK11SymKey *key, const PRUint8 *identity,
                      unsigned int identityLen, SSLHashType hash)
{
    return SSLExp_AddExternalPsk0Rtt(fd, key, identity, identityLen,
                                     hash, TLS_NULL_WITH_NULL_NULL, 0);
}

SECStatus
SSLExp_RemoveExternalPsk(PRFileDesc *fd, const PRUint8 *identity, unsigned int identityLen)
{
    if (!identity || !identityLen) {
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
        return SECFailure;
    }

    sslSocket *ss = ssl_FindSocket(fd);
    if (!ss) {
        SSL_DBG(("%d: SSL[%d]: bad socket in SSL_SetPSK",
                 SSL_GETPID(), fd));
        return SECFailure;
    }

    SECItem removeIdentity = { siBuffer,
                               (unsigned char *)identity,
                               identityLen };

    SECStatus rv;
    ssl_Get1stHandshakeLock(ss);
    ssl_GetSSL3HandshakeLock(ss);

    if (!ss->psk || SECITEM_CompareItem(&ss->psk->label, &removeIdentity) != SECEqual) {
        PORT_SetError(SEC_ERROR_NO_KEY);
        rv = SECFailure;
    } else {
        tls13_DestroyPsk(ss->psk);
        ss->psk = NULL;
        tls13_ResetHandshakePsks(ss, &ss->ssl3.hs.psks);
        rv = SECSuccess;
    }

    ssl_ReleaseSSL3HandshakeLock(ss);
    ssl_Release1stHandshakeLock(ss);

    return rv;
}

sslPsk *
tls13_CopyPsk(sslPsk *opsk)
{
    if (!opsk || !opsk->key) {
        return NULL;
    }

    sslPsk *psk = PORT_ZNew(sslPsk);
    if (!psk) {
        return NULL;
    }

    SECStatus rv = SECITEM_CopyItem(NULL, &psk->label, &opsk->label);
    if (rv != SECSuccess) {
        PORT_Free(psk);
        return NULL;
    }
    /* We should only have the initial key. Binder keys
     * are derived during the handshake.  */
    PORT_Assert(opsk->type == ssl_psk_external);
    PORT_Assert(opsk->key);
    PORT_Assert(!opsk->binderKey);
    psk->hash = opsk->hash;
    psk->type = opsk->type;
    psk->key = opsk->key ? PK11_ReferenceSymKey(opsk->key) : NULL;
    psk->binderKey = opsk->binderKey ? PK11_ReferenceSymKey(opsk->binderKey) : NULL;
    return psk;
}

void
tls13_DestroyPsk(sslPsk *psk)
{
    if (!psk) {
        return;
    }
    if (psk->key) {
        PK11_FreeSymKey(psk->key);
        psk->key = NULL;
    }
    if (psk->binderKey) {
        PK11_FreeSymKey(psk->binderKey);
        psk->binderKey = NULL;
    }
    SECITEM_ZfreeItem(&psk->label, PR_FALSE);
    PORT_ZFree(psk, sizeof(*psk));
}

void
tls13_DestroyPskList(PRCList *list)
{
    PRCList *cur_p;
    while (!PR_CLIST_IS_EMPTY(list)) {
        cur_p = PR_LIST_TAIL(list);
        PR_REMOVE_LINK(cur_p);
        tls13_DestroyPsk((sslPsk *)cur_p);
    }
}

sslPsk *
tls13_MakePsk(PK11SymKey *key, SSLPskType pskType, SSLHashType hashType, const SECItem *label)
{
    sslPsk *psk = PORT_ZNew(sslPsk);
    if (!psk) {
        PORT_SetError(SEC_ERROR_NO_MEMORY);
        return NULL;
    }
    psk->type = pskType;
    psk->hash = hashType;
    psk->key = key;

    /* Label is NULL in the resumption case. */
    if (label) {
        PORT_Assert(psk->type != ssl_psk_resume);
        SECStatus rv = SECITEM_CopyItem(NULL, &psk->label, label);
        if (rv != SECSuccess) {
            PORT_SetError(SEC_ERROR_NO_MEMORY);
            tls13_DestroyPsk(psk);
            return NULL;
        }
    }

    return psk;
}

/* Destroy any existing PSKs in |list| then copy
 * in the configured |ss->psk|, if any.*/
SECStatus
tls13_ResetHandshakePsks(sslSocket *ss, PRCList *list)
{
    tls13_DestroyPskList(list);
    PORT_Assert(!ss->xtnData.selectedPsk);
    ss->xtnData.selectedPsk = NULL;
    if (ss->psk) {
        PORT_Assert(ss->psk->type == ssl_psk_external);
        PORT_Assert(ss->psk->key);
        PORT_Assert(!ss->psk->binderKey);

        sslPsk *epsk = tls13_MakePsk(PK11_ReferenceSymKey(ss->psk->key),
                                     ss->psk->type, ss->psk->hash, &ss->psk->label);
        if (!epsk) {
            return SECFailure;
        }
        epsk->zeroRttSuite = ss->psk->zeroRttSuite;
        epsk->maxEarlyData = ss->psk->maxEarlyData;
        PR_APPEND_LINK(&epsk->link, list);
    }
    return SECSuccess;
}
