mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-05 00:25:27 +00:00
274 lines
7.7 KiB
C++
274 lines
7.7 KiB
C++
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/* vim: set ts=2 et sw=2 tw=80: */
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
|
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
#include "nspr.h"
|
|
#include "cryptohi.h"
|
|
#include "ssl.h"
|
|
#include "keyhi.h"
|
|
#include "pk11pub.h"
|
|
#include "sechash.h"
|
|
#include "nsError.h"
|
|
#include "dtlsidentity.h"
|
|
#include "logging.h"
|
|
|
|
namespace mozilla {
|
|
|
|
MOZ_MTLOG_MODULE("mtransport")
|
|
|
|
DtlsIdentity::~DtlsIdentity() {
|
|
// XXX: make cert_ a smart pointer to avoid this, after we figure
|
|
// out the linking problem.
|
|
if (cert_)
|
|
CERT_DestroyCertificate(cert_);
|
|
}
|
|
|
|
TemporaryRef<DtlsIdentity> DtlsIdentity::Generate() {
|
|
|
|
ScopedPK11SlotInfo slot(PK11_GetInternalSlot());
|
|
if (!slot) {
|
|
return nullptr;
|
|
}
|
|
|
|
uint8_t random_name[16];
|
|
|
|
SECStatus rv = PK11_GenerateRandomOnSlot(slot, random_name,
|
|
sizeof(random_name));
|
|
if (rv != SECSuccess)
|
|
return nullptr;
|
|
|
|
std::string name;
|
|
char chunk[3];
|
|
for (size_t i = 0; i < sizeof(random_name); ++i) {
|
|
PR_snprintf(chunk, sizeof(chunk), "%.2x", random_name[i]);
|
|
name += chunk;
|
|
}
|
|
|
|
std::string subject_name_string = "CN=" + name;
|
|
ScopedCERTName subject_name(CERT_AsciiToName(subject_name_string.c_str()));
|
|
if (!subject_name) {
|
|
return nullptr;
|
|
}
|
|
|
|
PK11RSAGenParams rsaparams;
|
|
rsaparams.keySizeInBits = 1024; // TODO: make this stronger when we
|
|
// pre-generate.
|
|
rsaparams.pe = 65537; // We are too paranoid to use 3 as the exponent.
|
|
|
|
ScopedSECKEYPrivateKey private_key;
|
|
ScopedSECKEYPublicKey public_key;
|
|
SECKEYPublicKey *pubkey;
|
|
|
|
private_key =
|
|
PK11_GenerateKeyPair(slot,
|
|
CKM_RSA_PKCS_KEY_PAIR_GEN, &rsaparams, &pubkey,
|
|
PR_FALSE, PR_TRUE, nullptr);
|
|
if (private_key == nullptr)
|
|
return nullptr;
|
|
public_key = pubkey;
|
|
|
|
ScopedCERTSubjectPublicKeyInfo spki(
|
|
SECKEY_CreateSubjectPublicKeyInfo(pubkey));
|
|
if (!spki) {
|
|
return nullptr;
|
|
}
|
|
|
|
ScopedCERTCertificateRequest certreq(
|
|
CERT_CreateCertificateRequest(subject_name, spki, nullptr));
|
|
if (!certreq) {
|
|
return nullptr;
|
|
}
|
|
|
|
// From 1 day before todayto 30 days after.
|
|
// This is a sort of arbitrary range designed to be valid
|
|
// now with some slack in case the other side expects
|
|
// some before expiry.
|
|
//
|
|
// Note: explicit casts necessary to avoid
|
|
// warning C4307: '*' : integral constant overflow
|
|
static const PRTime oneDay = PRTime(PR_USEC_PER_SEC)
|
|
* PRTime(60) // sec
|
|
* PRTime(60) // min
|
|
* PRTime(24); // hours
|
|
PRTime now = PR_Now();
|
|
PRTime notBefore = now - oneDay;
|
|
PRTime notAfter = now + (PRTime(30) * oneDay);
|
|
|
|
ScopedCERTValidity validity(CERT_CreateValidity(notBefore, notAfter));
|
|
if (!validity) {
|
|
return nullptr;
|
|
}
|
|
|
|
unsigned long serial;
|
|
// Note: This serial in principle could collide, but it's unlikely
|
|
rv = PK11_GenerateRandomOnSlot(slot,
|
|
reinterpret_cast<unsigned char *>(&serial),
|
|
sizeof(serial));
|
|
if (rv != SECSuccess) {
|
|
return nullptr;
|
|
}
|
|
|
|
ScopedCERTCertificate certificate(
|
|
CERT_CreateCertificate(serial, subject_name, validity, certreq));
|
|
if (!certificate) {
|
|
return nullptr;
|
|
}
|
|
|
|
PLArenaPool *arena = certificate->arena;
|
|
|
|
rv = SECOID_SetAlgorithmID(arena, &certificate->signature,
|
|
SEC_OID_PKCS1_SHA1_WITH_RSA_ENCRYPTION, 0);
|
|
if (rv != SECSuccess)
|
|
return nullptr;
|
|
|
|
// Set version to X509v3.
|
|
*(certificate->version.data) = SEC_CERTIFICATE_VERSION_3;
|
|
certificate->version.len = 1;
|
|
|
|
SECItem innerDER;
|
|
innerDER.len = 0;
|
|
innerDER.data = nullptr;
|
|
|
|
if (!SEC_ASN1EncodeItem(arena, &innerDER, certificate,
|
|
SEC_ASN1_GET(CERT_CertificateTemplate))) {
|
|
return nullptr;
|
|
}
|
|
|
|
SECItem *signedCert = PORT_ArenaZNew(arena, SECItem);
|
|
if (!signedCert) {
|
|
return nullptr;
|
|
}
|
|
|
|
rv = SEC_DerSignData(arena, signedCert, innerDER.data, innerDER.len,
|
|
private_key,
|
|
SEC_OID_PKCS1_SHA1_WITH_RSA_ENCRYPTION);
|
|
if (rv != SECSuccess) {
|
|
return nullptr;
|
|
}
|
|
certificate->derCert = *signedCert;
|
|
|
|
return new DtlsIdentity(private_key.forget(), certificate.forget());
|
|
}
|
|
|
|
|
|
nsresult DtlsIdentity::ComputeFingerprint(const std::string algorithm,
|
|
unsigned char *digest,
|
|
std::size_t size,
|
|
std::size_t *digest_length) {
|
|
MOZ_ASSERT(cert_);
|
|
|
|
return ComputeFingerprint(cert_, algorithm, digest, size, digest_length);
|
|
}
|
|
|
|
nsresult DtlsIdentity::ComputeFingerprint(const CERTCertificate *cert,
|
|
const std::string algorithm,
|
|
unsigned char *digest,
|
|
std::size_t size,
|
|
std::size_t *digest_length) {
|
|
MOZ_ASSERT(cert);
|
|
|
|
HASH_HashType ht;
|
|
|
|
if (algorithm == "sha-1") {
|
|
ht = HASH_AlgSHA1;
|
|
} else if (algorithm == "sha-224") {
|
|
ht = HASH_AlgSHA224;
|
|
} else if (algorithm == "sha-256") {
|
|
ht = HASH_AlgSHA256;
|
|
} else if (algorithm == "sha-384") {
|
|
ht = HASH_AlgSHA384;
|
|
} else if (algorithm == "sha-512") {
|
|
ht = HASH_AlgSHA512;
|
|
} else {
|
|
return NS_ERROR_INVALID_ARG;
|
|
}
|
|
|
|
const SECHashObject *ho = HASH_GetHashObject(ht);
|
|
MOZ_ASSERT(ho);
|
|
if (!ho)
|
|
return NS_ERROR_INVALID_ARG;
|
|
|
|
MOZ_ASSERT(ho->length >= 20); // Double check
|
|
|
|
if (size < ho->length)
|
|
return NS_ERROR_INVALID_ARG;
|
|
|
|
SECStatus rv = HASH_HashBuf(ho->type, digest,
|
|
cert->derCert.data,
|
|
cert->derCert.len);
|
|
if (rv != SECSuccess)
|
|
return NS_ERROR_FAILURE;
|
|
|
|
*digest_length = ho->length;
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
// Format the fingerprint in RFC 4572 Section 5 format, colons and
|
|
// all.
|
|
std::string DtlsIdentity::FormatFingerprint(const unsigned char *digest,
|
|
std::size_t size) {
|
|
std::string str("");
|
|
char group[3];
|
|
|
|
for (std::size_t i=0; i < size; i++) {
|
|
PR_snprintf(group, sizeof(group), "%.2X", digest[i]);
|
|
if (i != 0){
|
|
str += ":";
|
|
}
|
|
str += group;
|
|
}
|
|
|
|
MOZ_ASSERT(str.size() == (size * 3 - 1)); // Check result length
|
|
return str;
|
|
}
|
|
|
|
// Parse a fingerprint in RFC 4572 format.
|
|
// Note that this tolerates some badly formatted data, in particular:
|
|
// (a) arbitrary runs of colons
|
|
// (b) colons at the beginning or end.
|
|
nsresult DtlsIdentity::ParseFingerprint(const std::string fp,
|
|
unsigned char *digest,
|
|
size_t size,
|
|
size_t *length) {
|
|
size_t offset = 0;
|
|
bool top_half = true;
|
|
uint8_t val = 0;
|
|
|
|
for (size_t i=0; i<fp.length(); i++) {
|
|
if (offset >= size) {
|
|
// Note: no known way for offset to get > size
|
|
MOZ_MTLOG(PR_LOG_ERROR, "Fingerprint too long for buffer");
|
|
return NS_ERROR_INVALID_ARG;
|
|
}
|
|
|
|
if (top_half && (fp[i] == ':')) {
|
|
continue;
|
|
} else if ((fp[i] >= '0') && (fp[i] <= '9')) {
|
|
val |= fp[i] - '0';
|
|
} else if ((fp[i] >= 'A') && (fp[i] <= 'F')) {
|
|
val |= fp[i] - 'A' + 10;
|
|
} else {
|
|
MOZ_MTLOG(PR_LOG_ERROR, "Invalid fingerprint value " << fp[i]);
|
|
return NS_ERROR_ILLEGAL_VALUE;
|
|
}
|
|
|
|
if (top_half) {
|
|
val <<= 4;
|
|
top_half = false;
|
|
} else {
|
|
digest[offset++] = val;
|
|
top_half = true;
|
|
val = 0;
|
|
}
|
|
}
|
|
|
|
*length = offset;
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
} // close namespace
|