6 n__ecc_8c
gullradriel edited this page 2026-10-09 00:55:18 +02:00

src/n_ecc.c

Curve25519 primitives: X25519 key agreement and Ed25519 signatures. More...

Detailed Description

Curve25519 primitives: X25519 key agreement and Ed25519 signatures.

Author: Castagnier Mickael

Version: 1.0

Source code

/*
 * Nilorea Library
 * Copyright (C) 2005-2026 Castagnier Mickael
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * permissions and limitations under the License.
 *
 * SPDX-License-Identifier: Apache-2.0
 */


#include "nilorea/n_ecc.h"

#include "nilorea/n_log.h"

#ifdef HAVE_OPENSSL

#include <openssl/evp.h>
#include <openssl/err.h>

#if N_ECC_AVAILABLE

/* Generate a raw keypair for the given EVP_PKEY type (X25519 or Ed25519). */
static int n_ecc_keypair(int type, unsigned char* priv, unsigned char* pub, const char* name) {
    if (!priv || !pub) {
        n_log(LOG_ERR, "%s: NULL output", name);
        return -1;
    }
    int rc = -1;
    EVP_PKEY_CTX* ctx = EVP_PKEY_CTX_new_id(type, NULL);
    EVP_PKEY* pkey = NULL;
    size_t plen;
    if (!ctx) {
        n_log(LOG_ERR, "%s: EVP_PKEY_CTX_new_id failed", name);
        goto done;
    }
    if (EVP_PKEY_keygen_init(ctx) != 1 || EVP_PKEY_keygen(ctx, &pkey) != 1) {
        n_log(LOG_ERR, "%s: keygen failed", name);
        goto done;
    }
    plen = N_ECC_KEY_LEN;
    if (EVP_PKEY_get_raw_private_key(pkey, priv, &plen) != 1 || plen != N_ECC_KEY_LEN) {
        n_log(LOG_ERR, "%s: get_raw_private_key failed", name);
        goto done;
    }
    plen = N_ECC_KEY_LEN;
    if (EVP_PKEY_get_raw_public_key(pkey, pub, &plen) != 1 || plen != N_ECC_KEY_LEN) {
        n_log(LOG_ERR, "%s: get_raw_public_key failed", name);
        goto done;
    }
    rc = 0;
done:
    if (rc != 0)
        ERR_clear_error(); /* never leave the thread error queue dirty */
    EVP_PKEY_free(pkey);
    EVP_PKEY_CTX_free(ctx);
    return rc;
}

int n_x25519_keypair(unsigned char* priv, unsigned char* pub) {
    return n_ecc_keypair(EVP_PKEY_X25519, priv, pub, "n_x25519_keypair");
}

int n_ed25519_keypair(unsigned char* priv, unsigned char* pub) {
    return n_ecc_keypair(EVP_PKEY_ED25519, priv, pub, "n_ed25519_keypair");
}

int n_x25519_derive(const unsigned char* priv, const unsigned char* peer_pub, unsigned char* shared) {
    if (!priv || !peer_pub || !shared) {
        n_log(LOG_ERR, "n_x25519_derive: NULL argument");
        return -1;
    }
    int rc = -1;
    EVP_PKEY* pk = EVP_PKEY_new_raw_private_key(EVP_PKEY_X25519, NULL, priv, N_ECC_KEY_LEN);
    EVP_PKEY* peer = EVP_PKEY_new_raw_public_key(EVP_PKEY_X25519, NULL, peer_pub, N_ECC_KEY_LEN);
    EVP_PKEY_CTX* dctx = NULL;
    size_t slen = N_ECC_KEY_LEN;
    if (!pk || !peer) {
        n_log(LOG_ERR, "n_x25519_derive: key import failed");
        goto done;
    }
    dctx = EVP_PKEY_CTX_new(pk, NULL);
    if (!dctx || EVP_PKEY_derive_init(dctx) != 1 || EVP_PKEY_derive_set_peer(dctx, peer) != 1) {
        n_log(LOG_ERR, "n_x25519_derive: derive init failed");
        goto done;
    }
    if (EVP_PKEY_derive(dctx, shared, &slen) != 1 || slen != N_ECC_KEY_LEN) {
        n_log(LOG_ERR, "n_x25519_derive: derive failed");
        goto done;
    }
    rc = 0;
done:
    if (rc != 0)
        ERR_clear_error(); /* never leave the thread error queue dirty */
    EVP_PKEY_CTX_free(dctx);
    EVP_PKEY_free(peer);
    EVP_PKEY_free(pk);
    return rc;
}

