Skip to content

Commit 4ba7ce8

Browse files
Fix RSA->Ed25519 crosscert: use raw RSA signing to match Tor's format
Tor's rsa_ed25519_crosscert_check uses RSA_public_decrypt (raw RSA) to recover a 32-byte SHA256 hash from the signature, then compares it with SHA256(prefix || ed_key || expiration). Our code was using EVP_DigestSign(SHA-256) which produces a PKCS#1v1.5 signature with DigestInfo wrapping (~51 bytes when decrypted), causing Tor to report: "The signature was good, but it didn't match the data". Fix: compute SHA256 ourselves, then sign the raw 32-byte hash using EVP_PKEY_sign with RSA_PKCS1_PADDING and no digest (equivalent to Tor's crypto_pk_private_sign / RSA_private_encrypt). Also removes diagnostic debug logging from CERTS cell creation. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 0fd7ba8 commit 4ba7ce8

File tree

3 files changed

+69
-53
lines changed

3 files changed

+69
-53
lines changed

include/tor/crypto/keys.hpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,11 @@ class Rsa1024Identity {
177177
[[nodiscard]] std::expected<std::vector<uint8_t>, KeyError>
178178
sign_sha256(std::span<const uint8_t> data) const;
179179

180+
// Raw RSA signature (PKCS1 padding, no digest wrapping)
181+
// Equivalent to Tor's crypto_pk_private_sign / RSA_private_encrypt
182+
[[nodiscard]] std::expected<std::vector<uint8_t>, KeyError>
183+
sign_raw(std::span<const uint8_t> data) const;
184+
180185
// Create self-signed X.509 identity cert (Type 2)
181186
[[nodiscard]] std::expected<std::vector<uint8_t>, KeyError>
182187
create_identity_cert() const;

src/crypto/keys.cpp

Lines changed: 63 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#include <openssl/err.h>
66
#include <openssl/x509.h>
77
#include <openssl/rsa.h>
8+
#include <openssl/sha.h>
89
#include <cstring>
910
#include <chrono>
1011
#include <iomanip>
@@ -647,6 +648,50 @@ Rsa1024Identity::create_link_cert(EVP_PKEY* tls_pkey) const {
647648
return der;
648649
}
649650

651+
std::expected<std::vector<uint8_t>, KeyError>
652+
Rsa1024Identity::sign_raw(std::span<const uint8_t> data) const {
653+
if (!pkey_) {
654+
return std::unexpected(KeyError::InvalidKey);
655+
}
656+
657+
// Raw RSA signing with PKCS1 padding (no digest wrapping).
658+
// Equivalent to Tor's crypto_pk_private_sign which calls
659+
// RSA_private_encrypt(data, RSA_PKCS1_PADDING).
660+
EVP_PKEY_CTX* ctx = EVP_PKEY_CTX_new(pkey_, nullptr);
661+
if (!ctx) {
662+
return std::unexpected(KeyError::SigningFailed);
663+
}
664+
665+
if (EVP_PKEY_sign_init(ctx) <= 0) {
666+
EVP_PKEY_CTX_free(ctx);
667+
return std::unexpected(KeyError::SigningFailed);
668+
}
669+
670+
if (EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_PADDING) <= 0) {
671+
EVP_PKEY_CTX_free(ctx);
672+
return std::unexpected(KeyError::SigningFailed);
673+
}
674+
675+
// No digest set — signs the raw data directly
676+
677+
// Get required signature length
678+
size_t sig_len = 0;
679+
if (EVP_PKEY_sign(ctx, nullptr, &sig_len, data.data(), data.size()) <= 0) {
680+
EVP_PKEY_CTX_free(ctx);
681+
return std::unexpected(KeyError::SigningFailed);
682+
}
683+
684+
std::vector<uint8_t> sig(sig_len);
685+
if (EVP_PKEY_sign(ctx, sig.data(), &sig_len, data.data(), data.size()) <= 0) {
686+
EVP_PKEY_CTX_free(ctx);
687+
return std::unexpected(KeyError::SigningFailed);
688+
}
689+
690+
EVP_PKEY_CTX_free(ctx);
691+
sig.resize(sig_len);
692+
return sig;
693+
}
694+
650695
std::expected<std::vector<uint8_t>, KeyError>
651696
Rsa1024Identity::create_ed25519_cross_cert(const Ed25519PublicKey& ed_pub) const {
652697
if (!pkey_) {
@@ -657,28 +702,33 @@ Rsa1024Identity::create_ed25519_cross_cert(const Ed25519PublicKey& ed_pub) const
657702
// ED25519_KEY (32 bytes) || EXPIRATION_DATE (4 bytes, hours since epoch)
658703
// || SIGLEN (1 byte) || RSA_SIG (variable, 128 bytes for 1024-bit key)
659704
//
660-
// Signature covers:
661-
// "Tor TLS RSA/Ed25519 cross-certificate" || ED25519_KEY || EXPIRATION
705+
// Tor signs SHA256(prefix || ED25519_KEY || EXPIRATION) with raw RSA
706+
// (RSA_private_encrypt of the 32-byte hash, NOT RSA-SHA256 DigestInfo).
662707

663708
auto now = std::chrono::system_clock::now();
664709
auto secs = std::chrono::duration_cast<std::chrono::seconds>(
665710
now.time_since_epoch()).count();
666711
uint32_t exp_hours = static_cast<uint32_t>((secs / 3600) + 24);
667712

668-
// Build the data to sign
713+
// Build the data to hash
669714
const std::string prefix = "Tor TLS RSA/Ed25519 cross-certificate";
670-
std::vector<uint8_t> to_sign;
671-
to_sign.insert(to_sign.end(), prefix.begin(), prefix.end());
715+
std::vector<uint8_t> to_hash;
716+
to_hash.insert(to_hash.end(), prefix.begin(), prefix.end());
672717
auto ed_data = ed_pub.as_span();
673-
to_sign.insert(to_sign.end(), ed_data.begin(), ed_data.end());
718+
to_hash.insert(to_hash.end(), ed_data.begin(), ed_data.end());
674719
// Expiration in network byte order (big-endian)
675-
to_sign.push_back(static_cast<uint8_t>((exp_hours >> 24) & 0xFF));
676-
to_sign.push_back(static_cast<uint8_t>((exp_hours >> 16) & 0xFF));
677-
to_sign.push_back(static_cast<uint8_t>((exp_hours >> 8) & 0xFF));
678-
to_sign.push_back(static_cast<uint8_t>(exp_hours & 0xFF));
679-
680-
// RSA-PKCS1v1.5-SHA256 signature
681-
auto sig = sign_sha256(to_sign);
720+
to_hash.push_back(static_cast<uint8_t>((exp_hours >> 24) & 0xFF));
721+
to_hash.push_back(static_cast<uint8_t>((exp_hours >> 16) & 0xFF));
722+
to_hash.push_back(static_cast<uint8_t>((exp_hours >> 8) & 0xFF));
723+
to_hash.push_back(static_cast<uint8_t>(exp_hours & 0xFF));
724+
725+
// Compute SHA256 digest
726+
std::array<uint8_t, 32> digest;
727+
SHA256(to_hash.data(), to_hash.size(), digest.data());
728+
729+
// Raw RSA signature of the 32-byte SHA256 hash
730+
// (matches Tor's crypto_pk_private_sign which uses RSA_private_encrypt)
731+
auto sig = sign_raw(digest);
682732
if (!sig) {
683733
return std::unexpected(sig.error());
684734
}

src/protocol/link_protocol.cpp

Lines changed: 1 addition & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,8 @@
44
#include <algorithm>
55
#include <chrono>
66
#include <cstring>
7-
#include <openssl/evp.h>
87
#include <openssl/rand.h>
98
#include <openssl/sha.h>
10-
#include <openssl/x509.h>
119

1210
namespace tor::protocol {
1311

@@ -162,27 +160,6 @@ CertsHandler::create_certs_cell(
162160
LOG_WARN("OR CERTS: failed to create RSA identity cert");
163161
return std::unexpected(LinkProtocolError::CertificateError);
164162
}
165-
LOG_INFO("OR CERTS: Type 2 RSA identity cert: {} bytes", cert2->size());
166-
167-
// Debug: verify the cert round-trips correctly
168-
{
169-
const unsigned char* p = cert2->data();
170-
X509* x509_check = d2i_X509(nullptr, &p, static_cast<long>(cert2->size()));
171-
if (x509_check) {
172-
EVP_PKEY* pk = X509_get0_pubkey(x509_check);
173-
if (pk) {
174-
int key_type = EVP_PKEY_base_id(pk);
175-
int key_bits = EVP_PKEY_bits(pk);
176-
LOG_INFO("OR CERTS: Type 2 cert key: type={} bits={} (expect type=6[RSA] bits=1024)",
177-
key_type, key_bits);
178-
} else {
179-
LOG_WARN("OR CERTS: Type 2 cert has NULL public key!");
180-
}
181-
X509_free(x509_check);
182-
} else {
183-
LOG_WARN("OR CERTS: Type 2 cert failed DER parse!");
184-
}
185-
}
186163

187164
// Type 4: Ed25519 signing key, certified by identity key
188165
auto cert4 = build_ed25519_cert(
@@ -227,23 +204,7 @@ CertsHandler::create_certs_cell(
227204
payload.write_u16(static_cast<uint16_t>(cert7->size()));
228205
payload.write_bytes(*cert7);
229206

230-
auto payload_data = payload.take();
231-
LOG_INFO("OR CERTS: total payload {} bytes: N={} cert2={} cert4={} cert5={} cert7={}",
232-
payload_data.size(), 4, cert2->size(), cert4.size(), cert5.size(), cert7->size());
233-
234-
// Dump first 20 hex bytes of the payload for debugging
235-
{
236-
std::string hex;
237-
for (size_t i = 0; i < std::min(payload_data.size(), size_t(40)); ++i) {
238-
char buf[4];
239-
snprintf(buf, sizeof(buf), "%02x", payload_data[i]);
240-
hex += buf;
241-
if (i < 39) hex += " ";
242-
}
243-
LOG_INFO("OR CERTS: payload hex: {}", hex);
244-
}
245-
246-
return core::VariableCell(0, core::CellCommand::CERTS, std::move(payload_data));
207+
return core::VariableCell(0, core::CellCommand::CERTS, payload.take());
247208
}
248209

249210
std::expected<std::vector<crypto::TorCertificate>, LinkProtocolError>

0 commit comments

Comments
 (0)