#include "guard.h"
#ifndef OPENSSL_NO_QUIC
#include <env-inl.h>
#include <memory_tracker-inl.h>
#include <ngtcp2/ngtcp2_crypto.h>
#include <node_buffer.h>
#include <node_errors.h>
#include <node_sockaddr-inl.h>
#include "session.h"
#include "sessionticket.h"
#include "tlscontext.h"
namespace node {
using v8::ArrayBufferView;
using v8::Just;
using v8::Local;
using v8::Maybe;
using v8::MaybeLocal;
using v8::Nothing;
using v8::Object;
using v8::Value;
using v8::ValueDeserializer;
using v8::ValueSerializer;
namespace quic {
namespace {
SessionTicket::AppData::Source* GetAppDataSource(SSL* ssl) {
auto& tls_session = TLSSession::From(ssl);
return &tls_session.session().ticket_app_data_source();
}
} // namespace
SessionTicket::SessionTicket(Store&& ticket, Store&& transport_params)
: ticket_(std::move(ticket)),
transport_params_(std::move(transport_params)) {}
Maybe<SessionTicket> SessionTicket::FromV8Value(Environment* env,
Local<Value> value) {
if (!value->IsArrayBufferView()) {
THROW_ERR_INVALID_ARG_TYPE(env, "The ticket must be an ArrayBufferView.");
return Nothing<SessionTicket>();
}
Store content;
if (!Store::From(value.As<ArrayBufferView>()).To(&content)) {
return Nothing<SessionTicket>();
}
ngtcp2_vec vec = content;
ValueDeserializer des(env->isolate(), vec.base, vec.len);
if (des.ReadHeader(env->context()).IsNothing()) {
THROW_ERR_INVALID_ARG_VALUE(env, "The ticket format is invalid.");
return Nothing<SessionTicket>();
}
Local<Value> ticket;
Local<Value> transport_params;
if (!des.ReadValue(env->context()).ToLocal(&ticket) ||
!des.ReadValue(env->context()).ToLocal(&transport_params)) {
return Nothing<SessionTicket>();
}
if (!ticket->IsArrayBufferView()) {
THROW_ERR_INVALID_ARG_TYPE(env, "The ticket must be an ArrayBufferView");
return Nothing<SessionTicket>();
}
if (!transport_params->IsArrayBufferView()) {
THROW_ERR_INVALID_ARG_TYPE(
env, "The transport parameters must be an ArrayBufferView");
return Nothing<SessionTicket>();
}
Store ticket_store;
Store transport_params_store;
if (!Store::From(ticket.As<ArrayBufferView>()).To(&ticket_store)) {
return Nothing<SessionTicket>();
}
if (!Store::From(transport_params.As<ArrayBufferView>())
.To(&transport_params_store)) {
return Nothing<SessionTicket>();
}
return Just(SessionTicket(std::move(ticket_store),
std::move(transport_params_store)));
}
MaybeLocal<Object> SessionTicket::encode(Environment* env) const {
auto context = env->context();
ValueSerializer ser(env->isolate());
ser.WriteHeader();
if (ser.WriteValue(context, ticket_.ToUint8Array(env)).IsNothing() ||
ser.WriteValue(context, transport_params_.ToUint8Array(env))
.IsNothing()) {
return MaybeLocal<Object>();
}
auto result = ser.Release();
return Buffer::New(env, reinterpret_cast<char*>(result.first), result.second);
}
const uv_buf_t SessionTicket::ticket() const {
return ticket_;
}
const ngtcp2_vec SessionTicket::transport_params() const {
return transport_params_;
}
void SessionTicket::MemoryInfo(MemoryTracker* tracker) const {
tracker->TrackField("ticket", ticket_);
tracker->TrackField("transport_params", transport_params_);
}
int SessionTicket::GenerateCallback(SSL* ssl, void* arg) {
AppData::Collect(ssl);
return 1;
}
SSL_TICKET_RETURN SessionTicket::DecryptedCallback(SSL* ssl,
SSL_SESSION* session,
const unsigned char* keyname,
size_t keyname_len,
SSL_TICKET_STATUS status,
void* arg) {
switch (status) {
default:
return SSL_TICKET_RETURN_IGNORE;
case SSL_TICKET_EMPTY:
[[fallthrough]];
case SSL_TICKET_NO_DECRYPT:
return SSL_TICKET_RETURN_IGNORE_RENEW;
case SSL_TICKET_SUCCESS_RENEW:
return static_cast<SSL_TICKET_RETURN>(
AppData::Extract(ssl, session, AppData::Source::Flag::STATUS_RENEW));
case SSL_TICKET_SUCCESS:
return static_cast<SSL_TICKET_RETURN>(AppData::Extract(ssl, session));
}
}
SessionTicket::AppData::AppData(SSL* ssl, SSL_SESSION* session)
: ssl_(ssl), session_(session) {}
SSL_SESSION* SessionTicket::AppData::GetSession() const {
return session_ != nullptr ? session_ : SSL_get0_session(ssl_);
}
bool SessionTicket::AppData::Set(const uv_buf_t& data) {
if (set_ || data.base == nullptr || data.len == 0) return false;
set_ = true;
SSL_SESSION_set1_ticket_appdata(GetSession(), data.base, data.len);
return set_;
}
std::optional<const uv_buf_t> SessionTicket::AppData::Get() const {
auto* sess = GetSession();
if (sess == nullptr) return std::nullopt;
uv_buf_t buf;
int ret =
SSL_SESSION_get0_ticket_appdata(sess,
reinterpret_cast<void**>(&buf.base),
reinterpret_cast<size_t*>(&buf.len));
if (ret != 1) return std::nullopt;
return buf;
}
void SessionTicket::AppData::Collect(SSL* ssl) {
AppData app_data(ssl);
if (auto source = GetAppDataSource(ssl)) {
source->CollectSessionTicketAppData(&app_data);
}
}
SessionTicket::AppData::Status SessionTicket::AppData::Extract(
SSL* ssl, SSL_SESSION* session, Source::Flag flag) {
auto source = GetAppDataSource(ssl);
if (source != nullptr) {
AppData app_data(ssl, session);
return source->ExtractSessionTicketAppData(app_data, flag);
}
return Status::TICKET_IGNORE;
}
} // namespace quic
} // namespace node
#endif // OPENSSL_NO_QUIC