text: /* 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:
  