blob: b4b0396377c9925b0b9af53188cd5a58f27fef50 [file] [log] [blame]
/* sign-macos.c
*
* Copyright (c) 2018-2021 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
*
* https://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 generation for DNSSD SRP using mbedtls.
*
* Functions required for loading, saving, and generating public/private keypairs, extracting the public key
* into KEY RR data, and computing signatures.
*
* This is the implementation for Mac OS X.
*/
#include <stdio.h>
#include <arpa/inet.h>
#include <sys/random.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <dns_sd.h>
#include "srp.h"
#include "srp-api.h"
#include "dns-msg.h"
#define SRP_CRYPTO_MACOS_INTERNAL
#include "srp-crypto.h"
// Key is stored in an opaque data structure, for mbedtls this is an mbedtls_pk_context.
// Function to read a public key from a KEY record
// Function to validate a signature given some data and a public key (not required on client)
// Function to free a key
void
srp_keypair_free(srp_key_t *key)
{
free(key);
}
uint16_t
srp_random16()
{
return (uint16_t)(arc4random_uniform(65536));
}
uint32_t
srp_random32()
{
return arc4random();
}
uint64_t
srp_random64()
{
uint64_t ret;
arc4random_buf(&ret, sizeof(ret));
return ret;
}
bool
srp_randombytes(uint8_t *dest, size_t num)
{
arc4random_buf(dest, num);
return true;
}
static void
srp_sec_error_print(const char *reason, OSStatus status)
{
const char *utf8 = NULL;
CFStringRef err = SecCopyErrorMessageString(status, NULL);
if (err != NULL) {
utf8 = CFStringGetCStringPtr(err, kCFStringEncodingUTF8);
}
if (utf8 != NULL) {
ERROR(PUB_S_SRP ": " PUB_S_SRP, reason, utf8);
} else {
ERROR(PUB_S_SRP ": %d", reason, (int)status);
}
if (err != NULL) {
CFRelease(err);
}
}
// Function to generate a key
static srp_key_t *
srp_get_key_internal(const char *key_name, bool delete)
{
long two56 = 256;
srp_key_t *key = NULL;
OSStatus status;
CFMutableDictionaryRef key_parameters = CFDictionaryCreateMutable(NULL, 8,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
CFMutableDictionaryRef pubkey_parameters;
if (key_parameters != NULL) {
CFDictionaryAddValue(key_parameters, kSecAttrIsPermanent, kCFBooleanTrue);
CFDictionaryAddValue(key_parameters, kSecAttrKeyType, kSecAttrKeyTypeECSECPrimeRandom);
CFNumberRef num = CFNumberCreate(NULL, kCFNumberLongType, &two56);
CFDictionaryAddValue(key_parameters, kSecAttrKeySizeInBits, num);
CFRelease(num);
CFStringRef str = CFStringCreateWithCString(NULL, key_name, kCFStringEncodingUTF8);
CFDictionaryAddValue(key_parameters, kSecAttrLabel, str);
CFRelease(str);
CFDictionaryAddValue(key_parameters, kSecReturnRef, kCFBooleanTrue);
CFDictionaryAddValue(key_parameters, kSecMatchLimit, kSecMatchLimitOne);
CFDictionaryAddValue(key_parameters, kSecClass, kSecClassKey);
pubkey_parameters = CFDictionaryCreateMutableCopy(NULL, 8, key_parameters);
if (pubkey_parameters != NULL) {
CFDictionaryAddValue(key_parameters, kSecAttrKeyClass, kSecAttrKeyClassPrivate);
CFDictionaryAddValue(pubkey_parameters, kSecAttrKeyClass, kSecAttrKeyClassPublic);
CFDictionaryAddValue(pubkey_parameters, kSecAttrIsExtractable, kCFBooleanTrue);
CFDictionaryAddValue(key_parameters, kSecAttrIsExtractable, kCFBooleanTrue);
CFDictionaryAddValue(pubkey_parameters, kSecAttrAccessible,
kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly);
if (delete) {
status = SecItemDelete(key_parameters);
if (status == errSecSuccess) {
status = SecItemDelete(pubkey_parameters);
if (status != errSecSuccess) {
ERROR("srp_get_key_internal: failed to delete the public key");
}
}
key = NULL;
} else {
key = calloc(1, sizeof(*key));
if (key != NULL) {
CFErrorRef error = NULL;
// See if the key is already on the keychain.
status = SecItemCopyMatching(key_parameters, (CFTypeRef *)&key->private);
if (status == errSecSuccess) {
status = SecItemCopyMatching(pubkey_parameters, (CFTypeRef *)&key->public);
} else {
key->private = SecKeyCreateRandomKey(key_parameters, &error);
if (key->private != NULL) {
key->public = SecKeyCopyPublicKey(key->private);
}
}
if (key->public == NULL || key->private == NULL) {
if (error != NULL) {
CFShow(error);
} else {
srp_sec_error_print("Failed to get key pair", status);
}
free(key);
key = NULL;
}
}
}
CFRelease(key_parameters);
CFRelease(pubkey_parameters);
}
}
return key;
}
srp_key_t *
srp_get_key(const char *key_name, void *__unused os_context)
{
return srp_get_key_internal(key_name, false);
}
// Remove an existing key
int
srp_reset_key(const char *key_name, void *__unused os_context)
{
srp_get_key_internal(key_name, true);
return kDNSServiceErr_NoError;
}
// void to get the length of the public key
size_t
srp_pubkey_length(srp_key_t *key)
{
(void)key;
return ECDSA_KEY_SIZE;
}
uint8_t
srp_key_algorithm(srp_key_t *key)
{
(void)key;
return dnssec_keytype_ecdsa;
}
size_t
srp_signature_length(srp_key_t *key)
{
(void)key;
return ECDSA_KEY_SIZE;
}
// Function to copy out the public key as binary data
size_t
srp_pubkey_copy(uint8_t *buf, size_t max, srp_key_t *key)
{
CFErrorRef error = NULL;
size_t ret = 0;
CFDataRef pubkey = SecKeyCopyExternalRepresentation(key->public, &error);
if (pubkey == NULL) {
if (error != NULL) {
CFShow(error);
} else {
ERROR("Unknown failure in SecKeyCopyExternalRepresentation");
}
} else {
const uint8_t *bytes = CFDataGetBytePtr(pubkey);
unsigned long len = (unsigned long)CFDataGetLength(pubkey);
// Should be 04 | X | Y
if (bytes[0] != 4) {
ERROR("Unexpected preamble to public key: %d", bytes[0]);
} else if (len - 1 > max) {
ERROR("Not enough room for public key in buffer: %ld > %zd", len - 1, max);
} else if (len - 1 != ECDSA_KEY_SIZE) {
ERROR("Unexpected key size %ld", len - 1);
} else {
memcpy(buf, bytes + 1, len - 1);
ret = ECDSA_KEY_SIZE;
}
CFRelease(pubkey);
}
return ret;
}
// Function to generate a signature given some data and a private key
int
srp_sign(uint8_t *output, size_t max, uint8_t *message, size_t msglen,
uint8_t *rr, size_t rdlen, srp_key_t *key)
{
CFMutableDataRef payload = NULL;
CFDataRef signature = NULL;
CFErrorRef error = NULL;
const uint8_t *bytes;
unsigned long len;
int ret = 0;
if (max < ECDSA_SHA256_SIG_SIZE) {
ERROR("srp_sign: not enough space in output buffer (%lu) for signature (%d).",
(unsigned long)max, ECDSA_SHA256_SIG_SIZE);
return 0;
}
payload = CFDataCreateMutable(NULL, (CFIndex)(msglen + rdlen));
if (payload == NULL) {
ERROR("srp_sign: CFDataCreateMutable failed on length %zd", msglen + rdlen);
return 0;
}
CFDataAppendBytes(payload, rr, (CFIndex)rdlen);
CFDataAppendBytes(payload, message, (CFIndex)msglen);
signature = SecKeyCreateSignature(key->private,
kSecKeyAlgorithmECDSASignatureMessageX962SHA256, payload, &error);
CFRelease(payload);
if (error != NULL) {
CFRelease(signature);
CFShow(error);
return 0;
}
if (signature == NULL) {
ERROR("No error, but no signature.");
return 0;
}
len = (unsigned long)CFDataGetLength(signature);
bytes = CFDataGetBytePtr(signature);
// The buffer is ASN.1 DER encoded as two numbers, so should be 30 <len> 02 <len> <bytes> 02 <len> <bytes>.
if (len < 8) {
ERROR("signature is too short to parse: %lu bytes", len);
goto out;
}
#define ASN1_SEQUENCE 0x30
if (bytes[0] != ASN1_SEQUENCE) {
ERROR("Unexpected ASN.1 type for signature: %x", bytes[0]);
goto out;
}
unsigned len_sequence = bytes[1];
if (len_sequence != len - 2) { // This is the length of the sequence, which is the remainder of the buffer
ERROR("Unexpected ASN.1 sequence length %u when %lu bytes remain", len_sequence, len - 2);
goto out;
}
const uint8_t *sequence_start = &bytes[2];
unsigned sequence_available = len_sequence;
int index = 0;
while (sequence_available > 0) {
if (index > 1) {
ERROR("Unexpected extra datum in top-level ASN.1 sequence for signature.");
goto out;
}
#define ASN1_INTEGER 0x02
if (sequence_start[0] != ASN1_INTEGER) {
ERROR("Unexpected ASN.1 type for key half %d: %x", index, bytes[2]);
goto out;
}
unsigned len_half = sequence_start[1];
if (len_half > sequence_available - 2 || len_half > ECDSA_SHA256_SIG_PART_SIZE + 1) {
ERROR("key half %d is too long: length %u, sequence length %u, sig part size %d",
index, len_half, len_sequence, ECDSA_SHA256_SIG_PART_SIZE);
goto out;
}
// Now that we've bounds-checked this key half, reduce the available space by its length for
// the next round.
sequence_available = sequence_available - len_half - 2;
// The key data are bignums. If the first byte of either bignum is >0x7f, it will include a leading zero
// to make it unsigned, which we don't need.
const uint8_t *key_half = sequence_start[2] == 0 ? &sequence_start[3] : &sequence_start[2];
if (sequence_start[2] == 0) {
len_half--;
}
unsigned long diff = ECDSA_SHA256_SIG_PART_SIZE - len_half;
uint8_t *output_start = output + index * ECDSA_SHA256_SIG_PART_SIZE;
if (diff > 0) {
memset(output_start, 0, diff);
}
memcpy(output_start + diff, key_half, len_half);
sequence_start = key_half + len_half;
index++;
}
ret = ECDSA_SHA256_SIG_PART_SIZE * 2;
for (int j = 0; j < 2; j++) {
const uint8_t *buf = j == 0 ? bytes : output;
char sigbuf[300];
char *sigbufp = sigbuf;
for (unsigned i = 0; i < len && (size_t)(sigbufp - sigbuf) < sizeof(sigbuf) - 3; i++) {
snprintf(sigbufp, 4, "%02x ", buf[i]);
sigbufp += 3;
}
*sigbufp = 0;
INFO("%s: %s", j == 0 ? "input" : "output", sigbuf);
}
out:
if (signature != NULL) {
CFRelease(signature);
}
return ret;
}
// Local Variables:
// mode: C
// tab-width: 4
// c-file-style: "bsd"
// c-basic-offset: 4
// fill-column: 108
// indent-tabs-mode: nil
// End: