| // Copyright (c) 2011 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "net/base/dnssec_keyset.h" |
| |
| #include <cryptohi.h> |
| #include <cryptoht.h> |
| #include <keyhi.h> |
| |
| #include "base/logging.h" |
| #include "base/memory/scoped_ptr.h" |
| #include "base/nss_util.h" |
| #include "base/time.h" |
| #include "net/base/dns_util.h" |
| |
| namespace { |
| |
| // These are encoded AlgorithmIdentifiers for the given signature algorithm. |
| const unsigned char kRSAWithSHA1[] = { |
| 0x30, 0xd, 0x6, 0x9, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0xd, 0x1, 0x1, 0x5, 5, 0 |
| }; |
| |
| const unsigned char kRSAWithSHA256[] = { |
| 0x30, 0xd, 0x6, 0x9, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0xd, 0x1, 0x1, 0xb, 5, 0 |
| }; |
| |
| } // namespace |
| |
| namespace net { |
| |
| DNSSECKeySet::DNSSECKeySet() |
| : ignore_timestamps_(false) { |
| } |
| |
| DNSSECKeySet::~DNSSECKeySet() { |
| } |
| |
| bool DNSSECKeySet::AddKey(const base::StringPiece& dnskey) { |
| uint16 keyid = DNSKEYToKeyID(dnskey); |
| std::string der_encoded = ASN1WrapDNSKEY(dnskey); |
| if (der_encoded.empty()) |
| return false; |
| |
| keyids_.push_back(keyid); |
| public_keys_.push_back(der_encoded); |
| return true; |
| } |
| |
| bool DNSSECKeySet::CheckSignature( |
| const base::StringPiece& name, |
| const base::StringPiece& zone, |
| const base::StringPiece& signature, |
| uint16 rrtype, |
| const std::vector<base::StringPiece>& rrdatas) { |
| // signature has this format: |
| // algorithm uint8 |
| // labels uint8 |
| // ttl uint32 |
| // expires uint32 |
| // begins uint32 |
| // keyid uint16 |
| // |
| // followed by the actual signature. |
| if (signature.size() < 16) |
| return false; |
| const unsigned char* sigdata = |
| reinterpret_cast<const unsigned char*>(signature.data()); |
| |
| uint8 algorithm = sigdata[0]; |
| uint32 expires = static_cast<uint32>(sigdata[6]) << 24 | |
| static_cast<uint32>(sigdata[7]) << 16 | |
| static_cast<uint32>(sigdata[8]) << 8 | |
| static_cast<uint32>(sigdata[9]); |
| uint32 begins = static_cast<uint32>(sigdata[10]) << 24 | |
| static_cast<uint32>(sigdata[11]) << 16 | |
| static_cast<uint32>(sigdata[12]) << 8 | |
| static_cast<uint32>(sigdata[13]); |
| uint16 keyid = static_cast<uint16>(sigdata[14]) << 8 | |
| static_cast<uint16>(sigdata[15]); |
| |
| if (!ignore_timestamps_) { |
| uint32 now = static_cast<uint32>(base::Time::Now().ToTimeT()); |
| if (now < begins || now >= expires) |
| return false; |
| } |
| |
| base::StringPiece sig(signature.data() + 16, signature.size() - 16); |
| |
| // You should have RFC 4034, 3.1.8.1 open when reading this code. |
| unsigned signed_data_len = 0; |
| signed_data_len += 2; // rrtype |
| signed_data_len += 16; // (see signature format, above) |
| signed_data_len += zone.size(); |
| |
| for (std::vector<base::StringPiece>::const_iterator |
| i = rrdatas.begin(); i != rrdatas.end(); i++) { |
| signed_data_len += name.size(); |
| signed_data_len += 2; // rrtype |
| signed_data_len += 2; // class |
| signed_data_len += 4; // ttl |
| signed_data_len += 2; // RRDATA length |
| signed_data_len += i->size(); |
| } |
| |
| scoped_array<unsigned char> signed_data(new unsigned char[signed_data_len]); |
| unsigned j = 0; |
| |
| signed_data[j++] = static_cast<uint8>(rrtype >> 8); |
| signed_data[j++] = static_cast<uint8>(rrtype); |
| memcpy(&signed_data[j], sigdata, 16); |
| j += 16; |
| memcpy(&signed_data[j], zone.data(), zone.size()); |
| j += zone.size(); |
| |
| for (std::vector<base::StringPiece>::const_iterator |
| i = rrdatas.begin(); i != rrdatas.end(); i++) { |
| memcpy(&signed_data[j], name.data(), name.size()); |
| j += name.size(); |
| signed_data[j++] = static_cast<uint8>(rrtype >> 8); |
| signed_data[j++] = static_cast<uint8>(rrtype); |
| signed_data[j++] = 0; // CLASS (always IN = 1) |
| signed_data[j++] = 1; |
| // Copy the TTL from |signature|. |
| memcpy(&signed_data[j], signature.data() + 2, sizeof(uint32)); |
| j += sizeof(uint32); |
| unsigned rrdata_len = i->size(); |
| signed_data[j++] = rrdata_len >> 8; |
| signed_data[j++] = rrdata_len; |
| memcpy(&signed_data[j], i->data(), i->size()); |
| j += i->size(); |
| } |
| |
| DCHECK_EQ(j, signed_data_len); |
| |
| base::StringPiece signature_algorithm; |
| if (algorithm == kDNSSEC_RSA_SHA1 || |
| algorithm == kDNSSEC_RSA_SHA1_NSEC3) { |
| signature_algorithm = base::StringPiece( |
| reinterpret_cast<const char*>(kRSAWithSHA1), |
| sizeof(kRSAWithSHA1)); |
| } else if (algorithm == kDNSSEC_RSA_SHA256) { |
| signature_algorithm = base::StringPiece( |
| reinterpret_cast<const char*>(kRSAWithSHA256), |
| sizeof(kRSAWithSHA256)); |
| } else { |
| // Unknown algorithm. |
| return false; |
| } |
| |
| // Check the signature with each trusted key which has a matching keyid. |
| DCHECK_EQ(public_keys_.size(), keyids_.size()); |
| for (unsigned i = 0; i < public_keys_.size(); i++) { |
| if (keyids_[i] != keyid) |
| continue; |
| |
| if (VerifySignature( |
| signature_algorithm, sig, public_keys_[i], |
| base::StringPiece(reinterpret_cast<const char*>(signed_data.get()), |
| signed_data_len))) { |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| // static |
| uint16 DNSSECKeySet::DNSKEYToKeyID(const base::StringPiece& dnskey) { |
| const unsigned char* data = |
| reinterpret_cast<const unsigned char*>(dnskey.data()); |
| |
| // RFC 4043: App B |
| uint32 ac = 0; |
| for (unsigned i = 0; i < dnskey.size(); i++) { |
| if (i & 1) { |
| ac += data[i]; |
| } else { |
| ac += static_cast<uint32>(data[i]) << 8; |
| } |
| } |
| ac += (ac >> 16) & 0xffff; |
| return ac; |
| } |
| |
| void DNSSECKeySet::IgnoreTimestamps() { |
| ignore_timestamps_ = true; |
| } |
| |
| bool DNSSECKeySet::VerifySignature( |
| base::StringPiece signature_algorithm, |
| base::StringPiece signature, |
| base::StringPiece public_key, |
| base::StringPiece signed_data) { |
| // This code is largely a copy-and-paste from |
| // base/crypto/signature_verifier_nss.cc. We can't change |
| // base::SignatureVerifier to always use NSS because we want the ability to |
| // be FIPS 140-2 compliant. However, we can't use base::SignatureVerifier |
| // here because some platforms don't support SHA256 signatures. Therefore, we |
| // use NSS directly. |
| |
| base::EnsureNSSInit(); |
| |
| CERTSubjectPublicKeyInfo* spki = NULL; |
| SECItem spki_der; |
| spki_der.type = siBuffer; |
| spki_der.data = (uint8*) public_key.data(); |
| spki_der.len = public_key.size(); |
| spki = SECKEY_DecodeDERSubjectPublicKeyInfo(&spki_der); |
| if (!spki) |
| return false; |
| SECKEYPublicKey* pub_key = SECKEY_ExtractPublicKey(spki); |
| SECKEY_DestroySubjectPublicKeyInfo(spki); // Done with spki. |
| if (!pub_key) |
| return false; |
| |
| PLArenaPool* arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); |
| if (!arena) { |
| SECKEY_DestroyPublicKey(pub_key); |
| return false; |
| } |
| |
| SECItem sig_alg_der; |
| sig_alg_der.type = siBuffer; |
| sig_alg_der.data = (uint8*) signature_algorithm.data(); |
| sig_alg_der.len = signature_algorithm.size(); |
| SECAlgorithmID sig_alg_id; |
| SECStatus rv; |
| rv = SEC_QuickDERDecodeItem(arena, &sig_alg_id, SECOID_AlgorithmIDTemplate, |
| &sig_alg_der); |
| if (rv != SECSuccess) { |
| SECKEY_DestroyPublicKey(pub_key); |
| PORT_FreeArena(arena, PR_TRUE); |
| return false; |
| } |
| |
| SECItem sig; |
| sig.type = siBuffer; |
| sig.data = (uint8*) signature.data(); |
| sig.len = signature.size(); |
| SECOidTag hash_alg_tag; |
| VFYContext* vfy_context = |
| VFY_CreateContextWithAlgorithmID(pub_key, &sig, |
| &sig_alg_id, &hash_alg_tag, |
| NULL); |
| SECKEY_DestroyPublicKey(pub_key); |
| PORT_FreeArena(arena, PR_TRUE); // Done with sig_alg_id. |
| if (!vfy_context) { |
| // A corrupted RSA signature could be detected without the data, so |
| // VFY_CreateContextWithAlgorithmID may fail with SEC_ERROR_BAD_SIGNATURE |
| // (-8182). |
| return false; |
| } |
| |
| rv = VFY_Begin(vfy_context); |
| if (rv != SECSuccess) { |
| NOTREACHED(); |
| return false; |
| } |
| rv = VFY_Update(vfy_context, (uint8*) signed_data.data(), signed_data.size()); |
| if (rv != SECSuccess) { |
| NOTREACHED(); |
| return false; |
| } |
| rv = VFY_End(vfy_context); |
| VFY_DestroyContext(vfy_context, PR_TRUE); |
| |
| return rv == SECSuccess; |
| } |
| |
| // This is an ASN.1 encoded AlgorithmIdentifier for RSA |
| static const unsigned char kASN1AlgorithmIdentifierRSA[] = { |
| 0x30, // SEQUENCE |
| 0x0d, // length (11 bytes) |
| 0x06, // OBJECT IDENTIFER |
| 0x09, // length (9 bytes) |
| // OID 1.2.840.113549.1.1.1 (RSA) |
| 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, |
| // NULL of length 0 |
| 0x05, 0x00, |
| }; |
| |
| // EncodeASN1Length assumes that |*length| contains the number of DER-encoded, |
| // length-prefixed ASN.1 bytes to follow and serialises the length to |out[*j]| |
| // and updates |j| and |length| accordingly. |
| static void EncodeASN1Length(unsigned char* out, unsigned* j, |
| unsigned* length) { |
| if ((*length - 1) < 128) { |
| (*length) -= 1; |
| out[(*j)++] = *length; |
| } else if ((*length - 2) < 256) { |
| (*length) -= 2; |
| out[(*j)++] = 0x80 | 1; |
| out[(*j)++] = *length; |
| } else { |
| (*length) -= 3; |
| out[(*j)++] = 0x80 | 2; |
| out[(*j)++] = *length >> 8; |
| out[(*j)++] = *length; |
| } |
| } |
| |
| // AdvanceForASN1Length returns the number of bytes required to encode a ASN1 |
| // DER length value of |remaining|. |
| static unsigned AdvanceForASN1Length(unsigned remaining) { |
| if (remaining < 128) { |
| return 1; |
| } else if (remaining < 256) { |
| return 2; |
| } else if (remaining < 65536) { |
| return 3; |
| } else { |
| NOTREACHED(); |
| return 3; |
| } |
| } |
| |
| // ASN1WrapDNSKEY converts the DNSKEY RDATA in |dnskey| into the ASN.1 wrapped |
| // format expected by NSS. To wit: |
| // SubjectPublicKeyInfo ::= SEQUENCE { |
| // algorithm AlgorithmIdentifier, |
| // subjectPublicKey BIT STRING } |
| std::string DNSSECKeySet::ASN1WrapDNSKEY(const base::StringPiece& dnskey) { |
| const unsigned char* data = |
| reinterpret_cast<const unsigned char*>(dnskey.data()); |
| |
| if (dnskey.size() < 5 || dnskey.size() > 32767) |
| return ""; |
| const uint8 algorithm = data[3]; |
| if (algorithm != kDNSSEC_RSA_SHA1 && |
| algorithm != kDNSSEC_RSA_SHA1_NSEC3 && |
| algorithm != kDNSSEC_RSA_SHA256) { |
| return ""; |
| } |
| |
| unsigned exp_length; |
| unsigned exp_offset; |
| // First we extract the public exponent. |
| if (data[4] == 0) { |
| if (dnskey.size() < 7) |
| return ""; |
| exp_length = static_cast<unsigned>(data[5]) << 8 | |
| static_cast<unsigned>(data[6]); |
| exp_offset = 7; |
| } else { |
| exp_length = static_cast<unsigned>(data[4]); |
| exp_offset = 5; |
| } |
| |
| // We refuse to deal with large public exponents. |
| if (exp_length > 3) |
| return ""; |
| if (dnskey.size() < exp_offset + exp_length) |
| return ""; |
| |
| unsigned exp = 0; |
| for (unsigned i = 0; i < exp_length; i++) { |
| exp <<= 8; |
| exp |= static_cast<unsigned>(data[exp_offset + i]); |
| } |
| |
| unsigned n_offset = exp_offset + exp_length; |
| unsigned n_length = dnskey.size() - n_offset; |
| |
| // Anything smaller than 512 bits is too weak to be trusted. |
| if (n_length < 64) |
| return ""; |
| |
| // If the MSB of exp is true then we need to prefix a zero byte to stop the |
| // ASN.1 encoding from being negative. |
| if (exp & (1 << ((8 * exp_length) - 1))) |
| exp_length++; |
| |
| // Likewise with the modulus |
| unsigned n_padding = data[n_offset] & 0x80 ? 1 : 0; |
| |
| // We now calculate the length of the full ASN.1 encoded public key. We're |
| // working backwards from the end of the structure. Keep in mind that it's: |
| // SEQUENCE |
| // AlgorithmIdentifier |
| // BITSTRING |
| // SEQUENCE |
| // INTEGER |
| // INTEGER |
| unsigned length = 0; |
| length += exp_length; // exponent data |
| length++; // we know that |exp_length| < 128 |
| length++; // INTEGER tag for exponent |
| length += n_length + n_padding; |
| length += AdvanceForASN1Length(n_length + n_padding); |
| length++; // INTEGER tag for modulus |
| length += AdvanceForASN1Length(length); // SEQUENCE length |
| length++; // SEQUENCE tag |
| length++; // BITSTRING unused bits |
| length += AdvanceForASN1Length(length); // BITSTRING length |
| length++; // BITSTRING tag |
| length += sizeof(kASN1AlgorithmIdentifierRSA); |
| length += AdvanceForASN1Length(length); // SEQUENCE length |
| length++; // SEQUENCE tag |
| |
| scoped_array<unsigned char> out(new unsigned char[length]); |
| |
| // Now we walk forwards and serialise the ASN.1, undoing the steps above. |
| unsigned j = 0; |
| out[j++] = 0x30; // SEQUENCE |
| length--; |
| EncodeASN1Length(out.get(), &j, &length); |
| memcpy(&out[j], kASN1AlgorithmIdentifierRSA, |
| sizeof(kASN1AlgorithmIdentifierRSA)); |
| j += sizeof(kASN1AlgorithmIdentifierRSA); |
| length -= sizeof(kASN1AlgorithmIdentifierRSA); |
| out[j++] = 3; // BITSTRING tag |
| length--; |
| EncodeASN1Length(out.get(), &j, &length); |
| out[j++] = 0; // BITSTRING unused bits |
| length--; |
| out[j++] = 0x30; // SEQUENCE |
| length--; |
| EncodeASN1Length(out.get(), &j, &length); |
| out[j++] = 2; // INTEGER |
| length--; |
| unsigned l = n_length + n_padding; |
| if (l < 128) { |
| out[j++] = l; |
| length--; |
| } else if (l < 256) { |
| out[j++] = 0x80 | 1; |
| out[j++] = l; |
| length -= 2; |
| } else if (l < 65536) { |
| out[j++] = 0x80 | 2; |
| out[j++] = l >> 8; |
| out[j++] = l; |
| length -= 3; |
| } else { |
| NOTREACHED(); |
| } |
| |
| if (n_padding) { |
| out[j++] = 0; |
| length--; |
| } |
| memcpy(&out[j], &data[n_offset], n_length); |
| j += n_length; |
| length -= n_length; |
| out[j++] = 2; // INTEGER |
| length--; |
| out[j++] = exp_length; |
| length--; |
| for (unsigned i = exp_length - 1; i < exp_length; i--) { |
| out[j++] = exp >> (8 * i); |
| length--; |
| } |
| |
| DCHECK_EQ(0u, length); |
| |
| return std::string(reinterpret_cast<char*>(out.get()), j); |
| } |
| |
| } // namespace net |