1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/media/mtransport/dtlsidentity.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,298 @@ 1.4 +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* vim: set ts=2 et sw=2 tw=80: */ 1.6 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this file, 1.8 + * You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.9 + 1.10 +#include <iomanip> 1.11 +#include "logging.h" 1.12 +#include "nspr.h" 1.13 +#include "cryptohi.h" 1.14 +#include "ssl.h" 1.15 +#include "keyhi.h" 1.16 +#include "pk11pub.h" 1.17 +#include "sechash.h" 1.18 +#include "nsError.h" 1.19 +#include "dtlsidentity.h" 1.20 + 1.21 +namespace mozilla { 1.22 + 1.23 +MOZ_MTLOG_MODULE("mtransport") 1.24 + 1.25 +DtlsIdentity::~DtlsIdentity() { 1.26 + // XXX: make cert_ a smart pointer to avoid this, after we figure 1.27 + // out the linking problem. 1.28 + if (cert_) 1.29 + CERT_DestroyCertificate(cert_); 1.30 +} 1.31 + 1.32 +const std::string DtlsIdentity::DEFAULT_HASH_ALGORITHM = "sha-256"; 1.33 +const size_t DtlsIdentity::HASH_ALGORITHM_MAX_LENGTH = 64; 1.34 + 1.35 +TemporaryRef<DtlsIdentity> DtlsIdentity::Generate() { 1.36 + 1.37 + ScopedPK11SlotInfo slot(PK11_GetInternalSlot()); 1.38 + if (!slot) { 1.39 + return nullptr; 1.40 + } 1.41 + 1.42 + uint8_t random_name[16]; 1.43 + 1.44 + SECStatus rv = PK11_GenerateRandomOnSlot(slot, random_name, 1.45 + sizeof(random_name)); 1.46 + if (rv != SECSuccess) 1.47 + return nullptr; 1.48 + 1.49 + std::string name; 1.50 + char chunk[3]; 1.51 + for (size_t i = 0; i < sizeof(random_name); ++i) { 1.52 + PR_snprintf(chunk, sizeof(chunk), "%.2x", random_name[i]); 1.53 + name += chunk; 1.54 + } 1.55 + 1.56 + std::string subject_name_string = "CN=" + name; 1.57 + ScopedCERTName subject_name(CERT_AsciiToName(subject_name_string.c_str())); 1.58 + if (!subject_name) { 1.59 + return nullptr; 1.60 + } 1.61 + 1.62 + PK11RSAGenParams rsaparams; 1.63 + rsaparams.keySizeInBits = 1024; // TODO: make this stronger when we 1.64 + // pre-generate. 1.65 + rsaparams.pe = 65537; // We are too paranoid to use 3 as the exponent. 1.66 + 1.67 + ScopedSECKEYPrivateKey private_key; 1.68 + ScopedSECKEYPublicKey public_key; 1.69 + SECKEYPublicKey *pubkey; 1.70 + 1.71 + private_key = 1.72 + PK11_GenerateKeyPair(slot, 1.73 + CKM_RSA_PKCS_KEY_PAIR_GEN, &rsaparams, &pubkey, 1.74 + PR_FALSE, PR_TRUE, nullptr); 1.75 + if (private_key == nullptr) 1.76 + return nullptr; 1.77 + public_key = pubkey; 1.78 + 1.79 + ScopedCERTSubjectPublicKeyInfo spki( 1.80 + SECKEY_CreateSubjectPublicKeyInfo(pubkey)); 1.81 + if (!spki) { 1.82 + return nullptr; 1.83 + } 1.84 + 1.85 + ScopedCERTCertificateRequest certreq( 1.86 + CERT_CreateCertificateRequest(subject_name, spki, nullptr)); 1.87 + if (!certreq) { 1.88 + return nullptr; 1.89 + } 1.90 + 1.91 + // From 1 day before todayto 30 days after. 1.92 + // This is a sort of arbitrary range designed to be valid 1.93 + // now with some slack in case the other side expects 1.94 + // some before expiry. 1.95 + // 1.96 + // Note: explicit casts necessary to avoid 1.97 + // warning C4307: '*' : integral constant overflow 1.98 + static const PRTime oneDay = PRTime(PR_USEC_PER_SEC) 1.99 + * PRTime(60) // sec 1.100 + * PRTime(60) // min 1.101 + * PRTime(24); // hours 1.102 + PRTime now = PR_Now(); 1.103 + PRTime notBefore = now - oneDay; 1.104 + PRTime notAfter = now + (PRTime(30) * oneDay); 1.105 + 1.106 + ScopedCERTValidity validity(CERT_CreateValidity(notBefore, notAfter)); 1.107 + if (!validity) { 1.108 + return nullptr; 1.109 + } 1.110 + 1.111 + unsigned long serial; 1.112 + // Note: This serial in principle could collide, but it's unlikely 1.113 + rv = PK11_GenerateRandomOnSlot(slot, 1.114 + reinterpret_cast<unsigned char *>(&serial), 1.115 + sizeof(serial)); 1.116 + if (rv != SECSuccess) { 1.117 + return nullptr; 1.118 + } 1.119 + 1.120 + ScopedCERTCertificate certificate( 1.121 + CERT_CreateCertificate(serial, subject_name, validity, certreq)); 1.122 + if (!certificate) { 1.123 + return nullptr; 1.124 + } 1.125 + 1.126 + PLArenaPool *arena = certificate->arena; 1.127 + 1.128 + rv = SECOID_SetAlgorithmID(arena, &certificate->signature, 1.129 + SEC_OID_PKCS1_SHA1_WITH_RSA_ENCRYPTION, 0); 1.130 + if (rv != SECSuccess) 1.131 + return nullptr; 1.132 + 1.133 + // Set version to X509v3. 1.134 + *(certificate->version.data) = SEC_CERTIFICATE_VERSION_3; 1.135 + certificate->version.len = 1; 1.136 + 1.137 + SECItem innerDER; 1.138 + innerDER.len = 0; 1.139 + innerDER.data = nullptr; 1.140 + 1.141 + if (!SEC_ASN1EncodeItem(arena, &innerDER, certificate, 1.142 + SEC_ASN1_GET(CERT_CertificateTemplate))) { 1.143 + return nullptr; 1.144 + } 1.145 + 1.146 + SECItem *signedCert = PORT_ArenaZNew(arena, SECItem); 1.147 + if (!signedCert) { 1.148 + return nullptr; 1.149 + } 1.150 + 1.151 + rv = SEC_DerSignData(arena, signedCert, innerDER.data, innerDER.len, 1.152 + private_key, 1.153 + SEC_OID_PKCS1_SHA1_WITH_RSA_ENCRYPTION); 1.154 + if (rv != SECSuccess) { 1.155 + return nullptr; 1.156 + } 1.157 + certificate->derCert = *signedCert; 1.158 + 1.159 + return new DtlsIdentity(private_key.forget(), certificate.forget()); 1.160 +} 1.161 + 1.162 + 1.163 +nsresult DtlsIdentity::ComputeFingerprint(const std::string algorithm, 1.164 + unsigned char *digest, 1.165 + std::size_t size, 1.166 + std::size_t *digest_length) { 1.167 + MOZ_ASSERT(cert_); 1.168 + 1.169 + return ComputeFingerprint(cert_, algorithm, digest, size, digest_length); 1.170 +} 1.171 + 1.172 +nsresult DtlsIdentity::ComputeFingerprint(const CERTCertificate *cert, 1.173 + const std::string algorithm, 1.174 + unsigned char *digest, 1.175 + std::size_t size, 1.176 + std::size_t *digest_length) { 1.177 + MOZ_ASSERT(cert); 1.178 + 1.179 + HASH_HashType ht; 1.180 + 1.181 + if (algorithm == "sha-1") { 1.182 + ht = HASH_AlgSHA1; 1.183 + } else if (algorithm == "sha-224") { 1.184 + ht = HASH_AlgSHA224; 1.185 + } else if (algorithm == "sha-256") { 1.186 + ht = HASH_AlgSHA256; 1.187 + } else if (algorithm == "sha-384") { 1.188 + ht = HASH_AlgSHA384; 1.189 + } else if (algorithm == "sha-512") { 1.190 + ht = HASH_AlgSHA512; 1.191 + } else { 1.192 + return NS_ERROR_INVALID_ARG; 1.193 + } 1.194 + 1.195 + const SECHashObject *ho = HASH_GetHashObject(ht); 1.196 + MOZ_ASSERT(ho); 1.197 + if (!ho) 1.198 + return NS_ERROR_INVALID_ARG; 1.199 + 1.200 + MOZ_ASSERT(ho->length >= 20); // Double check 1.201 + 1.202 + if (size < ho->length) 1.203 + return NS_ERROR_INVALID_ARG; 1.204 + 1.205 + SECStatus rv = HASH_HashBuf(ho->type, digest, 1.206 + cert->derCert.data, 1.207 + cert->derCert.len); 1.208 + if (rv != SECSuccess) 1.209 + return NS_ERROR_FAILURE; 1.210 + 1.211 + *digest_length = ho->length; 1.212 + 1.213 + return NS_OK; 1.214 +} 1.215 + 1.216 +// Format the fingerprint in RFC 4572 Section 5 attribute format, including both 1.217 +// the hash name and the fingerprint, colons and all. 1.218 +// returns an empty string if there is a problem 1.219 +std::string DtlsIdentity::GetFormattedFingerprint(const std::string &algorithm) { 1.220 + unsigned char digest[HASH_ALGORITHM_MAX_LENGTH]; 1.221 + size_t digest_length; 1.222 + 1.223 + nsresult res = this->ComputeFingerprint(algorithm, 1.224 + digest, 1.225 + sizeof(digest), 1.226 + &digest_length); 1.227 + if (NS_FAILED(res)) { 1.228 + MOZ_MTLOG(ML_ERROR, "Unable to compute " << algorithm 1.229 + << " hash for identity: nsresult = 0x" 1.230 + << std::hex << std::uppercase 1.231 + << static_cast<uint32_t>(res) 1.232 + << std::nouppercase << std::dec); 1.233 + return ""; 1.234 + } 1.235 + 1.236 + return algorithm + " " + this->FormatFingerprint(digest, digest_length); 1.237 +} 1.238 + 1.239 +std::string DtlsIdentity::FormatFingerprint(const unsigned char *digest, 1.240 + std::size_t size) { 1.241 + std::string str(""); 1.242 + char group[3]; 1.243 + 1.244 + for (std::size_t i=0; i < size; i++) { 1.245 + PR_snprintf(group, sizeof(group), "%.2X", digest[i]); 1.246 + if (i != 0) { 1.247 + str += ":"; 1.248 + } 1.249 + str += group; 1.250 + } 1.251 + 1.252 + MOZ_ASSERT(str.size() == (size * 3 - 1)); // Check result length 1.253 + return str; 1.254 +} 1.255 + 1.256 +// Parse a fingerprint in RFC 4572 format. 1.257 +// Note that this tolerates some badly formatted data, in particular: 1.258 +// (a) arbitrary runs of colons 1.259 +// (b) colons at the beginning or end. 1.260 +nsresult DtlsIdentity::ParseFingerprint(const std::string fp, 1.261 + unsigned char *digest, 1.262 + size_t size, 1.263 + size_t *length) { 1.264 + size_t offset = 0; 1.265 + bool top_half = true; 1.266 + uint8_t val = 0; 1.267 + 1.268 + for (size_t i=0; i<fp.length(); i++) { 1.269 + if (offset >= size) { 1.270 + // Note: no known way for offset to get > size 1.271 + MOZ_MTLOG(ML_ERROR, "Fingerprint too long for buffer"); 1.272 + return NS_ERROR_INVALID_ARG; 1.273 + } 1.274 + 1.275 + if (top_half && (fp[i] == ':')) { 1.276 + continue; 1.277 + } else if ((fp[i] >= '0') && (fp[i] <= '9')) { 1.278 + val |= fp[i] - '0'; 1.279 + } else if ((fp[i] >= 'A') && (fp[i] <= 'F')) { 1.280 + val |= fp[i] - 'A' + 10; 1.281 + } else { 1.282 + MOZ_MTLOG(ML_ERROR, "Invalid fingerprint value " << fp[i]); 1.283 + return NS_ERROR_ILLEGAL_VALUE; 1.284 + } 1.285 + 1.286 + if (top_half) { 1.287 + val <<= 4; 1.288 + top_half = false; 1.289 + } else { 1.290 + digest[offset++] = val; 1.291 + top_half = true; 1.292 + val = 0; 1.293 + } 1.294 + } 1.295 + 1.296 + *length = offset; 1.297 + 1.298 + return NS_OK; 1.299 +} 1.300 + 1.301 +} // close namespace