Based on Nekogram. Key additions: - Rebrand to FoxiGram (app name, APK name, applicationId com.foxigram.app) - Embedded Xray (VLESS+Reality) proxy client via JNI libxray.so - Bundled hidden one-tap proxies (LTE + WiFi), read-only in UI - Auto-restore proxy on restart, rebind to active network (LTE/WiFi) - Server credentials externalized to git-ignored XrayServers.java (+ template) - libxray Go source included; compiled .so, keystore, google-services.json ignored
685 lines
20 KiB
C++
685 lines
20 KiB
C++
// Copyright 2024 The BoringSSL Authors
|
|
//
|
|
// 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
|
|
//
|
|
// https://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.
|
|
|
|
#include <openssl/ssl.h>
|
|
|
|
#include <assert.h>
|
|
|
|
#include <openssl/span.h>
|
|
|
|
#include "../crypto/internal.h"
|
|
#include "../crypto/spake2plus/internal.h"
|
|
#include "internal.h"
|
|
|
|
|
|
BSSL_NAMESPACE_BEGIN
|
|
|
|
// new_leafless_chain returns a fresh stack of buffers set to {nullptr}.
|
|
static UniquePtr<STACK_OF(CRYPTO_BUFFER)> new_leafless_chain(void) {
|
|
UniquePtr<STACK_OF(CRYPTO_BUFFER)> chain(sk_CRYPTO_BUFFER_new_null());
|
|
if (!chain || !sk_CRYPTO_BUFFER_push(chain.get(), nullptr)) {
|
|
return nullptr;
|
|
}
|
|
|
|
return chain;
|
|
}
|
|
|
|
bool ssl_get_full_credential_list(SSL_HANDSHAKE *hs,
|
|
Array<SSL_CREDENTIAL *> *out) {
|
|
CERT *cert = hs->config->cert.get();
|
|
// Finish filling in the legacy credential if needed.
|
|
if (!cert->x509_method->ssl_auto_chain_if_needed(hs)) {
|
|
return false;
|
|
}
|
|
|
|
size_t num_creds = cert->credentials.size();
|
|
bool include_legacy = cert->legacy_credential->IsComplete();
|
|
if (include_legacy) {
|
|
num_creds++;
|
|
}
|
|
|
|
if (!out->InitForOverwrite(num_creds)) {
|
|
return false;
|
|
}
|
|
|
|
for (size_t i = 0; i < cert->credentials.size(); i++) {
|
|
(*out)[i] = cert->credentials[i].get();
|
|
}
|
|
if (include_legacy) {
|
|
(*out)[num_creds - 1] = cert->legacy_credential.get();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool ssl_credential_matches_requested_issuers(SSL_HANDSHAKE *hs,
|
|
const SSL_CREDENTIAL *cred) {
|
|
if (!cred->must_match_issuer) {
|
|
// This credential does not need to match a requested issuer, so
|
|
// it is good to use without a match.
|
|
return true;
|
|
}
|
|
|
|
// If we have names sent by the CA extension, and this
|
|
// credential matches it, it is good.
|
|
if (hs->ca_names != nullptr) {
|
|
for (const CRYPTO_BUFFER *ca_name : hs->ca_names.get()) {
|
|
if (cred->ChainContainsIssuer(
|
|
Span(CRYPTO_BUFFER_data(ca_name), CRYPTO_BUFFER_len(ca_name)))) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
// If the credential has a trust anchor ID and it matches one sent by the
|
|
// peer, it is good.
|
|
if (!cred->trust_anchor_id.empty() && hs->peer_requested_trust_anchors) {
|
|
CBS cbs = CBS(*hs->peer_requested_trust_anchors), candidate;
|
|
while (CBS_len(&cbs) > 0) {
|
|
if (!CBS_get_u8_length_prefixed(&cbs, &candidate) ||
|
|
CBS_len(&candidate) == 0) {
|
|
OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
|
|
return false;
|
|
}
|
|
if (candidate == Span(cred->trust_anchor_id)) {
|
|
hs->matched_peer_trust_anchor = true;
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
OPENSSL_PUT_ERROR(SSL, SSL_R_NO_MATCHING_ISSUER);
|
|
return false;
|
|
}
|
|
|
|
BSSL_NAMESPACE_END
|
|
|
|
using namespace bssl;
|
|
|
|
static CRYPTO_EX_DATA_CLASS g_ex_data_class = CRYPTO_EX_DATA_CLASS_INIT;
|
|
|
|
ssl_credential_st::ssl_credential_st(SSLCredentialType type_arg)
|
|
: RefCounted(CheckSubClass()), type(type_arg) {
|
|
CRYPTO_new_ex_data(&ex_data);
|
|
}
|
|
|
|
ssl_credential_st::~ssl_credential_st() {
|
|
CRYPTO_free_ex_data(&g_ex_data_class, this, &ex_data);
|
|
}
|
|
|
|
static CRYPTO_BUFFER *buffer_up_ref(const CRYPTO_BUFFER *buffer) {
|
|
CRYPTO_BUFFER_up_ref(const_cast<CRYPTO_BUFFER *>(buffer));
|
|
return const_cast<CRYPTO_BUFFER *>(buffer);
|
|
}
|
|
|
|
UniquePtr<SSL_CREDENTIAL> ssl_credential_st::Dup() const {
|
|
assert(type == SSLCredentialType::kX509);
|
|
UniquePtr<SSL_CREDENTIAL> ret = MakeUnique<SSL_CREDENTIAL>(type);
|
|
if (ret == nullptr) {
|
|
return nullptr;
|
|
}
|
|
|
|
ret->pubkey = UpRef(pubkey);
|
|
ret->privkey = UpRef(privkey);
|
|
ret->key_method = key_method;
|
|
if (!ret->sigalgs.CopyFrom(sigalgs)) {
|
|
return nullptr;
|
|
}
|
|
|
|
if (chain) {
|
|
ret->chain.reset(sk_CRYPTO_BUFFER_deep_copy(chain.get(), buffer_up_ref,
|
|
CRYPTO_BUFFER_free));
|
|
if (!ret->chain) {
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
ret->dc = UpRef(dc);
|
|
ret->signed_cert_timestamp_list = UpRef(signed_cert_timestamp_list);
|
|
ret->ocsp_response = UpRef(ocsp_response);
|
|
ret->dc_algorithm = dc_algorithm;
|
|
return ret;
|
|
}
|
|
|
|
void ssl_credential_st::ClearCertAndKey() {
|
|
pubkey = nullptr;
|
|
privkey = nullptr;
|
|
key_method = nullptr;
|
|
chain = nullptr;
|
|
}
|
|
|
|
bool ssl_credential_st::UsesX509() const {
|
|
switch (type) {
|
|
case SSLCredentialType::kX509:
|
|
case SSLCredentialType::kDelegated:
|
|
return true;
|
|
case SSLCredentialType::kSPAKE2PlusV1Client:
|
|
case SSLCredentialType::kSPAKE2PlusV1Server:
|
|
return false;
|
|
}
|
|
abort();
|
|
}
|
|
|
|
bool ssl_credential_st::UsesPrivateKey() const {
|
|
switch (type) {
|
|
case SSLCredentialType::kX509:
|
|
case SSLCredentialType::kDelegated:
|
|
return true;
|
|
case SSLCredentialType::kSPAKE2PlusV1Client:
|
|
case SSLCredentialType::kSPAKE2PlusV1Server:
|
|
return false;
|
|
}
|
|
abort();
|
|
}
|
|
|
|
bool ssl_credential_st::IsComplete() const {
|
|
// APIs like |SSL_use_certificate| and |SSL_set1_chain| configure the leaf and
|
|
// other certificates separately. It is possible for |chain| have a null leaf.
|
|
if (UsesX509() && (sk_CRYPTO_BUFFER_num(chain.get()) == 0 ||
|
|
sk_CRYPTO_BUFFER_value(chain.get(), 0) == nullptr)) {
|
|
return false;
|
|
}
|
|
// We must have successfully extracted a public key from the certificate,
|
|
// delegated credential, etc.
|
|
if (UsesPrivateKey() && pubkey == nullptr) {
|
|
return false;
|
|
}
|
|
if (UsesPrivateKey() && privkey == nullptr && key_method == nullptr) {
|
|
return false;
|
|
}
|
|
if (type == SSLCredentialType::kDelegated && dc == nullptr) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool ssl_credential_st::SetLeafCert(UniquePtr<CRYPTO_BUFFER> leaf,
|
|
bool discard_key_on_mismatch) {
|
|
if (!UsesX509()) {
|
|
OPENSSL_PUT_ERROR(SSL, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED);
|
|
return false;
|
|
}
|
|
|
|
const bool private_key_matches_leaf = type != SSLCredentialType::kDelegated;
|
|
|
|
CBS cbs;
|
|
CRYPTO_BUFFER_init_CBS(leaf.get(), &cbs);
|
|
UniquePtr<EVP_PKEY> new_pubkey = ssl_cert_parse_pubkey(&cbs);
|
|
if (new_pubkey == nullptr) {
|
|
return false;
|
|
}
|
|
|
|
if (!ssl_is_key_type_supported(EVP_PKEY_id(new_pubkey.get()))) {
|
|
OPENSSL_PUT_ERROR(SSL, SSL_R_UNKNOWN_CERTIFICATE_TYPE);
|
|
return false;
|
|
}
|
|
|
|
// An ECC certificate may be usable for ECDH or ECDSA. We only support ECDSA
|
|
// certificates, so sanity-check the key usage extension.
|
|
if (EVP_PKEY_id(new_pubkey.get()) == EVP_PKEY_EC &&
|
|
!ssl_cert_check_key_usage(&cbs, key_usage_digital_signature)) {
|
|
OPENSSL_PUT_ERROR(SSL, SSL_R_UNKNOWN_CERTIFICATE_TYPE);
|
|
return false;
|
|
}
|
|
|
|
if (private_key_matches_leaf && privkey != nullptr &&
|
|
!ssl_compare_public_and_private_key(new_pubkey.get(), privkey.get())) {
|
|
if (!discard_key_on_mismatch) {
|
|
return false;
|
|
}
|
|
ERR_clear_error();
|
|
privkey = nullptr;
|
|
}
|
|
|
|
if (chain == nullptr) {
|
|
chain = new_leafless_chain();
|
|
if (chain == nullptr) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
CRYPTO_BUFFER_free(sk_CRYPTO_BUFFER_value(chain.get(), 0));
|
|
sk_CRYPTO_BUFFER_set(chain.get(), 0, leaf.release());
|
|
if (private_key_matches_leaf) {
|
|
pubkey = std::move(new_pubkey);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void ssl_credential_st::ClearIntermediateCerts() {
|
|
if (chain == nullptr) {
|
|
return;
|
|
}
|
|
|
|
while (sk_CRYPTO_BUFFER_num(chain.get()) > 1) {
|
|
CRYPTO_BUFFER_free(sk_CRYPTO_BUFFER_pop(chain.get()));
|
|
}
|
|
}
|
|
|
|
bool ssl_credential_st::ChainContainsIssuer(
|
|
bssl::Span<const uint8_t> dn) const {
|
|
if (UsesX509()) {
|
|
// TODO(bbe) This is used for matching a chain by CA name for the CA
|
|
// extension. If we require a chain to be present, we could remove any
|
|
// remaining parts of the chain after the found issuer, on the assumption
|
|
// that the peer sending the CA extension has the issuer in their trust
|
|
// store and does not need us to waste bytes on the wire.
|
|
CBS dn_cbs;
|
|
CBS_init(&dn_cbs, dn.data(), dn.size());
|
|
for (size_t i = 0; i < sk_CRYPTO_BUFFER_num(chain.get()); i++) {
|
|
const CRYPTO_BUFFER *cert = sk_CRYPTO_BUFFER_value(chain.get(), i);
|
|
CBS cert_cbs;
|
|
CRYPTO_BUFFER_init_CBS(cert, &cert_cbs);
|
|
if (ssl_cert_matches_issuer(&cert_cbs, &dn_cbs)) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool ssl_credential_st::HasPAKEAttempts() const {
|
|
return pake_limit.load() != 0;
|
|
}
|
|
|
|
bool ssl_credential_st::ClaimPAKEAttempt() const {
|
|
uint32_t current = pake_limit.load(std::memory_order_relaxed);
|
|
for (;;) {
|
|
if (current == 0) {
|
|
return false;
|
|
}
|
|
if (pake_limit.compare_exchange_weak(current, current - 1)) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void ssl_credential_st::RestorePAKEAttempt() const {
|
|
// This should not overflow because it will only be paired with
|
|
// ClaimPAKEAttempt.
|
|
pake_limit.fetch_add(1);
|
|
}
|
|
|
|
bool ssl_credential_st::AppendIntermediateCert(UniquePtr<CRYPTO_BUFFER> cert) {
|
|
if (!UsesX509()) {
|
|
OPENSSL_PUT_ERROR(SSL, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED);
|
|
return false;
|
|
}
|
|
|
|
if (chain == nullptr) {
|
|
chain = new_leafless_chain();
|
|
if (chain == nullptr) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return PushToStack(chain.get(), std::move(cert));
|
|
}
|
|
|
|
SSL_CREDENTIAL *SSL_CREDENTIAL_new_x509(void) {
|
|
return New<SSL_CREDENTIAL>(SSLCredentialType::kX509);
|
|
}
|
|
|
|
SSL_CREDENTIAL *SSL_CREDENTIAL_new_delegated(void) {
|
|
return New<SSL_CREDENTIAL>(SSLCredentialType::kDelegated);
|
|
}
|
|
|
|
void SSL_CREDENTIAL_up_ref(SSL_CREDENTIAL *cred) { cred->UpRefInternal(); }
|
|
|
|
void SSL_CREDENTIAL_free(SSL_CREDENTIAL *cred) {
|
|
if (cred != nullptr) {
|
|
cred->DecRefInternal();
|
|
}
|
|
}
|
|
|
|
int SSL_CREDENTIAL_set1_private_key(SSL_CREDENTIAL *cred, EVP_PKEY *key) {
|
|
if (!cred->UsesPrivateKey()) {
|
|
OPENSSL_PUT_ERROR(SSL, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED);
|
|
return 0;
|
|
}
|
|
|
|
// If the public half has been configured, check |key| matches. |pubkey| will
|
|
// have been extracted from the certificate, delegated credential, etc.
|
|
if (cred->pubkey != nullptr &&
|
|
!ssl_compare_public_and_private_key(cred->pubkey.get(), key)) {
|
|
return false;
|
|
}
|
|
|
|
cred->privkey = UpRef(key);
|
|
cred->key_method = nullptr;
|
|
return 1;
|
|
}
|
|
|
|
int SSL_CREDENTIAL_set_private_key_method(
|
|
SSL_CREDENTIAL *cred, const SSL_PRIVATE_KEY_METHOD *key_method) {
|
|
if (!cred->UsesPrivateKey()) {
|
|
OPENSSL_PUT_ERROR(SSL, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED);
|
|
return 0;
|
|
}
|
|
|
|
cred->privkey = nullptr;
|
|
cred->key_method = key_method;
|
|
return 1;
|
|
}
|
|
|
|
int SSL_CREDENTIAL_set1_cert_chain(SSL_CREDENTIAL *cred,
|
|
CRYPTO_BUFFER *const *certs,
|
|
size_t num_certs) {
|
|
if (!cred->UsesX509() || num_certs == 0) {
|
|
OPENSSL_PUT_ERROR(SSL, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED);
|
|
return 0;
|
|
}
|
|
|
|
if (!cred->SetLeafCert(UpRef(certs[0]), /*discard_key_on_mismatch=*/false)) {
|
|
return 0;
|
|
}
|
|
|
|
cred->ClearIntermediateCerts();
|
|
for (size_t i = 1; i < num_certs; i++) {
|
|
if (!cred->AppendIntermediateCert(UpRef(certs[i]))) {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
int SSL_CREDENTIAL_set1_delegated_credential(SSL_CREDENTIAL *cred,
|
|
CRYPTO_BUFFER *dc) {
|
|
if (cred->type != SSLCredentialType::kDelegated) {
|
|
OPENSSL_PUT_ERROR(SSL, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED);
|
|
return 0;
|
|
}
|
|
|
|
// Parse the delegated credential to check for validity, and extract a few
|
|
// fields from it. See RFC 9345, section 4.
|
|
CBS cbs, spki, sig;
|
|
uint32_t valid_time;
|
|
uint16_t dc_cert_verify_algorithm, algorithm;
|
|
CRYPTO_BUFFER_init_CBS(dc, &cbs);
|
|
if (!CBS_get_u32(&cbs, &valid_time) ||
|
|
!CBS_get_u16(&cbs, &dc_cert_verify_algorithm) ||
|
|
!CBS_get_u24_length_prefixed(&cbs, &spki) ||
|
|
!CBS_get_u16(&cbs, &algorithm) ||
|
|
!CBS_get_u16_length_prefixed(&cbs, &sig) || //
|
|
CBS_len(&sig) == 0 || //
|
|
CBS_len(&cbs) != 0) {
|
|
OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
|
|
return 0;
|
|
}
|
|
|
|
// RFC 9345 forbids algorithms that use the rsaEncryption OID. As the
|
|
// RSASSA-PSS OID is unusably complicated, this effectively means we will not
|
|
// support RSA delegated credentials.
|
|
if (SSL_get_signature_algorithm_key_type(dc_cert_verify_algorithm) ==
|
|
EVP_PKEY_RSA) {
|
|
OPENSSL_PUT_ERROR(SSL, SSL_R_INVALID_SIGNATURE_ALGORITHM);
|
|
return 0;
|
|
}
|
|
|
|
UniquePtr<EVP_PKEY> pubkey(EVP_parse_public_key(&spki));
|
|
if (pubkey == nullptr || CBS_len(&spki) != 0) {
|
|
OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
|
|
return 0;
|
|
}
|
|
|
|
if (!cred->sigalgs.CopyFrom(Span(&dc_cert_verify_algorithm, 1))) {
|
|
return 0;
|
|
}
|
|
|
|
if (cred->privkey != nullptr &&
|
|
!ssl_compare_public_and_private_key(pubkey.get(), cred->privkey.get())) {
|
|
return 0;
|
|
}
|
|
|
|
cred->dc = UpRef(dc);
|
|
cred->pubkey = std::move(pubkey);
|
|
cred->dc_algorithm = algorithm;
|
|
return 1;
|
|
}
|
|
|
|
int SSL_CREDENTIAL_set1_ocsp_response(SSL_CREDENTIAL *cred,
|
|
CRYPTO_BUFFER *ocsp) {
|
|
if (!cred->UsesX509()) {
|
|
OPENSSL_PUT_ERROR(SSL, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED);
|
|
return 0;
|
|
}
|
|
|
|
cred->ocsp_response = UpRef(ocsp);
|
|
return 1;
|
|
}
|
|
|
|
int SSL_CREDENTIAL_set1_signed_cert_timestamp_list(SSL_CREDENTIAL *cred,
|
|
CRYPTO_BUFFER *sct_list) {
|
|
if (!cred->UsesX509()) {
|
|
OPENSSL_PUT_ERROR(SSL, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED);
|
|
return 0;
|
|
}
|
|
|
|
CBS cbs;
|
|
CRYPTO_BUFFER_init_CBS(sct_list, &cbs);
|
|
if (!ssl_is_sct_list_valid(&cbs)) {
|
|
OPENSSL_PUT_ERROR(SSL, SSL_R_INVALID_SCT_LIST);
|
|
return 0;
|
|
}
|
|
|
|
cred->signed_cert_timestamp_list = UpRef(sct_list);
|
|
return 1;
|
|
}
|
|
|
|
int SSL_spake2plusv1_register(uint8_t out_w0[32], uint8_t out_w1[32],
|
|
uint8_t out_registration_record[65],
|
|
const uint8_t *password, size_t password_len,
|
|
const uint8_t *client_identity,
|
|
size_t client_identity_len,
|
|
const uint8_t *server_identity,
|
|
size_t server_identity_len) {
|
|
return spake2plus::Register(
|
|
Span(out_w0, 32), Span(out_w1, 32), Span(out_registration_record, 65),
|
|
Span(password, password_len), Span(client_identity, client_identity_len),
|
|
Span(server_identity, server_identity_len));
|
|
}
|
|
|
|
static UniquePtr<SSL_CREDENTIAL> ssl_credential_new_spake2plusv1(
|
|
SSLCredentialType type, Span<const uint8_t> context,
|
|
Span<const uint8_t> client_identity, Span<const uint8_t> server_identity,
|
|
uint32_t limit) {
|
|
assert(type == SSLCredentialType::kSPAKE2PlusV1Client ||
|
|
type == SSLCredentialType::kSPAKE2PlusV1Server);
|
|
auto cred = MakeUnique<SSL_CREDENTIAL>(type);
|
|
if (cred == nullptr) {
|
|
return nullptr;
|
|
}
|
|
|
|
if (!cred->pake_context.CopyFrom(context) ||
|
|
!cred->client_identity.CopyFrom(client_identity) ||
|
|
!cred->server_identity.CopyFrom(server_identity)) {
|
|
return nullptr;
|
|
}
|
|
|
|
cred->pake_limit.store(limit);
|
|
return cred;
|
|
}
|
|
|
|
SSL_CREDENTIAL *SSL_CREDENTIAL_new_spake2plusv1_client(
|
|
const uint8_t *context, size_t context_len, const uint8_t *client_identity,
|
|
size_t client_identity_len, const uint8_t *server_identity,
|
|
size_t server_identity_len, uint32_t error_limit, const uint8_t *w0,
|
|
size_t w0_len, const uint8_t *w1, size_t w1_len) {
|
|
if (w0_len != spake2plus::kVerifierSize ||
|
|
w1_len != spake2plus::kVerifierSize ||
|
|
(context == nullptr && context_len != 0)) {
|
|
OPENSSL_PUT_ERROR(SSL, SSL_R_INVALID_SPAKE2PLUSV1_VALUE);
|
|
return nullptr;
|
|
}
|
|
|
|
UniquePtr<SSL_CREDENTIAL> cred = ssl_credential_new_spake2plusv1(
|
|
SSLCredentialType::kSPAKE2PlusV1Client, Span(context, context_len),
|
|
Span(client_identity, client_identity_len),
|
|
Span(server_identity, server_identity_len), error_limit);
|
|
if (!cred) {
|
|
return nullptr;
|
|
}
|
|
|
|
if (!cred->password_verifier_w0.CopyFrom(Span(w0, w0_len)) ||
|
|
!cred->password_verifier_w1.CopyFrom(Span(w1, w1_len))) {
|
|
return nullptr;
|
|
}
|
|
|
|
return cred.release();
|
|
}
|
|
|
|
SSL_CREDENTIAL *SSL_CREDENTIAL_new_spake2plusv1_server(
|
|
const uint8_t *context, size_t context_len, const uint8_t *client_identity,
|
|
size_t client_identity_len, const uint8_t *server_identity,
|
|
size_t server_identity_len, uint32_t rate_limit, const uint8_t *w0,
|
|
size_t w0_len, const uint8_t *registration_record,
|
|
size_t registration_record_len) {
|
|
if (w0_len != spake2plus::kVerifierSize ||
|
|
registration_record_len != spake2plus::kRegistrationRecordSize ||
|
|
(context == nullptr && context_len != 0)) {
|
|
return nullptr;
|
|
}
|
|
|
|
UniquePtr<SSL_CREDENTIAL> cred = ssl_credential_new_spake2plusv1(
|
|
SSLCredentialType::kSPAKE2PlusV1Server, Span(context, context_len),
|
|
Span(client_identity, client_identity_len),
|
|
Span(server_identity, server_identity_len), rate_limit);
|
|
if (!cred) {
|
|
return nullptr;
|
|
}
|
|
|
|
if (!cred->password_verifier_w0.CopyFrom(Span(w0, w0_len)) ||
|
|
!cred->registration_record.CopyFrom(
|
|
Span(registration_record, registration_record_len))) {
|
|
return nullptr;
|
|
}
|
|
|
|
return cred.release();
|
|
}
|
|
|
|
int SSL_CTX_add1_credential(SSL_CTX *ctx, SSL_CREDENTIAL *cred) {
|
|
if (!cred->IsComplete()) {
|
|
OPENSSL_PUT_ERROR(SSL, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED);
|
|
return 0;
|
|
}
|
|
return ctx->cert->credentials.Push(UpRef(cred));
|
|
}
|
|
|
|
int SSL_add1_credential(SSL *ssl, SSL_CREDENTIAL *cred) {
|
|
if (ssl->config == nullptr) {
|
|
return 0;
|
|
}
|
|
|
|
if (!cred->IsComplete()) {
|
|
OPENSSL_PUT_ERROR(SSL, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED);
|
|
return 0;
|
|
}
|
|
return ssl->config->cert->credentials.Push(UpRef(cred));
|
|
}
|
|
|
|
const SSL_CREDENTIAL *SSL_get0_selected_credential(const SSL *ssl) {
|
|
if (ssl->s3->hs == nullptr) {
|
|
return nullptr;
|
|
}
|
|
return ssl->s3->hs->credential.get();
|
|
}
|
|
|
|
int SSL_CREDENTIAL_get_ex_new_index(long argl, void *argp,
|
|
CRYPTO_EX_unused *unused,
|
|
CRYPTO_EX_dup *dup_unused,
|
|
CRYPTO_EX_free *free_func) {
|
|
return CRYPTO_get_ex_new_index_ex(&g_ex_data_class, argl, argp, free_func);
|
|
}
|
|
|
|
int SSL_CREDENTIAL_set_ex_data(SSL_CREDENTIAL *cred, int idx, void *arg) {
|
|
return CRYPTO_set_ex_data(&cred->ex_data, idx, arg);
|
|
}
|
|
|
|
void *SSL_CREDENTIAL_get_ex_data(const SSL_CREDENTIAL *cred, int idx) {
|
|
return CRYPTO_get_ex_data(&cred->ex_data, idx);
|
|
}
|
|
|
|
void SSL_CREDENTIAL_set_must_match_issuer(SSL_CREDENTIAL *cred, int match) {
|
|
cred->must_match_issuer = !!match;
|
|
}
|
|
|
|
int SSL_CREDENTIAL_set1_trust_anchor_id(SSL_CREDENTIAL *cred, const uint8_t *id,
|
|
size_t id_len) {
|
|
// For now, this is only valid for X.509.
|
|
if (!cred->UsesX509()) {
|
|
OPENSSL_PUT_ERROR(SSL, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED);
|
|
return 0;
|
|
}
|
|
|
|
if (!cred->trust_anchor_id.CopyFrom(Span(id, id_len))) {
|
|
OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
int SSL_CREDENTIAL_set1_certificate_properties(
|
|
SSL_CREDENTIAL *cred, CRYPTO_BUFFER *cert_property_list) {
|
|
std::optional<CBS> trust_anchor;
|
|
CBS cbs, cpl;
|
|
CRYPTO_BUFFER_init_CBS(cert_property_list, &cbs);
|
|
|
|
if (!CBS_get_u16_length_prefixed(&cbs, &cpl)) {
|
|
OPENSSL_PUT_ERROR(SSL, SSL_R_INVALID_CERTIFICATE_PROPERTY_LIST);
|
|
return 0;
|
|
}
|
|
while (CBS_len(&cpl) != 0) {
|
|
uint16_t cp_type;
|
|
CBS cp_data;
|
|
if (!CBS_get_u16(&cpl, &cp_type) ||
|
|
!CBS_get_u16_length_prefixed(&cpl, &cp_data)) {
|
|
OPENSSL_PUT_ERROR(SSL, SSL_R_INVALID_CERTIFICATE_PROPERTY_LIST);
|
|
return 0;
|
|
}
|
|
switch (cp_type) {
|
|
case 0: // trust anchor identifier.
|
|
if (trust_anchor.has_value()) {
|
|
OPENSSL_PUT_ERROR(SSL, SSL_R_INVALID_CERTIFICATE_PROPERTY_LIST);
|
|
return 0;
|
|
}
|
|
trust_anchor = cp_data;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
if (CBS_len(&cbs) != 0) {
|
|
OPENSSL_PUT_ERROR(SSL, SSL_R_INVALID_CERTIFICATE_PROPERTY_LIST);
|
|
return 0;
|
|
}
|
|
// Certificate property list has parsed correctly.
|
|
|
|
// We do not currently retain |cert_property_list|, but if we define another
|
|
// property with larger fields (e.g. stapled SCTs), it may make sense for
|
|
// those fields to retain |cert_property_list| and alias into it.
|
|
if (trust_anchor.has_value()) {
|
|
if (!CBS_len(&trust_anchor.value())) {
|
|
OPENSSL_PUT_ERROR(SSL, SSL_R_INVALID_TRUST_ANCHOR_LIST);
|
|
return 0;
|
|
}
|
|
if (!SSL_CREDENTIAL_set1_trust_anchor_id(cred,
|
|
CBS_data(&trust_anchor.value()),
|
|
CBS_len(&trust_anchor.value()))) {
|
|
return 0;
|
|
}
|
|
}
|
|
return 1;
|
|
}
|