blob: b7c053489fcf0a0d7a64a66010ae73b699f3b4b1 [file] [log] [blame]
/* verify_macos.c
*
* Copyright (c) 2019 Apple Computer, Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* DNS SIG(0) signature verification for DNSSD SRP using MacOS Security Framework.
*
* Provides functions for generating a public key validating context based on SIG(0) KEY RR data, and
* validating a signature using a context generated with that public key. Currently only ECDSASHA256 is
* supported.
*/
#include <stdio.h>
#include <arpa/inet.h>
#include <string.h>
#include <stdlib.h>
#include <CommonCrypto/CommonDigest.h>
#include <AssertMacros.h>
#include "srp.h"
#define SRP_CRYPTO_MACOS_INTERNAL
#include "dns-msg.h"
#include "srp-crypto.h"
//======================================================================================================================
static SecKeyRef
create_public_sec_key(const dns_rr_t *const key_record);
static CFDataRef
create_data_to_verify(dns_wire_t *const message, const dns_rr_t *const signature);
bool
srp_sig0_verify(dns_wire_t *message, dns_rr_t *key, dns_rr_t *signature)
{
bool valid = false;
CFErrorRef cf_error = NULL;
SecKeyRef public_key = NULL;
CFDataRef data_to_verify_cfdata = NULL;
CFDataRef sig_to_match_cfdata = NULL;
// The algorithm in KEY and SIG(0) has to match.
require_action_quiet(key->data.key.algorithm == signature->data.sig.algorithm, exit,
ERROR("KEY algorithm does not match the SIG(0) algorithm - "
"KEY algorithm: %u, SIG(0) algorithm: %u",
key->data.key.algorithm, signature->data.sig.algorithm));
// The only supported algorithm now is ECDSA Curve P-256 with SHA-256.
require_action_quiet(signature->data.sig.algorithm == dnssec_keytype_ecdsa, exit,
ERROR("Unsupported KEY algorithm - KEY algorithm: %u", signature->data.sig.algorithm));
// The key size should always be ECDSA_KEY_SIZE since only ECDSA Curve P-256 with SHA-256 is used now.
require_action_quiet(key->data.key.len == ECDSA_KEY_SIZE, exit,
ERROR("Invalid KEY length - KEY len: %d", key->data.key.len));
// The signature size should always be ECDSA_SHA256_SIG_SIZE, since only ECDSA Curve P-256 with SHA-256 is used now.
require_action_quiet(signature->data.sig.len == ECDSA_SHA256_SIG_SIZE, exit,
ERROR("Invalid SIG(0) length - SIG(0) length: %d", signature->data.sig.len));
// Get SecKeyRef given the KEY data.
public_key = create_public_sec_key(key);
require_action_quiet(public_key != NULL, exit, ERROR("Failed to create public_key"));
// Create signature to check.
sig_to_match_cfdata = CFDataCreate(kCFAllocatorDefault, signature->data.sig.signature, signature->data.sig.len);
require_action_quiet(sig_to_match_cfdata != NULL, exit,
ERROR("CFDataCreate failed when creating sig_to_match_cfdata"));
// Reconstruct the data (the digest of the raw data) that is signed by SIG(0).
data_to_verify_cfdata = create_data_to_verify(message, signature);
require_action_quiet(data_to_verify_cfdata != NULL, exit, ERROR("Failed to data_to_verify_cfdata"));
// Set the corresponding SecKeyAlgorithm for SecKeyVerifySignature.
SecKeyAlgorithm verify_algorithm;
switch (key->data.key.algorithm) {
case dnssec_keytype_ecdsa:
verify_algorithm = kSecKeyAlgorithmECDSASignatureRFC4754;
break;
default:
FAULT("Unsupported KEY algorithm - KEY algorithm: %u", key->data.key.algorithm);
goto exit;
}
// Validate the signature.
valid = SecKeyVerifySignature(public_key, verify_algorithm, data_to_verify_cfdata, sig_to_match_cfdata, &cf_error);
if (!valid) {
CFStringRef error_cfstring = CFErrorCopyDescription(cf_error);
ERROR("SecKeyVerifySignature failed to validate - Error Description: %@", error_cfstring);
CFRelease(error_cfstring);
CFRelease(cf_error);
cf_error = NULL;
}
exit:
if (data_to_verify_cfdata != NULL) {
CFRelease(data_to_verify_cfdata);
}
if (sig_to_match_cfdata != NULL) {
CFRelease(sig_to_match_cfdata);
}
if (public_key != NULL) {
CFRelease(public_key);
}
return valid;
}
static SecKeyRef
create_public_sec_key(const dns_rr_t *const key_record)
{
SecKeyRef key_ref = NULL;
CFErrorRef cf_error = NULL;
if (key_record->data.key.algorithm == dnssec_keytype_ecdsa){
uint8_t four = 4;
const void *public_key_options_keys[] = {kSecAttrKeyType, kSecAttrKeyClass};
const void *public_key_options_values[] = {kSecAttrKeyTypeECSECPrimeRandom, kSecAttrKeyClassPublic};
CFMutableDataRef public_key_cfdata = NULL;
CFDictionaryRef public_key_options = NULL;
// The format of the public key data is 04 | X | Y
public_key_cfdata = CFDataCreateMutable(kCFAllocatorDefault, 1 + key_record->data.key.len);
require_action_quiet(public_key_cfdata != NULL, ecdsa_exit,
ERROR("CFDataCreateMutable failed when creating public key CFMutableDataRef"));
CFDataAppendBytes(public_key_cfdata, &four, sizeof(four));
CFDataAppendBytes(public_key_cfdata, key_record->data.key.key, key_record->data.key.len);
public_key_options = CFDictionaryCreate(kCFAllocatorDefault, public_key_options_keys, public_key_options_values,
sizeof(public_key_options_keys) / sizeof(void *),
&kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
require_action_quiet(public_key_options != NULL, ecdsa_exit,
ERROR("CFDictionaryCreate failed when creating public key options CFDictionaryRef"));
key_ref = SecKeyCreateWithData(public_key_cfdata, public_key_options, &cf_error);
require_action_quiet(key_ref != NULL, ecdsa_exit,
ERROR("SecKeyCreateWithData failed when creating public key SecKeyRef"));
ecdsa_exit:
if (public_key_cfdata != NULL) {
CFRelease(public_key_cfdata);
}
if (public_key_options != NULL) {
CFRelease(public_key_options);
}
} else {
key_ref = NULL;
}
if (cf_error != NULL) {
CFRelease(cf_error);
cf_error = NULL;
}
return key_ref;
}
static CFDataRef
create_data_to_verify(dns_wire_t *const message, const dns_rr_t *const signature)
{
bool encounter_error;
CFDataRef data_to_verify_cfdata = NULL;
uint8_t *canonical_signer_name = NULL;
uint8_t digest[ECDSA_SHA256_HASH_SIZE];
// Right now, the only supported KEY algorithm is ECDSAP256.
require_action_quiet(signature->data.sig.algorithm == dnssec_keytype_ecdsa, exit, encounter_error = true;
FAULT("Unsupported SIG(0) algorithm - SIG(0) algorithm: %u", signature->data.sig.algorithm));
CC_SHA256_CTX cc_digest_context;
CC_SHA256_Init(&cc_digest_context);
// data to be hashed = (SIG(0) RDATA without signature field) + (request - SIG(0)).
// (SIG(0) RDATA without signature field) = SIG(0) fields without signer name + canonical signer name.
// Copy SIG(0) fields without signer name.
CC_SHA256_Update(&cc_digest_context, &message->data[signature->data.sig.start + SIG_HEADERLEN], SIG_STATIC_RDLEN);
// Construct and copy canonical signer name.
size_t canonical_signer_name_length = dns_name_wire_length(signature->data.sig.signer);
// CC_SHA256_Update only accepts CC_LONG type (which is uint32_t) length parameter, so we need to check if the
// canonical_signer_name_length has invalid value.
require_action_quiet(canonical_signer_name_length <= MAXDOMNAMELEN, exit, encounter_error = true;
FAULT("Invalid signer name length - signer name length: %zu", canonical_signer_name_length));
canonical_signer_name = malloc(canonical_signer_name_length);
require_action_quiet(canonical_signer_name != NULL, exit, encounter_error = true;
ERROR("malloc failed when allocating memory - for canonical_signer_name, len: %lu",
canonical_signer_name_length));
bool convert_fail = !dns_name_to_wire_canonical(canonical_signer_name, canonical_signer_name_length,
signature->data.sig.signer);
require_action_quiet(!convert_fail, exit, encounter_error = true;
ERROR("Failed to write canonical name - canonical_signer_name_length: %lu",
canonical_signer_name_length));
CC_SHA256_Update(&cc_digest_context, canonical_signer_name, (CC_LONG)canonical_signer_name_length);
// Copy (request - SIG(0)).
// The authority response count is before the counts have been adjusted for the inclusion of the SIG(0).
message->arcount = htons(ntohs(message->arcount) - 1);
CC_SHA256_Update(&cc_digest_context, (uint8_t *)message, offsetof(dns_wire_t, data) + signature->data.sig.start);
// Recover the count after copying.
message->arcount = htons(ntohs(message->arcount) + 1);
// Generate the final digest.
CC_SHA256_Final(digest, &cc_digest_context);
// Create CFDataRef.
data_to_verify_cfdata = CFDataCreate(kCFAllocatorDefault, digest, sizeof(digest));
require_action_quiet(data_to_verify_cfdata != NULL, exit, encounter_error = true;
ERROR("CFDataCreate failed when creating data_to_verify_cfdata"));
encounter_error = false;
exit:
if (canonical_signer_name != NULL) {
free(canonical_signer_name);
}
if (encounter_error) {
if (data_to_verify_cfdata != NULL) {
CFRelease(data_to_verify_cfdata);
}
}
return data_to_verify_cfdata;
}
//======================================================================================================================
#if SECTRANSFORM_AVAILABLE
// Function to copy out the public key as binary data
void
srp_print_key(srp_key_t *key)
{
SecTransformRef b64encoder;
CFDataRef key_data, public_key = NULL;
CFDataRef encoded;
const uint8_t *data;
CFErrorRef error = NULL;
key_data = SecKeyCopyExternalRepresentation(key->public, &error);
if (error == NULL) {
data = CFDataGetBytePtr(key_data);
public_key = CFDataCreateWithBytesNoCopy(NULL, data + 1,
CFDataGetLength(key_data) - 1, kCFAllocatorNull);
if (public_key != NULL) {
b64encoder = SecEncodeTransformCreate(public_key, &error);
if (error == NULL) {
SecTransformSetAttribute(b64encoder, kSecTransformInputAttributeName, key, &error);
if (error == NULL) {
encoded = SecTransformExecute(b64encoder, &error);
if (error == NULL) {
data = CFDataGetBytePtr(encoded);
fputs("thread-demo.default.service.arpa. IN KEY 513 3 13 ", stdout);
fwrite(data, CFDataGetLength(encoded), 1, stdout);
putc('\n', stdout);
}
if (encoded != NULL) {
CFRelease(encoded);
}
}
}
if (b64encoder != NULL) {
CFRelease(b64encoder);
}
CFRelease(public_key);
}
}
if (key_data != NULL) {
CFRelease(key_data);
}
}
#endif // SECTRANSFORM_AVAILABLE
// Local Variables:
// mode: C
// tab-width: 4
// c-file-style: "bsd"
// c-basic-offset: 4
// fill-column: 108
// indent-tabs-mode: nil
// End: