Skip to content

Commit

Permalink
Support all Certificate Fingerprint Algorithms
Browse files Browse the repository at this point in the history
Before was hardcoded to sha256

Resolves #1076
  • Loading branch information
Sean-Der committed Jan 8, 2024
1 parent 715e296 commit 2742626
Show file tree
Hide file tree
Showing 10 changed files with 230 additions and 77 deletions.
18 changes: 14 additions & 4 deletions include/rtc/description.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,15 @@ const string DEFAULT_OPUS_AUDIO_PROFILE =
const string DEFAULT_H264_VIDEO_PROFILE =
"profile-level-id=42e01f;packetization-mode=1;level-asymmetry-allowed=1";

struct CertificateFingerprint {
enum Algorithm { Sha1, Sha224, Sha256, Sha384, Sha512 };
static string AlgorithmIdentifier(Algorithm algorithm);
static size_t AlgorithmSize(Algorithm algorithm);

Algorithm algorithm;
string value;
};

class RTC_CPP_EXPORT Description {
public:
enum class Type { Unspec, Offer, Answer, Pranswer, Rollback };
Expand All @@ -51,11 +60,11 @@ class RTC_CPP_EXPORT Description {
std::vector<string> iceOptions() const;
optional<string> iceUfrag() const;
optional<string> icePwd() const;
optional<string> fingerprint() const;
optional<CertificateFingerprint> fingerprint() const;
bool ended() const;

void hintType(Type type);
void setFingerprint(string fingerprint);
void setFingerprint(string fingerprint, CertificateFingerprint::Algorithm fingerprintAlgorithm);
void addIceOption(string option);
void removeIceOption(const string &option);

Expand Down Expand Up @@ -291,7 +300,7 @@ class RTC_CPP_EXPORT Description {
string mSessionId;
std::vector<string> mIceOptions;
optional<string> mIceUfrag, mIcePwd;
optional<string> mFingerprint;
optional<CertificateFingerprint> mFingerprint;
std::vector<string> mAttributes; // other attributes

// Entries
Expand All @@ -308,6 +317,7 @@ class RTC_CPP_EXPORT Description {
RTC_CPP_EXPORT std::ostream &operator<<(std::ostream &out, const rtc::Description &description);
RTC_CPP_EXPORT std::ostream &operator<<(std::ostream &out, rtc::Description::Type type);
RTC_CPP_EXPORT std::ostream &operator<<(std::ostream &out, rtc::Description::Role role);
RTC_CPP_EXPORT std::ostream &operator<<(std::ostream &out, const rtc::Description::Direction &direction);
RTC_CPP_EXPORT std::ostream &operator<<(std::ostream &out,
const rtc::Description::Direction &direction);

#endif
102 changes: 81 additions & 21 deletions src/description.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,6 @@ inline bool match_prefix(string_view str, string_view prefix) {
std::mismatch(prefix.begin(), prefix.end(), str.begin()).first == prefix.end();
}

inline void trim_begin(string &str) {
str.erase(str.begin(),
std::find_if(str.begin(), str.end(), [](char c) { return !std::isspace(c); }));
}

inline void trim_end(string &str) {
str.erase(
std::find_if(str.rbegin(), str.rend(), [](char c) { return !std::isspace(c); }).base(),
Expand Down Expand Up @@ -71,9 +66,10 @@ template <typename T> T to_integer(string_view s) {
}
}

inline bool is_sha256_fingerprint(string_view f) {
if (f.size() != 32 * 3 - 1)
inline bool is_valid_fingerprint(string_view f, size_t expectedSize) {
if (f.size() != expectedSize * 3 - 1) {
return false;
}

for (size_t i = 0; i < f.size(); ++i) {
if (i % 3 == 2) {
Expand Down Expand Up @@ -131,12 +127,35 @@ Description::Description(const string &sdp, Type type, Role role)
// media-level SDP attribute. If it is a session-level attribute, it applies to all
// TLS sessions for which no media-level fingerprint attribute is defined.
if (!mFingerprint || index == 0) { // first media overrides session-level
if (match_prefix(value, "sha-256 ") || match_prefix(value, "SHA-256 ")) {
string fingerprint{value.substr(8)};
trim_begin(fingerprint);
setFingerprint(std::move(fingerprint));
} else {
auto fingerprintExploded = utils::explode(string(value), ' ');
if (fingerprintExploded.size() != 2) {
PLOG_WARNING << "Unknown SDP fingerprint format: " << value;
continue;
}

auto first = fingerprintExploded.at(0);
std::transform(first.begin(), first.end(), first.begin(),
[](char c) { return char(std::tolower(c)); });

std::optional<CertificateFingerprint::Algorithm> fingerprintAlgorithm;

for (auto a : std::vector<CertificateFingerprint::Algorithm>{
CertificateFingerprint::Algorithm::Sha1,
CertificateFingerprint::Algorithm::Sha224,
CertificateFingerprint::Algorithm::Sha256,
CertificateFingerprint::Algorithm::Sha384,
CertificateFingerprint::Algorithm::Sha512}) {
if (first == CertificateFingerprint::AlgorithmIdentifier(a)) {
fingerprintAlgorithm = a;
break;
}
}

if (fingerprintAlgorithm.has_value()) {
setFingerprint(std::move(fingerprintExploded.at(1)),
fingerprintAlgorithm.value());
} else {
PLOG_WARNING << "Unknown certificate fingerprint algorithm: " << first;
}
}
} else if (key == "ice-ufrag") {
Expand Down Expand Up @@ -205,7 +224,7 @@ std::vector<string> Description::iceOptions() const { return mIceOptions; }

optional<string> Description::icePwd() const { return mIcePwd; }

optional<string> Description::fingerprint() const { return mFingerprint; }
optional<CertificateFingerprint> Description::fingerprint() const { return mFingerprint; }

bool Description::ended() const { return mEnded; }

Expand All @@ -214,13 +233,15 @@ void Description::hintType(Type type) {
mType = type;
}

void Description::setFingerprint(string fingerprint) {
if (!is_sha256_fingerprint(fingerprint))
throw std::invalid_argument("Invalid SHA256 fingerprint \"" + fingerprint + "\"");
void Description::setFingerprint(string fingerprint,
CertificateFingerprint::Algorithm fingerprintAlgorithm) {
if (!is_valid_fingerprint(fingerprint,
CertificateFingerprint::AlgorithmSize(fingerprintAlgorithm)))
throw std::invalid_argument("Invalid certificate fingerprint \"" + fingerprint + "\"");

std::transform(fingerprint.begin(), fingerprint.end(), fingerprint.begin(),
[](char c) { return char(std::toupper(c)); });
mFingerprint.emplace(std::move(fingerprint));
mFingerprint = CertificateFingerprint{fingerprintAlgorithm, std::move(fingerprint)};
}

void Description::addIceOption(string option) {
Expand Down Expand Up @@ -315,7 +336,9 @@ string Description::generateSdp(string_view eol) const {
if (!mIceOptions.empty())
sdp << "a=ice-options:" << utils::implode(mIceOptions, ',') << eol;
if (mFingerprint)
sdp << "a=fingerprint:sha-256 " << *mFingerprint << eol;
sdp << "a=fingerprint:"
<< CertificateFingerprint::AlgorithmIdentifier(mFingerprint.value().algorithm) << " "
<< mFingerprint.value().value << eol;

for (const auto &attr : mAttributes)
sdp << "a=" << attr << eol;
Expand Down Expand Up @@ -378,7 +401,9 @@ string Description::generateApplicationSdp(string_view eol) const {
if (!mIceOptions.empty())
sdp << "a=ice-options:" << utils::implode(mIceOptions, ',') << eol;
if (mFingerprint)
sdp << "a=fingerprint:sha-256 " << *mFingerprint << eol;
sdp << "a=fingerprint:"
<< CertificateFingerprint::AlgorithmIdentifier(mFingerprint.value().algorithm) << " "
<< mFingerprint.value().value << eol;

for (const auto &attr : mAttributes)
sdp << "a=" << attr << eol;
Expand Down Expand Up @@ -876,8 +901,7 @@ void Description::Application::parseSdpLine(string_view line) {
}
}

Description::Media::Media(const string &sdp)
: Entry(get_first_line(sdp), "", Direction::Unknown) {
Description::Media::Media(const string &sdp) : Entry(get_first_line(sdp), "", Direction::Unknown) {
string line;
std::istringstream ss(sdp);
std::getline(ss, line); // discard first line
Expand Down Expand Up @@ -1288,6 +1312,42 @@ string Description::typeToString(Type type) {
}
}

size_t
CertificateFingerprint::AlgorithmSize(CertificateFingerprint::Algorithm fingerprintAlgorithm) {
switch (fingerprintAlgorithm) {
case CertificateFingerprint::Algorithm::Sha1:
return 20;
case CertificateFingerprint::Algorithm::Sha224:
return 28;
case CertificateFingerprint::Algorithm::Sha256:
return 32;
case CertificateFingerprint::Algorithm::Sha384:
return 48;
case CertificateFingerprint::Algorithm::Sha512:
return 64;
}

return 0;
}

std::string CertificateFingerprint::AlgorithmIdentifier(
CertificateFingerprint::Algorithm fingerprintAlgorithm) {
switch (fingerprintAlgorithm) {
case CertificateFingerprint::Algorithm::Sha1:
return "sha-1";
case CertificateFingerprint::Algorithm::Sha224:
return "sha-224";
case CertificateFingerprint::Algorithm::Sha256:
return "sha-256";
case CertificateFingerprint::Algorithm::Sha384:
return "sha-256";
case CertificateFingerprint::Algorithm::Sha512:
return "sha-512";
}

return "";
}

} // namespace rtc

std::ostream &operator<<(std::ostream &out, const rtc::Description &description) {
Expand Down
112 changes: 88 additions & 24 deletions src/impl/certificate.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -100,18 +100,20 @@ Certificate Certificate::Generate(CertificateType type, const string &commonName

Certificate::Certificate(gnutls_x509_crt_t crt, gnutls_x509_privkey_t privkey)
: mCredentials(gnutls::new_credentials(), gnutls::free_credentials),
mFingerprint(make_fingerprint(crt)) {
mFingerprint(make_fingerprint(crt, CertificateFingerprint::Algorithm::Sha256)) {

gnutls::check(gnutls_certificate_set_x509_key(*mCredentials, &crt, 1, privkey),
"Unable to set certificate and key pair in credentials");
}

Certificate::Certificate(shared_ptr<gnutls_certificate_credentials_t> creds)
: mCredentials(std::move(creds)), mFingerprint(make_fingerprint(*mCredentials)) {}
: mCredentials(std::move(creds)),
mFingerprint(make_fingerprint(*mCredentials, CertificateFingerprint::Algorithm::Sha256)) {}

gnutls_certificate_credentials_t Certificate::credentials() const { return *mCredentials; }

string make_fingerprint(gnutls_certificate_credentials_t credentials) {
string make_fingerprint(gnutls_certificate_credentials_t credentials,
CertificateFingerprint::Algorithm fingerprintAlgorithm) {
auto new_crt_list = [credentials]() -> gnutls_x509_crt_t * {
gnutls_x509_crt_t *crt_list = nullptr;
unsigned int crt_list_size = 0;
Expand All @@ -127,38 +129,79 @@ string make_fingerprint(gnutls_certificate_credentials_t credentials) {

unique_ptr<gnutls_x509_crt_t, decltype(free_crt_list)> crt_list(new_crt_list(), free_crt_list);

return make_fingerprint(*crt_list);
return make_fingerprint(*crt_list, fingerprintAlgorithm);
}

string make_fingerprint(gnutls_x509_crt_t crt) {
const size_t size = 32;
unsigned char buffer[size];
string make_fingerprint(gnutls_x509_crt_t crt,
CertificateFingerprint::Algorithm fingerprintAlgorithm) {
const size_t size = CertificateFingerprint::AlgorithmSize(fingerprintAlgorithm);
std::vector<unsigned char> buffer(size);
size_t len = size;
gnutls::check(gnutls_x509_crt_get_fingerprint(crt, GNUTLS_DIG_SHA256, buffer, &len),

gnutls_digest_algorithm_t hashFunc;
switch (fingerprintAlgorithm) {
case CertificateFingerprint::Algorithm::Sha1:
hashFunc = GNUTLS_DIG_SHA1;
break;
case CertificateFingerprint::Algorithm::Sha224:
hashFunc = GNUTLS_DIG_SHA224;
break;
case CertificateFingerprint::Algorithm::Sha384:
hashFunc = GNUTLS_DIG_SHA384;
break;
case CertificateFingerprint::Algorithm::Sha512:
hashFunc = GNUTLS_DIG_SHA512;
break;
default:
hashFunc = GNUTLS_DIG_SHA256;
}

gnutls::check(gnutls_x509_crt_get_fingerprint(crt, hashFunc, buffer.data(), &len),
"X509 fingerprint error");

std::ostringstream oss;
oss << std::hex << std::uppercase << std::setfill('0');
for (size_t i = 0; i < len; ++i) {
if (i)
oss << std::setw(1) << ':';
oss << std::setw(2) << unsigned(buffer[i]);
oss << std::setw(2) << unsigned(buffer.at(i));
}
return oss.str();
}

#elif USE_MBEDTLS
string make_fingerprint(mbedtls_x509_crt *crt) {
const int size = 32;
uint8_t buffer[size];
string make_fingerprint(mbedtls_x509_crt *crt,
CertificateFingerprint::Algorithm fingerprintAlgorithm) {
const int size = CertificateFingerprint::AlgorithmSize(fingerprintAlgorithm);
std::vector<unsigned char> buffer(size);
std::stringstream fingerprint;

mbedtls::check(
mbedtls_sha256(crt->raw.p, crt->raw.len, reinterpret_cast<unsigned char *>(buffer), 0),
"Failed to generate certificate fingerprint");
switch (fingerprintAlgorithm) {
case CertificateFingerprint::Algorithm::Sha1:
mbedtls::check(mbedtls_sha1(crt->raw.p, crt->raw.len, buffer.data()),
"Failed to generate certificate fingerprint");
break;
case CertificateFingerprint::Algorithm::Sha224:
mbedtls::check(mbedtls_sha256(crt->raw.p, crt->raw.len, buffer.data(), 1),
"Failed to generate certificate fingerprint");

break;
case CertificateFingerprint::Algorithm::Sha384:
mbedtls::check(mbedtls_sha512(crt->raw.p, crt->raw.len, buffer.data(), 1),
"Failed to generate certificate fingerprint");
break;
case CertificateFingerprint::Algorithm::Sha512:
mbedtls::check(mbedtls_sha512(crt->raw.p, crt->raw.len, buffer.data(), 0),
"Failed to generate certificate fingerprint");
break;
default:
mbedtls::check(mbedtls_sha256(crt->raw.p, crt->raw.len, buffer.data(), 0),
"Failed to generate certificate fingerprint");
}

for (auto i = 0; i < size; i++) {
fingerprint << std::setfill('0') << std::setw(2) << std::hex << static_cast<int>(buffer[i]);
fingerprint << std::setfill('0') << std::setw(2) << std::hex
<< static_cast<int>(buffer.at(i));
if (i != (size - 1)) {
fingerprint << ":";
}
Expand All @@ -168,7 +211,8 @@ string make_fingerprint(mbedtls_x509_crt *crt) {
}

Certificate::Certificate(shared_ptr<mbedtls_x509_crt> crt, shared_ptr<mbedtls_pk_context> pk)
: mCrt(crt), mPk(pk), mFingerprint(make_fingerprint(crt.get())) {}
: mCrt(crt), mPk(pk),
mFingerprint(make_fingerprint(crt.get(), CertificateFingerprint::Algorithm::Sha256)) {}

Certificate Certificate::FromString(string crt_pem, string key_pem) {
PLOG_DEBUG << "Importing certificate from PEM string (MbedTLS)";
Expand Down Expand Up @@ -465,25 +509,45 @@ Certificate Certificate::Generate(CertificateType type, const string &commonName
}

Certificate::Certificate(shared_ptr<X509> x509, shared_ptr<EVP_PKEY> pkey)
: mX509(std::move(x509)), mPKey(std::move(pkey)), mFingerprint(make_fingerprint(mX509.get())) {}
: mX509(std::move(x509)), mPKey(std::move(pkey)),
mFingerprint(make_fingerprint(mX509.get(), CertificateFingerprint::Algorithm::Sha256)) {}

std::tuple<X509 *, EVP_PKEY *> Certificate::credentials() const {
return {mX509.get(), mPKey.get()};
}

string make_fingerprint(X509 *x509) {
const size_t size = 32;
unsigned char buffer[size];
unsigned int len = size;
if (!X509_digest(x509, EVP_sha256(), buffer, &len))
string make_fingerprint(X509 *x509, CertificateFingerprint::Algorithm fingerprintAlgorithm) {
size_t size = CertificateFingerprint::AlgorithmSize(fingerprintAlgorithm);
std::vector<unsigned char> buffer(size);
auto len = static_cast<unsigned int>(size);

const EVP_MD *hashFunc;
switch (fingerprintAlgorithm) {
case CertificateFingerprint::Algorithm::Sha1:
hashFunc = EVP_sha1();
break;
case CertificateFingerprint::Algorithm::Sha224:
hashFunc = EVP_sha224();
break;
case CertificateFingerprint::Algorithm::Sha384:
hashFunc = EVP_sha384();
break;
case CertificateFingerprint::Algorithm::Sha512:
hashFunc = EVP_sha512();
break;
default:
hashFunc = EVP_sha256();
}

if (!X509_digest(x509, hashFunc, buffer.data(), &len))
throw std::runtime_error("X509 fingerprint error");

std::ostringstream oss;
oss << std::hex << std::uppercase << std::setfill('0');
for (size_t i = 0; i < len; ++i) {
if (i)
oss << std::setw(1) << ':';
oss << std::setw(2) << unsigned(buffer[i]);
oss << std::setw(2) << unsigned(buffer.at(i));
}
return oss.str();
}
Expand Down
Loading

0 comments on commit 2742626

Please sign in to comment.