#include <TargetConditionals.h>
#if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR
#include "SecurityCommands.h"
#include <unistd.h>
#include <uuid/uuid.h>
#include <AssertMacros.h>
#include <Security/SecItem.h>
#include <Security/SecCertificateRequest.h>
#include <Security/SecCertificatePriv.h>
#include <Security/SecIdentityPriv.h>
#include <Security/SecSCEP.h>
#include <Security/SecCMS.h>
#include <utilities/array_size.h>
#include <utilities/SecIOFormat.h>
#include <CommonCrypto/CommonDigest.h>
#include <CFNetwork/CFNetwork.h>
#include "SecBase64.h"
#include <utilities/SecCFRelease.h>
#include <fcntl.h>
static inline void write_data(const char * path, CFDataRef data)
{
int data_file = open(path, O_CREAT|O_WRONLY|O_TRUNC, 0644);
write(data_file, CFDataGetBytePtr(data), CFDataGetLength(data));
close(data_file);
}
#define BUFSIZE 1024
static CF_RETURNS_RETAINED CFHTTPMessageRef load_request(CFHTTPMessageRef request, CFMutableDataRef data, int retry, bool validate_cert)
{
CFHTTPMessageRef result = NULL;
if (retry < 0)
return result;
CFReadStreamRef rs;
rs = CFReadStreamCreateForHTTPRequest(NULL, request);
if (!validate_cert) {
const void *keys[] = {
kCFStreamSSLValidatesCertificateChain,
};
const void *values[] = {
kCFBooleanFalse,
};
CFDictionaryRef dict = CFDictionaryCreate(NULL, keys, values,
array_size(keys),
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
CFReadStreamSetProperty(rs, kCFStreamPropertySSLSettings, dict);
CFRelease(dict);
CFReadStreamSetProperty(rs, kCFStreamPropertyHTTPAttemptPersistentConnection, kCFBooleanTrue);
}
if (CFReadStreamOpen(rs)) {
do {
UInt8 buf[BUFSIZE];
CFIndex bytesRead = CFReadStreamRead(rs, buf, BUFSIZE);
if (bytesRead > 0) {
CFDataAppendBytes(data, buf, bytesRead);
} else if (bytesRead == 0) {
result = (CFHTTPMessageRef)CFReadStreamCopyProperty(rs, kCFStreamPropertyHTTPResponseHeader);
break;
} else {
CFStreamStatus status = CFReadStreamGetStatus(rs);
CFStreamError error = CFReadStreamGetError(rs);
fprintf(stderr, "CFReadStreamRead status=%ld error(domain=%" PRIdCFIndex " error=%ld)\n",
status, error.domain, (long) error.error);
break;
}
} while (true);
} else {
CFStreamStatus status = CFReadStreamGetStatus(rs);
CFStreamError error = CFReadStreamGetError(rs);
fprintf(stderr, "CFReadStreamRead status=%ld error(domain=%" PRIdCFIndex " error=%ld)\n",
status, error.domain, (long) error.error);
}
CFReadStreamClose(rs);
CFRelease(rs);
return result;
}
static CF_RETURNS_RETAINED CFDataRef MCNetworkLoadRequest(CFURLRef url, CFDataRef content, CFStringRef type,
CFStringRef *contentType, bool validate_cert)
{
CFMutableDataRef out_data = CFDataCreateMutable(kCFAllocatorDefault, 0);
CFHTTPMessageRef response = NULL;
CFStringRef request_type = content ? CFSTR("POST") : CFSTR("GET");
CFHTTPMessageRef request = CFHTTPMessageCreateRequest(kCFAllocatorDefault,
request_type, url, kCFHTTPVersion1_0);
if (content)
CFHTTPMessageSetBody(request, content);
CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Content-Type"), type);
int retries = 1;
do {
response = load_request(request, out_data, 1, validate_cert);
if (!response && retries) {
sleep(5);
CFDataSetLength(out_data, 0);
}
} while (!response && retries--);
CFRelease(request);
CFIndex status_code = response ? CFHTTPMessageGetResponseStatusCode(response) : 0;
if (!response || (200 != status_code)) {
CFStringRef url_string = CFURLGetString(url);
if (url_string && request_type && out_data)
fprintf(stderr, "MCNetworkLoadRequest failed to load\n");
CFReleaseNull(out_data);
goto out;
}
if (contentType)
*contentType = CFHTTPMessageCopyHeaderFieldValue(response, CFSTR("Content-Type"));
out:
CFReleaseSafe(response);
return out_data;
}
static void _query_string_apply(CFMutableStringRef query_string, const void *key, const void *value)
{
CFStringRef escaped_key =
CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault,
(CFStringRef)key, NULL, CFSTR("+/="), kCFStringEncodingUTF8);
CFStringRef escaped_value =
CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault,
(CFStringRef)value, NULL, CFSTR("+/="), kCFStringEncodingUTF8);
if (CFStringGetLength(query_string) > 1)
CFStringAppend(query_string, CFSTR("&"));
CFStringAppendFormat(query_string, NULL, CFSTR("%@=%@"), escaped_key, escaped_value);
CFRelease(escaped_key);
CFRelease(escaped_value);
}
static CF_RETURNS_RETAINED CFURLRef scep_url_operation(CFStringRef base, CFStringRef operation, CFStringRef message)
{
CFURLRef url = NULL, base_url = NULL;
CFMutableStringRef query_string =
CFStringCreateMutableCopy(kCFAllocatorDefault, 0, CFSTR("?"));
require(query_string, out);
require(operation, out);
_query_string_apply(query_string, CFSTR("operation"), operation);
if (message)
_query_string_apply(query_string, CFSTR("message"), message);
base_url = CFURLCreateWithString(kCFAllocatorDefault, base, NULL);
url = CFURLCreateWithString(kCFAllocatorDefault, query_string, base_url);
out:
if (query_string)
CFRelease(query_string);
if (base_url)
CFRelease(base_url);
return url;
}
static CF_RETURNS_RETAINED CFDataRef perform_pki_op(CFStringRef scep_base_url, CFDataRef scep_request, bool scep_can_use_post, bool validate_cert)
{
CFDataRef scep_reply = NULL;
CFURLRef pki_op = NULL;
if (scep_can_use_post) {
pki_op = scep_url_operation(scep_base_url, CFSTR("PKIOperation"), NULL);
scep_reply = MCNetworkLoadRequest(pki_op, scep_request, CFSTR("application/x-pki-message"), NULL, validate_cert);
} else {
SecBase64Result base64_result;
size_t buffer_length = CFDataGetLength(scep_request)*2+1;
char *buffer = malloc(buffer_length);
require(buffer, out);
size_t output_size = SecBase64Encode2(CFDataGetBytePtr(scep_request), CFDataGetLength(scep_request), buffer, buffer_length,
kSecB64_F_LINE_LEN_INFINITE, -1, &base64_result);
*(buffer + output_size) = '\0';
require(!base64_result, out);
CFStringRef message = CFStringCreateWithCString(kCFAllocatorDefault, buffer, kCFStringEncodingASCII);
require(message, out);
pki_op = scep_url_operation(scep_base_url, CFSTR("PKIOperation"), message);
CFRelease(message);
fprintf(stderr, "Performing PKIOperation using GET\n");
scep_reply = MCNetworkLoadRequest(pki_op, NULL, CFSTR("application/x-pki-message"), NULL, validate_cert);
}
out:
CFReleaseSafe(pki_op);
return scep_reply;
}
static inline CF_RETURNS_RETAINED CFStringRef uuid_cfstring()
{
char uuid_string[40] = "CN=";
uuid_t uuid;
uuid_generate_random(uuid);
uuid_unparse(uuid, uuid_string+3);
return CFStringCreateWithCString(kCFAllocatorDefault, uuid_string, kCFStringEncodingASCII);
}
static void make_subject_pairs(const void *value, void *context)
{
CFArrayRef entries = NULL, array = NULL;
if (!CFStringGetLength(value))
return;
entries = CFStringCreateArrayBySeparatingStrings(kCFAllocatorDefault, value, CFSTR("="));
require(entries, out);
if (CFArrayGetCount(entries)) {
array = CFArrayCreate(kCFAllocatorDefault, (const void **)&entries, 1, &kCFTypeArrayCallBacks);
require(array, out);
CFArrayAppendValue((CFMutableArrayRef)context, array);
}
out:
if (entries) CFRelease(entries);
if (array) CFRelease(array);
}
static CFArrayRef make_scep_subject(CFStringRef scep_subject_name)
{
CFMutableArrayRef subject = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
require(subject, out);
CFArrayRef entries = NULL;
entries = CFStringCreateArrayBySeparatingStrings(kCFAllocatorDefault, scep_subject_name, CFSTR("/"));
require(entries, out);
CFArrayApplyFunction(entries, CFRangeMake(0, CFArrayGetCount(entries)), make_subject_pairs, subject);
CFRelease(entries);
if (CFArrayGetCount(subject))
return subject;
out:
if (subject) CFRelease(subject);
return NULL;
}
extern int command_scep(int argc, char * const *argv)
{
int result = 1, verbose = 0;
char ch;
int key_usage = 1, key_bitsize = 1024;
bool validate_cert = true;
CFStringRef scep_challenge = NULL, scep_instance_name = NULL,
scep_subject_name = uuid_cfstring(), scep_subject_alt_name = NULL,
scep_capabilities = NULL;
SecCertificateRef ca_certificate = NULL, ra_certificate = NULL;
CFArrayRef cert_array = NULL;
CFDataRef caps_data = NULL;
while ((ch = getopt(argc, argv, "vu:b:c:n:s:h:xo:")) != -1)
{
switch (ch)
{
case 'v':
verbose++;
break;
case 'u':
key_usage = atoi(optarg);
break;
case 'b':
key_bitsize = atoi(optarg);
break;
case 'c':
CFReleaseNull(scep_challenge);
scep_challenge = CFStringCreateWithCString(kCFAllocatorDefault, optarg, kCFStringEncodingUTF8);
break;
case 'n':
CFReleaseNull(scep_instance_name);
scep_instance_name = CFStringCreateWithCString(kCFAllocatorDefault, optarg, kCFStringEncodingUTF8);
break;
case 's':
CFReleaseNull(scep_subject_name);
scep_subject_name = CFStringCreateWithCString(kCFAllocatorDefault, optarg, kCFStringEncodingUTF8);
break;
case 'h':
CFReleaseNull(scep_subject_alt_name);
scep_subject_alt_name = CFStringCreateWithCString(kCFAllocatorDefault, optarg, kCFStringEncodingUTF8);
break;
case 'x':
validate_cert = false;
break;
case 'o':
CFReleaseNull(scep_capabilities);
scep_capabilities = CFStringCreateWithCString(kCFAllocatorDefault, optarg, kCFStringEncodingUTF8);
break;
default:
CFReleaseNull(scep_subject_name);
CFReleaseNull(scep_capabilities);
CFReleaseNull(scep_challenge);
CFReleaseNull(scep_instance_name);
CFReleaseNull(scep_subject_alt_name);
return SHOW_USAGE_MESSAGE;
}
}
argc -= optind;
argv += optind;
if (argc != 1) {
CFReleaseNull(scep_subject_name);
CFReleaseNull(scep_capabilities);
CFReleaseNull(scep_challenge);
CFReleaseNull(scep_instance_name);
CFReleaseNull(scep_subject_alt_name);
return SHOW_USAGE_MESSAGE;
}
CFDataRef scep_request = NULL;
CFArrayRef issued_certs = NULL;
SecCertificateRef leaf = NULL;
SecIdentityRef candidate_identity = NULL;
CFMutableDictionaryRef csr_parameters = NULL;
CFDataRef scep_reply = NULL;
SecKeyRef phone_publicKey = NULL, phone_privateKey = NULL;
CFStringRef scep_base_url = NULL;
CFDictionaryRef identity_add = NULL;
CFNumberRef scep_key_usage = NULL;
CFNumberRef scep_key_bitsize = NULL;
CFURLRef url = NULL;
CFDataRef data = NULL;
CFStringRef ctype = NULL;
scep_base_url = CFStringCreateWithCString(kCFAllocatorDefault, argv[0], kCFStringEncodingASCII);
#if 0
CFStringRef uuid_cfstr = uuid_cfstring();
require(uuid_cfstr, out);
const void * ca_cn[] = { kSecOidCommonName, uuid_cfstr };
CFArrayRef ca_cn_dn = CFArrayCreate(kCFAllocatorDefault, ca_cn, 2, NULL);
const void *ca_dn_array[1];
ca_dn_array[0] = CFArrayCreate(kCFAllocatorDefault, (const void **)&ca_cn_dn, 1, NULL);
CFArrayRef scep_subject = CFArrayCreate(kCFAllocatorDefault, ca_dn_array, array_size(ca_dn_array), NULL);
#else
CFArrayRef scep_subject = make_scep_subject(scep_subject_name);
require(scep_subject, out);
CFShow(scep_subject);
#endif
scep_key_usage = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &key_usage);
scep_key_bitsize = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &key_bitsize);
const void *keygen_keys[] = { kSecAttrKeyType, kSecAttrKeySizeInBits };
const void *keygen_vals[] = { kSecAttrKeyTypeRSA, scep_key_bitsize };
CFDictionaryRef keygen_parameters = CFDictionaryCreate(kCFAllocatorDefault,
keygen_keys, keygen_vals, array_size(keygen_vals),
&kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks );
CFRelease(scep_key_bitsize);
require_noerr(SecKeyGeneratePair(keygen_parameters, &phone_publicKey, &phone_privateKey), out);
CFRelease(keygen_parameters);
SecCertificateRef ra_encryption_certificate = NULL;
CFArrayRef ra_certificates = NULL;
CFTypeRef scep_certificates = NULL;
SecCertificateRef scep_signing_certificate = NULL;
url = scep_url_operation(scep_base_url, CFSTR("GetCACert"), scep_instance_name);
data = MCNetworkLoadRequest(url, NULL, NULL, &ctype, validate_cert);
if (data && ctype) {
if (CFEqual(CFSTR("application/x-x509-ca-cert"), ctype)) {
ca_certificate = SecCertificateCreateWithData(kCFAllocatorDefault, (CFDataRef)data);
fprintf(stderr, "GetCACert returned a single CA certificate.\n");
} else if (CFEqual(ctype, CFSTR("application/x-x509-ca-ra-cert"))) {
cert_array = SecCMSCertificatesOnlyMessageCopyCertificates(data);
require_noerr(SecSCEPValidateCACertMessage(cert_array,
NULL,
&ca_certificate,
&ra_certificate,
&ra_encryption_certificate), out);
if (ra_certificate && ra_encryption_certificate) {
const void *ra_certs[] = { ra_encryption_certificate, ra_certificate };
ra_certificates = CFArrayCreate(kCFAllocatorDefault, ra_certs, array_size(ra_certs), &kCFTypeArrayCallBacks);
fprintf(stderr, "GetCACert returned a separate signing and encryption certificates for RA.\n");
}
}
}
fprintf(stderr, "CA certificate to issue cert:\n");
CFShow(ca_certificate);
if (ra_certificates) {
scep_certificates = ra_certificates;
scep_signing_certificate = ra_certificate;
} else if (ra_certificate) {
scep_certificates = ra_certificate;
scep_signing_certificate = ra_certificate;
} else if (ca_certificate) {
scep_certificates = ca_certificate;
scep_signing_certificate = ca_certificate;
} else {
fprintf(stderr, "Unsupported GetCACert configuration: please file a bug.\n");
goto out;
}
(void) scep_signing_certificate;
bool scep_can_use_post = false;
bool scep_can_use_3des = false;
bool scep_can_use_aes = false;
bool scep_can_use_sha1 = false;
bool scep_can_use_sha512 = false;
bool scep_can_use_sha256 = false;
CFArrayRef caps = NULL;
if (!scep_capabilities) {
CFURLRef ca_caps_url = scep_url_operation(scep_base_url, CFSTR("GetCACaps"), scep_instance_name);
require(ca_caps_url, out);
caps_data = MCNetworkLoadRequest(ca_caps_url, NULL, NULL, NULL, validate_cert);
CFRelease(ca_caps_url);
if (caps_data) {
CFStringRef caps_data_string = CFStringCreateFromExternalRepresentation(kCFAllocatorDefault, caps_data, kCFStringEncodingASCII);
require(caps_data_string, out);
caps = CFStringCreateArrayBySeparatingStrings(kCFAllocatorDefault, caps_data_string, CFSTR("\n"));
if (!caps) {
fprintf(stderr, "GetCACaps couldn't be parsed:\n");
CFShow(caps_data);
}
CFRelease(caps_data);
CFRelease(caps_data_string);
}
} else {
caps = CFStringCreateArrayBySeparatingStrings(kCFAllocatorDefault, scep_capabilities, CFSTR(","));
}
if (caps) {
fprintf(stderr, "GetCACaps advertised following capabilities:\n");
CFShow(caps);
CFRange caps_length = CFRangeMake(0, CFArrayGetCount(caps));
scep_can_use_post = CFArrayContainsValue(caps, caps_length, CFSTR("POSTPKIOperation"));
scep_can_use_3des = CFArrayContainsValue(caps, caps_length, CFSTR("DES3"));
scep_can_use_aes = CFArrayContainsValue(caps, caps_length, CFSTR("AES"));
scep_can_use_sha1 = CFArrayContainsValue(caps, caps_length, CFSTR("SHA-1"));
scep_can_use_sha256 = CFArrayContainsValue(caps, caps_length, CFSTR("SHA-256"));
scep_can_use_sha512 = CFArrayContainsValue(caps, caps_length, CFSTR("SHA-512"));
CFRelease(caps);
}
csr_parameters = CFDictionaryCreateMutable(kCFAllocatorDefault, 0,
&kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
if (scep_key_usage)
CFDictionarySetValue(csr_parameters, kSecCertificateKeyUsage, scep_key_usage);
if (scep_challenge)
CFDictionarySetValue(csr_parameters, kSecCSRChallengePassword, scep_challenge);
else
fprintf(stderr, "No SCEP challenge provided, hope that's ok.\n");
if (scep_can_use_aes) {
CFDictionarySetValue(csr_parameters, kSecCMSBulkEncryptionAlgorithm, kSecCMSEncryptionAlgorithmAESCBC);
} else if (!scep_can_use_3des) {
fprintf(stderr, "SCEP server does not support 3DES -- using it anyway. You must reconfigure your server.\n");
}
if (scep_can_use_sha512) {
CFDictionarySetValue(csr_parameters, kSecCMSSignHashAlgorithm, kSecCMSHashingAlgorithmSHA512);
} else if (scep_can_use_sha256) {
CFDictionarySetValue(csr_parameters, kSecCMSSignHashAlgorithm, kSecCMSHashingAlgorithmSHA256);
} else if (scep_can_use_sha1) {
CFDictionarySetValue(csr_parameters, kSecCMSSignHashAlgorithm, kSecCMSHashingAlgorithmSHA1);
} else {
fprintf(stderr, "SCEP server does not support SHA-1 -- using it anyway. You must reconfigure your server.\n");
}
if (scep_subject_alt_name) {
fprintf(stderr, "Adding subjectAltName to request\n");
CFDictionaryRef subject_alt_name = CFDictionaryCreate(kCFAllocatorDefault,
(const void **)&kSecSubjectAltNameDNSName, (const void **)&scep_subject_alt_name,
1, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
CFDictionarySetValue(csr_parameters, kSecSubjectAltName, subject_alt_name);
}
SecIdentityRef self_signed_identity = SecSCEPCreateTemporaryIdentity(phone_publicKey, phone_privateKey);
identity_add = CFDictionaryCreate(NULL,
(const void **)&kSecValueRef, (const void **)&self_signed_identity, 1, NULL, NULL);
require_noerr(SecItemAdd(identity_add, NULL), out);
require(scep_request = SecSCEPGenerateCertificateRequest((CFArrayRef)scep_subject,
csr_parameters, phone_publicKey, phone_privateKey, self_signed_identity,
scep_certificates), out);
fprintf(stderr, "storing scep_request.der\n");
write_data("scep_request.der", scep_request);
scep_reply = perform_pki_op(scep_base_url, scep_request, scep_can_use_post, validate_cert);
require(scep_reply, out);
require_action(CFDataGetLength(scep_reply), out, fprintf(stderr, "Empty scep_reply, exiting.\n"));
fprintf(stderr, "Storing scep_reply.der\n");
write_data("scep_reply.der", scep_reply);
CFErrorRef server_error = NULL;
int retry_count = 3;
while ( !(issued_certs = SecSCEPVerifyReply(scep_request, scep_reply, scep_certificates, &server_error)) &&
server_error &&
retry_count--)
{
CFDataRef retry_get_cert_initial = NULL;
CFDictionaryRef error_dict = CFErrorCopyUserInfo(server_error);
retry_get_cert_initial = SecSCEPGetCertInitial(ra_certificate ? ra_certificate : ca_certificate, scep_subject, NULL, error_dict, self_signed_identity, scep_certificates);
CFReleaseNull(scep_reply);
CFReleaseSafe(error_dict);
fprintf(stderr, "Waiting 10 seconds before trying a GetCertInitial\n");
sleep(10);
scep_reply = perform_pki_op(scep_base_url, retry_get_cert_initial, scep_can_use_post, validate_cert);
CFReleaseSafe(retry_get_cert_initial);
}
require(issued_certs, out);
require_string(CFArrayGetCount(issued_certs) > 0, out, "No certificates issued.");
leaf = (SecCertificateRef)CFArrayGetValueAtIndex(issued_certs, 0);
require(leaf, out);
CFDataRef leaf_data = SecCertificateCopyData(leaf);
if (leaf_data) {
fprintf(stderr, "Storing issued_cert.der\n");
write_data("issued_cert.der", leaf_data);
CFRelease(leaf_data);
}
CFShow(leaf);
candidate_identity = SecIdentityCreate(kCFAllocatorDefault, leaf, phone_privateKey);
const void *keys_ref_to_persist[] = {
kSecValueRef, kSecAttrLabel };
const void *values_ref_to_persist[] = {
candidate_identity, scep_subject_name };
CFDictionaryRef dict = CFDictionaryCreate(NULL,
(const void **)keys_ref_to_persist,
(const void **)values_ref_to_persist,
array_size(keys_ref_to_persist),
&kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
OSStatus status = SecItemAdd(dict, NULL);
require_noerr_action(status, out, fprintf(stderr, "failed to store new identity, SecItemAdd: %" PRIdOSStatus, status));
result = 0;
out:
if (identity_add)
SecItemDelete(identity_add);
CFReleaseSafe(identity_add);
CFReleaseSafe(candidate_identity);
CFReleaseSafe(scep_request);
CFReleaseSafe(scep_reply);
CFReleaseSafe(scep_key_usage);
CFReleaseSafe(scep_key_bitsize);
CFReleaseSafe(csr_parameters);
CFReleaseSafe(scep_subject_name);
CFReleaseSafe(scep_base_url);
CFReleaseSafe(url);
CFReleaseSafe(issued_certs);
CFReleaseSafe(data);
CFReleaseSafe(ctype);
CFReleaseNull(ca_certificate);
CFReleaseNull(scep_capabilities);
CFReleaseNull(scep_challenge);
CFReleaseNull(scep_instance_name);
CFReleaseNull(scep_subject_alt_name);
CFReleaseNull(cert_array);
CFReleaseNull(caps_data);
return result;
}
#endif // TARGET_OS_MAC