| // Copyright 2026 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 <assert.h> | |
| #include <stdint.h> | |
| #include <string.h> | |
| #include <openssl/base.h> | |
| #include <openssl/bytestring.h> | |
| #include <openssl/curve25519.h> | |
| #include <openssl/ec.h> | |
| #include <openssl/err.h> | |
| #include <openssl/mem.h> | |
| #include <openssl/rand.h> | |
| #include <openssl/sha2.h> | |
| #include <openssl/span.h> | |
| #include "../internal.h" | |
| #include "../mem_internal.h" | |
| #include "./internal.h" | |
| CPACE_CTX *CPACE_CTX_new(enum cpace_role_t role, const uint8_t *password, | |
| size_t password_len, const uint8_t *channel_id, | |
| size_t channel_id_len, const uint8_t *assoc_data, | |
| size_t assoc_data_len, const uint8_t *session_id, | |
| size_t session_id_len) { | |
| bssl::UniquePtr<bssl::CpaceCtx> ctx = bssl::MakeUnique<bssl::CpaceCtx>(); | |
| if (ctx == nullptr) { | |
| return nullptr; | |
| } | |
| ctx->our_role = role; | |
| if (!ctx->our_password.CopyFrom(bssl::Span(password, password_len)) || | |
| !ctx->channel_id.CopyFrom(bssl::Span(channel_id, channel_id_len)) || | |
| !ctx->our_aad.CopyFrom(bssl::Span(assoc_data, assoc_data_len)) || | |
| !ctx->session_id.CopyFrom(bssl::Span(session_id, session_id_len))) { | |
| return nullptr; | |
| } | |
| return ctx.release(); | |
| } | |
| void CPACE_CTX_free(CPACE_CTX *ctx) { Delete(bssl::FromOpaque(ctx)); } | |
| namespace { | |
| constexpr uint64_t leb128_len(uint64_t len) { | |
| if (len < 128) { | |
| return 1; | |
| } | |
| return (bssl::CRYPTO_bit_width(len) + 6) / 7; | |
| } | |
| constexpr size_t kMaxLenTagSize = leb128_len(UINT64_MAX); | |
| constexpr std::string_view kDSI = "CPace255"; | |
| constexpr size_t leb128_tag(uint64_t len, uint8_t tag[kMaxLenTagSize]) { | |
| size_t tag_len = leb128_len(len); | |
| for (size_t i = 0; i < tag_len; ++i) { | |
| if (len < 128) { | |
| tag[i] = len; | |
| } else { | |
| tag[i] = (len & 0x7f) | 0x80; | |
| len >>= 7; | |
| } | |
| } | |
| return tag_len; | |
| } | |
| void push_lv_to_hasher(SHA512_CTX &hasher, bssl::Span<const uint8_t> data) { | |
| uint8_t lv_tag[kMaxLenTagSize]; | |
| size_t lv_tag_len = leb128_tag(data.size(), lv_tag); | |
| SHA512_Update(&hasher, lv_tag, lv_tag_len); | |
| SHA512_Update(&hasher, data.data(), data.size()); | |
| } | |
| } // namespace | |
| // This implements the first two steps of G.calculate_generator in Section 8.2 | |
| // of draft-irtf-cfrg-cpace-21. | |
| void bssl::CpaceCtx::ComputeGeneratorStrHash( | |
| uint8_t out_gen_str_hash[32]) const { | |
| // len_zpad = max(0,s_in_bytes - 1 - len(prepend_len(PRS)) - | |
| // len(prepend_len(DSI))) | |
| size_t digest_len = leb128_len(our_password.size()) + our_password.size() // | |
| + leb128_len(kDSI.length()) + kDSI.length(); | |
| size_t len_zpad = 0; | |
| if (digest_len < SHA512_CBLOCK - 1) { | |
| len_zpad = SHA512_CBLOCK - 1 - digest_len; | |
| } | |
| constexpr uint8_t kZeros[SHA512_CBLOCK] = {}; | |
| // gen_str = generator_string(G.DSI, PRS, CI, sid, H.s_in_bytes) | |
| SHA512_CTX hasher; | |
| SHA512_Init(&hasher); | |
| push_lv_to_hasher(hasher, StringAsBytes(kDSI)); | |
| push_lv_to_hasher(hasher, our_password); | |
| push_lv_to_hasher(hasher, Span(kZeros, len_zpad)); | |
| push_lv_to_hasher(hasher, channel_id); | |
| push_lv_to_hasher(hasher, session_id); | |
| uint8_t out[SHA512_DIGEST_LENGTH]; | |
| // gen_str_hash = H.hash(gen_str, G.field_size_bytes). | |
| SHA512_Final(out, &hasher); | |
| OPENSSL_memcpy(out_gen_str_hash, out, 32); | |
| } | |
| bool bssl::CpaceCtx::ComputeMessage(uint8_t msg[CPACE_MSG_LEN]) { | |
| if (state != cpace_state_init) { | |
| OPENSSL_PUT_ERROR(CRYPTO, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED); | |
| return false; | |
| } | |
| uint8_t gen_str_hash[32]; | |
| // We now compute the first two steps of G.calculate_generator(H, PRS, sid, | |
| // CI). | |
| ComputeGeneratorStrHash(gen_str_hash); | |
| // This result is then considered as a field coordinate using the u = | |
| // decodeUCoordinate(gen_str_hash, G.field_size_bits) function from RFC7748. | |
| uint8_t g[32], v[32]; | |
| map_to_curve_curve25519_elligator2(g, v, gen_str_hash); | |
| // sample_scalar() = sample_random_bytes(G.field_size_bytes) | |
| RAND_bytes(our_private_key, 32); | |
| auto rc = X25519(our_public_key, our_private_key, g); | |
| OPENSSL_memcpy(msg, our_public_key, 32); | |
| if (!rc) { | |
| // This will happen with negligible probability. | |
| OPENSSL_PUT_ERROR(CRYPTO, ERR_R_INTERNAL_ERROR); | |
| return false; | |
| } | |
| state = cpace_state_msg_generated; | |
| return true; | |
| } | |
| int CPACE_generate_msg(CPACE_CTX *ctx, uint8_t out_msg[CPACE_MSG_LEN]) { | |
| auto *impl = bssl::FromOpaque(ctx); | |
| return impl->ComputeMessage(out_msg); | |
| } | |
| bool bssl::CpaceCtx::ComputeISK( | |
| const uint8_t peer_msg[CPACE_MSG_LEN], Span<const uint8_t> peer_assoc_data, | |
| uint8_t out_shared_secret[CPACE_SHARED_SECRET_LEN], | |
| uint8_t out_sid[SHA512_DIGEST_LENGTH]) { | |
| if (state != cpace_state_msg_generated) { | |
| OPENSSL_PUT_ERROR(CRYPTO, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED); | |
| return false; | |
| } | |
| state = cpace_state_key_generated; | |
| uint8_t k[32]; | |
| if (X25519(k, our_private_key, peer_msg) != 1) { | |
| // TODO(crbug.com/42290066): This is another instance where unifying the | |
| // reason code and library code into one error code makes most sense. | |
| OPENSSL_PUT_ERROR(EC, EC_R_DECODE_ERROR); | |
| return false; | |
| } | |
| // ISK = H.hash(lv_cat(G.DSI || b"_ISK", sid, K)||transcript(Ya,ADa,Yb,ADb)) | |
| static constexpr std::string_view kDsiIsk = "CPace255_ISK"; | |
| static constexpr std::string_view kDsiSidOut = "CPaceSidOutput"; | |
| SHA512_CTX hasher, sid_hasher; | |
| SHA512_Init(&hasher); | |
| const bool compute_sid = out_sid != nullptr; | |
| if (compute_sid) { | |
| SHA512_Init(&sid_hasher); | |
| SHA512_Update(&sid_hasher, kDsiSidOut.data(), kDsiSidOut.size()); | |
| } | |
| push_lv_to_hasher(hasher, StringAsBytes(kDsiIsk)); | |
| push_lv_to_hasher(hasher, session_id); | |
| push_lv_to_hasher(hasher, Span(k, 32)); | |
| Span peer_public_key(peer_msg, 32); | |
| auto push_our_transcript = [&] { | |
| push_lv_to_hasher(hasher, our_public_key); | |
| push_lv_to_hasher(hasher, our_aad); | |
| if (compute_sid) { | |
| push_lv_to_hasher(sid_hasher, our_public_key); | |
| push_lv_to_hasher(sid_hasher, our_aad); | |
| } | |
| }; | |
| auto push_peer_transcript = [&] { | |
| push_lv_to_hasher(hasher, peer_public_key); | |
| push_lv_to_hasher(hasher, peer_assoc_data); | |
| if (compute_sid) { | |
| push_lv_to_hasher(sid_hasher, peer_public_key); | |
| push_lv_to_hasher(sid_hasher, peer_assoc_data); | |
| } | |
| }; | |
| switch (our_role) { | |
| case cpace_role_initiator: { | |
| push_our_transcript(); | |
| push_peer_transcript(); | |
| break; | |
| } | |
| case cpace_role_responder: { | |
| push_peer_transcript(); | |
| push_our_transcript(); | |
| break; | |
| } | |
| } | |
| if (compute_sid) { | |
| SHA512_Final(out_sid, &sid_hasher); | |
| } | |
| return SHA512_Final(out_shared_secret, &hasher); | |
| } | |
| // CPACE_process_msg processes a received message and finalize | |
| int CPACE_process_msg(CPACE_CTX *ctx, const uint8_t msg[CPACE_MSG_LEN], | |
| const uint8_t *peer_assoc_data, | |
| size_t peer_assoc_data_len, | |
| uint8_t out_shared_secret[CPACE_SHARED_SECRET_LEN], | |
| uint8_t out_sid[SHA512_DIGEST_LENGTH]) { | |
| auto *impl = bssl::FromOpaque(ctx); | |
| return impl->ComputeISK(msg, bssl::Span(peer_assoc_data, peer_assoc_data_len), | |
| out_shared_secret, out_sid); | |
| } |