int n_ed25519_sign(const unsigned char* priv, const unsigned char* msg, size_t len, unsigned char* sig) {
    if (!priv || !sig || (!msg && len > 0)) {
        n_log(LOG_ERR, "n_ed25519_sign: invalid arguments");
        return -1;
    }
    int rc = -1;
    EVP_PKEY* pkey = EVP_PKEY_new_raw_private_key(EVP_PKEY_ED25519, NULL, priv, N_ECC_KEY_LEN);
    EVP_MD_CTX* mdctx = EVP_MD_CTX_new();
    size_t siglen = N_ECC_SIG_LEN;
    if (!pkey || !mdctx) {
        n_log(LOG_ERR, "n_ed25519_sign: setup failed");
        goto done;
    }
    if (EVP_DigestSignInit(mdctx, NULL, NULL, NULL, pkey) != 1) {
        n_log(LOG_ERR, "n_ed25519_sign: init failed");
        goto done;
    }
    if (EVP_DigestSign(mdctx, sig, &siglen, msg, len) != 1 || siglen != N_ECC_SIG_LEN) {
        n_log(LOG_ERR, "n_ed25519_sign: sign failed");
        goto done;
    }
    rc = 0;
done:
    if (rc != 0)
        ERR_clear_error(); /* never leave the thread error queue dirty */
    EVP_MD_CTX_free(mdctx);
    EVP_PKEY_free(pkey);
    return rc;
}

int n_ed25519_verify(const unsigned char* pub, const unsigned char* msg, size_t len, const unsigned char* sig) {
    if (!pub || !sig || (!msg && len > 0)) {
        n_log(LOG_ERR, "n_ed25519_verify: invalid arguments");
        return -1;
    }
    int rc = -1;
    EVP_PKEY* pkey = EVP_PKEY_new_raw_public_key(EVP_PKEY_ED25519, NULL, pub, N_ECC_KEY_LEN);
    EVP_MD_CTX* mdctx = EVP_MD_CTX_new();
    if (!pkey || !mdctx) {
        n_log(LOG_ERR, "n_ed25519_verify: setup failed");
        goto done;
    }
    if (EVP_DigestVerifyInit(mdctx, NULL, NULL, NULL, pkey) != 1) {
        n_log(LOG_ERR, "n_ed25519_verify: init failed");
        goto done;
    }
    /* EVP_DigestVerify returns 1 for a valid signature, 0 for an invalid one,
       negative on error; only a valid signature is a success here. */
    if (EVP_DigestVerify(mdctx, sig, N_ECC_SIG_LEN, msg, len) == 1)
        rc = 0;
done:
    /* A signature mismatch is a NORMAL outcome (e.g. probing which of several
       candidate keys signed a message), yet EVP_DigestVerify pushes an entry
       onto the OpenSSL per-thread error queue on any non-match. Left there,
       that stale entry misleads a later, unrelated OpenSSL call on the same
       thread -- the classic trap where SSL_get_error() then returns
       SSL_ERROR_SSL and a healthy connection is torn down. Drain the queue on
       every non-success path so it reflects only genuine, unhandled failures. */
    if (rc != 0)
        ERR_clear_error();
    EVP_MD_CTX_free(mdctx);
    EVP_PKEY_free(pkey);
    return rc;
}

#else /* !N_ECC_AVAILABLE */

/* OpenSSL grew Curve25519 over two releases: EVP_PKEY_X25519 in 1.1.0 and
   Ed25519 plus the raw-key import/export helpers in 1.1.1. The 1.0.2 this
   library still targets has none of it, and there is no reasonable in-tree
   substitute for the curve arithmetic, so the module keeps its symbols and
   reports the primitive as unavailable instead. Callers test N_ECC_AVAILABLE
   to skip the call rather than treat the -1 as a failure. */

int n_x25519_keypair(unsigned char* priv, unsigned char* pub) {
    (void)priv;
    (void)pub;
    n_log(LOG_ERR, "n_x25519_keypair: requires OpenSSL 1.1.1+ (raw EVP_PKEY X25519 keys)");
    return -1;
}

int n_ed25519_keypair(unsigned char* priv, unsigned char* pub) {
    (void)priv;
    (void)pub;
    n_log(LOG_ERR, "n_ed25519_keypair: requires OpenSSL 1.1.1+ (raw EVP_PKEY Ed25519 keys)");
    return -1;
}

int n_x25519_derive(const unsigned char* priv, const unsigned char* peer_pub, unsigned char* shared) {
    (void)priv;
    (void)peer_pub;
    (void)shared;
    n_log(LOG_ERR, "n_x25519_derive: requires OpenSSL 1.1.1+ (raw EVP_PKEY X25519 keys)");
    return -1;
}

int n_ed25519_sign(const unsigned char* priv, const unsigned char* msg, size_t len, unsigned char* sig) {
    (void)priv;
    (void)msg;
    (void)len;
    (void)sig;
    n_log(LOG_ERR, "n_ed25519_sign: requires OpenSSL 1.1.1+ (raw EVP_PKEY Ed25519 keys)");
    return -1;
}

int n_ed25519_verify(const unsigned char* pub, const unsigned char* msg, size_t len, const unsigned char* sig) {
    (void)pub;
    (void)msg;
    (void)len;
    (void)sig;
    n_log(LOG_ERR, "n_ed25519_verify: requires OpenSSL 1.1.1+ (raw EVP_PKEY Ed25519 keys)");
    return -1;
}

#endif /* N_ECC_AVAILABLE */

#endif /* HAVE_OPENSSL */