+/*
+ * Copyright (c) 2008-2010,2012-2014 Apple Inc. All Rights Reserved.
+ *
+ * @APPLE_LICENSE_HEADER_START@
+ *
+ * This file contains Original Code and/or Modifications of Original Code
+ * as defined in and that are subject to the Apple Public Source License
+ * Version 2.0 (the 'License'). You may not use this file except in
+ * compliance with the License. Please obtain a copy of the License at
+ * http://www.opensource.apple.com/apsl/ and read it before using this
+ * file.
+ *
+ * The Original Code and all software distributed under the License are
+ * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
+ * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
+ * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
+ * Please see the License for the specific language governing rights and
+ * limitations under the License.
+ *
+ * @APPLE_LICENSE_HEADER_END@
+ */
+
+#include "SecSCEP.h"
+
+#include <Security/SecCMS.h>
+#include <Security/SecRandom.h>
+#include <Security/SecIdentityPriv.h>
+#include <string.h>
+#include <AssertMacros.h>
+#include <CommonCrypto/CommonDigest.h>
+#include <CommonCrypto/CommonDigestSPI.h>
+#include <Security/SecItem.h>
+#include <Security/SecInternal.h>
+#include <Security/SecCertificateInternal.h>
+#include <Security/SecKeyPriv.h>
+#include <Security/SecInternal.h>
+#include <libDER/DER_Encode.h>
+#include <uuid/uuid.h>
+#include <utilities/array_size.h>
+#include <utilities/debugging.h>
+#include <utilities/SecIOFormat.h>
+
+typedef enum {
+ messageType = 2,
+ pkiStatus = 3,
+ failInfo = 4,
+ senderNonce = 5,
+ recipientNonce = 6,
+ transId = 7
+} scep_attr_t;
+
+static CF_RETURNS_RETAINED CFDataRef scep_oid(scep_attr_t type)
+{
+/* +-------------------+-----------------------------------------------+
+ | Name | ASN.1 Definition |
+ +-------------------+-----------------------------------------------+
+ | id-VeriSign | OBJECT_IDENTIFIER ::= {2 16 US(840) 1 |
+ | | VeriSign(113733)} |
+ | id-pki | OBJECT_IDENTIFIER ::= {id-VeriSign pki(1)} |
+ | id-attributes | OBJECT_IDENTIFIER ::= {id-pki attributes(9)} |
+ | id-messageType | OBJECT_IDENTIFIER ::= {id-attributes |
+ | | messageType(2)} |
+ | id-pkiStatus | OBJECT_IDENTIFIER ::= {id-attributes |
+ | | pkiStatus(3)} |
+ | id-failInfo | OBJECT_IDENTIFIER ::= {id-attributes |
+ | | failInfo(4)} |
+ | id-senderNonce | OBJECT_IDENTIFIER ::= {id-attributes |
+ | | senderNonce(5)} |
+ | id-recipientNonce | OBJECT_IDENTIFIER ::= {id-attributes |
+ | | recipientNonce(6)} |
+ | id-transId | OBJECT_IDENTIFIER ::= {id-attributes |
+ | | transId(7)} |
+ | id-extensionReq | OBJECT_IDENTIFIER ::= {id-attributes |
+ | | extensionReq(8)} |
+ +-------------------+-----------------------------------------------+ */
+ uint8_t oid_scep_attrs[] =
+ { 0x60, 0x86, 0x48, 0x01, 0x86, 0xF8, 0x45, 0x01, 0x09, 0 };
+ /* messageType:2 pkiStatus:3 failInfo:4 senderNonce:5 recipientNonce:6 transId:7 */
+ if ((type < messageType) || (type > transId))
+ return NULL;
+
+ oid_scep_attrs[sizeof(oid_scep_attrs) - 1] = type;
+ return CFDataCreate(kCFAllocatorDefault, oid_scep_attrs, sizeof(oid_scep_attrs));
+}
+
+static const char CertRep[] = "3";
+static const char PKCSReq[] = "19";
+static const char GetCertInitial[] = "20";
+__unused static const char GetCert[] = "21";
+__unused static const char GetCRL[] = "22";
+static const char PKIStatusSUCCESS[] = "0";
+__unused static const char PKIStatusFAILURE[] = "2";
+static const char PKIStatusPENDING[] = "3";
+
+static CF_RETURNS_RETAINED CFDataRef
+printable_string_data(size_t length, const char *bytes)
+{
+ DERSize der_length_len = DERLengthOfLength(length);
+ size_t value_length = sizeof(SecASN1PrintableString) + der_length_len + length;
+ CFMutableDataRef data = CFDataCreateMutable(kCFAllocatorDefault, value_length);
+ CFDataSetLength(data, value_length);
+ uint8_t *ptr = (uint8_t *)CFDataGetBytePtr(data);
+ *ptr++ = SecASN1PrintableString;
+ DEREncodeLength(length, ptr, &der_length_len);
+ ptr += der_length_len;
+ memcpy(ptr, bytes, length);
+ return (CFDataRef)data;
+}
+
+#define scep_result(value) printable_string_data(sizeof(value)-1, value)
+
+static CF_RETURNS_NOT_RETAINED CFTypeRef
+dictionary_array_value_1(CFDictionaryRef attrs, CFTypeRef attr)
+{
+ CFTypeRef value = NULL;
+ CFArrayRef attr_values = NULL;
+
+ require(attr_values = (CFArrayRef)CFDictionaryGetValue(attrs, attr), out);
+ require(CFArrayGetCount(attr_values) == 1, out);
+ value = CFArrayGetValueAtIndex(attr_values, 0);
+out:
+ return value;
+}
+
+/* @@@ consider splitting into function returning single value
+ and function creating printable string from c str */
+static bool scep_attr_has_val(CFDictionaryRef attrs, scep_attr_t attr, const char *val)
+{
+ bool result = false;
+ CFDataRef msgtype_value_data = printable_string_data(strlen(val), val);
+ CFArrayRef msgtype_value_datas = CFArrayCreate(kCFAllocatorDefault,
+ (const void **)&msgtype_value_data, 1, &kCFTypeArrayCallBacks);
+ CFRelease(msgtype_value_data);
+ CFDataRef msgtype_oid_data = scep_oid(attr);
+ CFArrayRef msgtype_values = (CFArrayRef)CFDictionaryGetValue(attrs, msgtype_oid_data);
+ CFRelease(msgtype_oid_data);
+ if (msgtype_values && CFEqual(msgtype_value_datas, msgtype_values))
+ result = true;
+ CFRelease(msgtype_value_datas);
+
+ return result;
+}
+
+static CF_RETURNS_RETAINED CFDataRef hexencode(CFDataRef data)
+{
+ CFIndex ix, length = CFDataGetLength(data);
+ const uint8_t *bin_data = CFDataGetBytePtr(data);
+ uint8_t *hex_data = calloc(1, 2*length + 1);
+ require(length && bin_data && hex_data, out);
+
+ for (ix = 0; ix < length; ix++)
+ snprintf((char *)&hex_data[2*ix], 3, "%02X", bin_data[ix]);
+
+ return CFDataCreateWithBytesNoCopy(kCFAllocatorDefault, hex_data,
+ 2*length, kCFAllocatorMalloc);
+out:
+ if (hex_data)
+ free(hex_data);
+ return NULL;
+}
+
+static CF_RETURNS_RETAINED CFDataRef pubkeyhash(SecKeyRef key)
+{
+ CFTypeRef key_type = NULL;
+ CFDictionaryRef pubkey_attrs = NULL;
+ CFDataRef hash_pubkey_data = NULL, pubkey_data = NULL;
+ uint8_t pubkey_hash[CC_SHA1_DIGEST_LENGTH];
+
+ require(pubkey_attrs = SecKeyCopyAttributeDictionary(key), out);
+ require( (key_type = CFDictionaryGetValue(pubkey_attrs, kSecAttrKeyClass)) &&
+ CFEqual(key_type, kSecAttrKeyClassPublic), out);
+ require(pubkey_data = CFDictionaryGetValue(pubkey_attrs, kSecValueData), out);
+ require((unsigned long)CFDataGetLength(pubkey_data)<=UINT32_MAX, out); /* Correct as long as CFIndex is long */
+ CCDigest(kCCDigestSHA1, CFDataGetBytePtr(pubkey_data), (CC_LONG)CFDataGetLength(pubkey_data), pubkey_hash);
+ hash_pubkey_data = CFDataCreate(kCFAllocatorDefault, pubkey_hash, sizeof(pubkey_hash));
+out:
+ CFReleaseSafe(pubkey_attrs);
+ return hash_pubkey_data;
+}
+
+static void generate_sender_nonce(CFMutableDictionaryRef dict)
+{
+ /* random sender nonce, to be verified against recipient nonce in reply */
+ CFDataRef senderNonce_oid_data = scep_oid(senderNonce);
+ uint8_t senderNonce_value[18] = { 4, 16, };
+ SecRandomCopyBytes(kSecRandomDefault, sizeof(senderNonce_value) - 2, senderNonce_value + 2);
+ CFDataRef senderNonce_value_data = CFDataCreate(kCFAllocatorDefault,
+ senderNonce_value, sizeof(senderNonce_value));
+ if (senderNonce_oid_data && senderNonce_value_data)
+ CFDictionarySetValue(dict, senderNonce_oid_data, senderNonce_value_data);
+ CFReleaseNull(senderNonce_oid_data);
+ CFReleaseNull(senderNonce_value_data);
+}
+
+SecIdentityRef SecSCEPCreateTemporaryIdentity(SecKeyRef publicKey, SecKeyRef privateKey)
+{
+ int key_usage = kSecKeyUsageDigitalSignature | kSecKeyUsageKeyEncipherment;
+ CFDictionaryRef self_signed_parameters = NULL;
+ CFNumberRef key_usage_num = NULL;
+ SecCertificateRef self_signed_certificate = NULL;
+ SecIdentityRef self_signed_identity = NULL;
+ CFStringRef cn_uuid = NULL;
+ CFArrayRef cn_dn = NULL, cn_dns = NULL, unique_rdns = NULL;
+
+ key_usage_num = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &key_usage);
+ require(key_usage_num, out);
+
+ const void *key[] = { kSecCertificateKeyUsage };
+ const void *val[] = { key_usage_num };
+ self_signed_parameters = CFDictionaryCreate(kCFAllocatorDefault,
+ key, val, array_size(key),
+ &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
+ require(self_signed_parameters, out);
+
+ char uuid_string[37] = {};
+ uuid_t uuid;
+ uuid_generate_random(uuid);
+ uuid_unparse(uuid, uuid_string);
+ cn_uuid = CFStringCreateWithCString(kCFAllocatorDefault, uuid_string, kCFStringEncodingASCII);
+ require(cn_uuid, out);
+ const void * cn[] = { kSecOidCommonName, cn_uuid };
+ cn_dn = CFArrayCreate(kCFAllocatorDefault, cn, 2, NULL);
+ require(cn_dn, out);
+ cn_dns = CFArrayCreate(kCFAllocatorDefault, (const void **)&cn_dn, 1, NULL);
+ require(cn_dns, out);
+ unique_rdns = CFArrayCreate(kCFAllocatorDefault, (const void **)&cn_dns, 1, NULL);
+ require(unique_rdns, out);
+
+ self_signed_certificate = SecGenerateSelfSignedCertificate(unique_rdns, self_signed_parameters, publicKey, privateKey);
+ require(self_signed_certificate, out);
+ self_signed_identity = SecIdentityCreate(kCFAllocatorDefault, self_signed_certificate, privateKey);
+
+out:
+ CFReleaseSafe(key_usage_num);
+ CFReleaseSafe(self_signed_parameters);
+ CFReleaseSafe(self_signed_certificate);
+ CFReleaseSafe(unique_rdns);
+ CFReleaseSafe(cn_dns);
+ CFReleaseSafe(cn_dn);
+ CFReleaseSafe(cn_uuid);
+
+ return self_signed_identity;
+}
+
+CFDataRef
+SecSCEPGenerateCertificateRequest(CFArrayRef subject, CFDictionaryRef parameters,
+ SecKeyRef publicKey, SecKeyRef privateKey,
+ SecIdentityRef signer, CFTypeRef recipients)
+{
+ CFDataRef csr = NULL;
+ CFMutableDataRef enveloped_data = NULL;
+ CFMutableDictionaryRef simple_attr = NULL;
+ SecIdentityRef self_signed_identity = NULL;
+ CFMutableDataRef signed_request = NULL;
+ SecCertificateRef recipient = NULL;
+ CFDataRef msgtype_value_data = NULL;
+ CFDataRef msgtype_oid_data = NULL;
+
+ if (CFGetTypeID(recipients) == SecCertificateGetTypeID()) {
+ recipient = (SecCertificateRef)recipients;
+ } else if (CFGetTypeID(recipients) == CFArrayGetTypeID()) {
+ CFIndex recipient_count = CFArrayGetCount(recipients);
+ if (recipient_count > 1) {
+ /* get the encryption cert */
+ recipient = (SecCertificateRef)CFArrayGetValueAtIndex(recipients, 0);
+ } else if (recipient_count == 1) {
+ /* if there is at least one we'll assume it's sign+encrypt */
+ recipient = (SecCertificateRef)CFArrayGetValueAtIndex(recipients, 0);
+ }
+ }
+ require(recipient, out);
+
+ require(csr = SecGenerateCertificateRequest(subject, parameters, publicKey, privateKey), out);
+ require(enveloped_data = CFDataCreateMutable(kCFAllocatorDefault, 0), out);
+ require_noerr(SecCMSCreateEnvelopedData(recipient, parameters, csr, enveloped_data), out);
+ CFReleaseNull(csr);
+
+ simple_attr = CFDictionaryCreateMutable(kCFAllocatorDefault, 3,
+ &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
+
+ /* generate a transaction id: hex encoded pubkey hash */
+ CFDataRef public_key_hash = pubkeyhash(publicKey);
+ CFDataRef public_key_hash_hex = hexencode(public_key_hash);
+ CFReleaseSafe(public_key_hash);
+ CFDataRef transid_oid_data = scep_oid(transId);
+ CFDataRef transid_data = printable_string_data(CFDataGetLength(public_key_hash_hex),
+ (const char *)CFDataGetBytePtr(public_key_hash_hex));
+ CFReleaseSafe(public_key_hash_hex);
+
+ CFDictionarySetValue(simple_attr, transid_oid_data, transid_data);
+ CFReleaseNull(transid_oid_data);
+ CFReleaseNull(transid_data);
+
+ /* message type: PKCSReq (19) */
+ msgtype_value_data = NULL;
+ msgtype_oid_data = NULL;
+ require(msgtype_oid_data = scep_oid(messageType), out);
+ require(msgtype_value_data = printable_string_data(strlen(PKCSReq), PKCSReq), out);
+
+ CFDictionarySetValue(simple_attr, msgtype_oid_data, msgtype_value_data);
+ CFReleaseNull(msgtype_oid_data);
+ CFReleaseNull(msgtype_value_data);
+
+ /* random sender nonce, to be verified against recipient nonce in reply */
+ generate_sender_nonce(simple_attr);
+
+ /* XXX/cs remove auto-generation once managedconfig is no longer using this */
+ if (signer) {
+ self_signed_identity = signer;
+ CFRetain(self_signed_identity);
+ } else {
+ self_signed_identity = SecSCEPCreateTemporaryIdentity(publicKey, privateKey);
+
+ /* Add our temporary cert to the keychain for CMS decryption of
+ the reply. If we happened to have picked an existing UUID
+ we fail. We should pick a different UUID and try again. */
+ require(self_signed_identity, out);
+ CFDictionaryRef identity_add = CFDictionaryCreate(NULL,
+ (const void **)&kSecValueRef, (const void **)&self_signed_identity, 1, NULL, NULL);
+ require_noerr_action(SecItemAdd(identity_add, NULL), out,
+ CFReleaseSafe(identity_add));
+ CFReleaseSafe(identity_add);
+ }
+ require(self_signed_identity, out);
+
+ signed_request = CFDataCreateMutable(kCFAllocatorDefault, 0);
+ require_noerr_action(SecCMSCreateSignedData(self_signed_identity, enveloped_data,
+ parameters, simple_attr, signed_request), out, CFReleaseNull(signed_request));
+
+
+out:
+
+ CFReleaseSafe(simple_attr);
+ CFReleaseSafe(msgtype_oid_data);
+ CFReleaseSafe(msgtype_value_data);
+ CFReleaseSafe(self_signed_identity);
+ CFReleaseSafe(enveloped_data);
+ CFReleaseSafe(csr);
+ return signed_request;
+}
+
+
+CFDataRef
+SecSCEPCertifyRequest(CFDataRef request, SecIdentityRef ca_identity, CFDataRef serialno, bool pend_request)
+{
+ CFDictionaryRef simple_attr = NULL;
+ SecCertificateRef ca_certificate = NULL;
+ SecKeyRef ca_public_key = NULL;
+ SecCertificateRef cert = NULL;
+ SecPolicyRef policy = NULL;
+ CFDataRef cert_pkcs7 = NULL;
+ CFMutableDataRef cert_msg = NULL;
+ CFMutableDataRef signed_reply = NULL;
+ SecTrustRef trust = NULL;
+ CFDataRef signed_content = NULL;
+ CFDictionaryRef signed_attributes = NULL;
+ SecCertificateRef signer_cert = NULL;
+ CFDataRef transid_oid_data = NULL, senderNonce_oid_data = NULL, transid_value = NULL;
+ CFDataRef subject = NULL, extensions = NULL, senderNonce_value = NULL;
+ CFStringRef challenge = NULL;
+ SecKeyRef tbsPublicKey = NULL;
+ CFMutableDataRef encrypted_content = NULL;
+ SecCertificateRef recipient = NULL;
+ CFDictionaryRef parameters = NULL;
+
+ require_noerr(SecIdentityCopyCertificate(ca_identity, &ca_certificate), out);
+ ca_public_key = SecCertificateCopyPublicKey(ca_certificate); /*@@@*/
+
+ /* unwrap outer layer: */
+ policy = SecPolicyCreateBasicX509();
+
+ require_noerr(SecCMSVerifyCopyDataAndAttributes(request, NULL,
+ policy, &trust, &signed_content, &signed_attributes), out);
+ /* remember signer: is signer certified by us, then re-certify, no challenge needed */
+ SecTrustResultType result;
+ require_noerr(SecTrustEvaluate(trust, &result), out);
+ require (signer_cert = SecTrustGetCertificateAtIndex(trust, 0), out);
+ bool recertify = !SecCertificateIsSignedBy(signer_cert, ca_public_key);
+
+ /* msgType should be certreq msg */
+ require(scep_attr_has_val(signed_attributes, messageType, PKCSReq), out);
+
+ /* remember transaction id just for reuse */
+ require(transid_oid_data = scep_oid(transId), out);
+ require(transid_value =
+ dictionary_array_value_1(signed_attributes, transid_oid_data), out);
+
+ /* senderNonce becomes recipientNonce */
+ require(senderNonce_oid_data = scep_oid(senderNonce), out);
+ require(senderNonce_value =
+ dictionary_array_value_1(signed_attributes, senderNonce_oid_data), out);
+
+ /* decrypt the request */
+ encrypted_content = CFDataCreateMutable(kCFAllocatorDefault, 0);
+ require_noerr(SecCMSDecryptEnvelopedData(signed_content, encrypted_content, &recipient), out);
+ require(recipient && CFEqual(ca_certificate, recipient), out);
+
+ /* verify CSR */
+ require(SecVerifyCertificateRequest(encrypted_content, &tbsPublicKey, &challenge, &subject, &extensions), out);
+ CFReleaseNull(encrypted_content);
+
+ /* @@@
+ // alternatively send a pending message
+ // pkistatus {{id-attributes pkiStatus(3)} "FAILURE"}
+ // failInfo {{id-attributes failInfo(4)} "the reason to reject"}
+ */
+
+ /* verify challenge - this would need to be a callout that can determine
+ the challenge appropriate for the subject */
+ if (!recertify)
+ require( challenge && (CFStringGetTypeID() == CFGetTypeID(challenge)) &&
+ CFEqual(CFSTR("magic"), challenge), out);
+
+ require(cert_msg = CFDataCreateMutable(kCFAllocatorDefault, 0), out);
+
+ if (!pend_request) {
+ /* sign cert */
+ cert = SecIdentitySignCertificate(ca_identity, serialno,
+ tbsPublicKey, subject, extensions);
+
+ /* degenerate cms with cert */
+ require (cert_pkcs7 = SecCMSCreateCertificatesOnlyMessage(cert), out);
+ CFReleaseNull(cert);
+
+ /* envelope for client */
+ require_noerr(SecCMSCreateEnvelopedData(signer_cert, NULL, cert_pkcs7, cert_msg), out);
+ CFReleaseNull(cert_pkcs7);
+ }
+
+ CFDataRef pki_status_oid = scep_oid(pkiStatus);
+ CFDataRef pki_status_value = pend_request ? scep_result(PKIStatusPENDING) : scep_result(PKIStatusSUCCESS);
+ CFDataRef message_type_oid = scep_oid(messageType), message_type_value = scep_result(CertRep);
+ const void *oid[] = { transid_oid_data, pki_status_oid, message_type_oid };
+ const void *value[] = { transid_value, pki_status_value, message_type_value };
+ simple_attr = CFDictionaryCreate(kCFAllocatorDefault, oid, value, array_size(oid),
+ &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
+ CFReleaseSafe(pki_status_oid); CFReleaseSafe(pki_status_value);
+ CFReleaseSafe(message_type_oid); CFReleaseSafe(message_type_value);
+
+ /* sign with ra/ca cert and add attributes */
+ signed_reply = CFDataCreateMutable(kCFAllocatorDefault, 0);
+ const void *signing_params[] = { kSecCMSCertChainMode };
+ const void *signing_params_vals[] = { kSecCMSCertChainModeNone };
+ parameters = CFDictionaryCreate(kCFAllocatorDefault, signing_params, signing_params_vals, array_size(signing_params), &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
+ require_noerr_action(SecCMSCreateSignedData(ca_identity, cert_msg, parameters, simple_attr, signed_reply), out, CFReleaseNull(signed_reply));
+
+out:
+ CFReleaseSafe(ca_certificate);
+ CFReleaseSafe(ca_public_key);
+ CFReleaseSafe(cert);
+ CFReleaseSafe(cert_pkcs7);
+ CFReleaseSafe(cert_msg);
+ CFReleaseSafe(trust);
+ CFReleaseSafe(policy);
+ CFReleaseSafe(signed_content);
+ CFReleaseSafe(signed_attributes);
+ CFReleaseSafe(transid_oid_data);
+ CFReleaseSafe(senderNonce_oid_data);
+ CFReleaseSafe(subject);
+ CFReleaseSafe(extensions);
+ CFReleaseSafe(challenge);
+ CFReleaseSafe(tbsPublicKey);
+ CFReleaseSafe(encrypted_content);
+ CFReleaseSafe(simple_attr);
+ CFReleaseSafe(recipient);
+ CFReleaseSafe(parameters);
+
+ return signed_reply;
+}
+
+static CFStringRef
+copy_signed_attr_printable_string_value(CFDictionaryRef signed_attributes, scep_attr_t attr)
+{
+ CFStringRef printable_string = NULL;
+ CFDataRef key_oid = NULL;
+
+ key_oid = scep_oid(attr);
+ require(key_oid, out);
+
+ CFArrayRef values = (CFArrayRef)CFDictionaryGetValue(signed_attributes, key_oid);
+ require_quiet(values && (CFGetTypeID(values) == CFArrayGetTypeID())
+ && (CFArrayGetCount(values) == 1), out);
+ CFDataRef value = CFArrayGetValueAtIndex(values, 0);
+ const uint8_t *bytes = CFDataGetBytePtr(value);
+ size_t length = CFDataGetLength(value);
+ require(length >= 2, out);
+ require(bytes[0] == 0x13, out);
+ /* no scep responses defined that are longer */
+ require(!(bytes[1] & 0x80) && (bytes[1] == length-2), out);
+ printable_string = CFStringCreateWithBytes(kCFAllocatorDefault,
+ bytes + 2, length - 2, kCFStringEncodingASCII, false);
+out:
+ CFReleaseSafe(key_oid);
+
+ return printable_string;
+}
+
+CFArrayRef
+SecSCEPVerifyReply(CFDataRef request, CFDataRef reply, CFTypeRef ca_certificates,
+ CFErrorRef *server_error)
+{
+ SecKeyRef ca_public_key = NULL;
+ SecCertificateRef cert = NULL;
+ SecPolicyRef policy = NULL;
+ CFDataRef cert_msg = NULL;
+ CFMutableDataRef enc_cert_msg = NULL;
+ SecTrustRef trust = NULL;
+ CFDataRef signed_content = NULL;
+ CFDictionaryRef signed_attributes = NULL;
+ CFDictionaryRef attributes = NULL;
+ SecCertificateRef signer_cert = NULL;
+
+ CFMutableDataRef encrypted_content = NULL;
+ SecCertificateRef recipient = NULL;
+ CFArrayRef certificates = NULL;
+
+ SecCertificateRef reply_signer = NULL;
+
+ CFStringRef msg_type = NULL;
+ CFStringRef pki_status = NULL;
+
+ if (CFGetTypeID(ca_certificates) == SecCertificateGetTypeID()) {
+ reply_signer = (SecCertificateRef)ca_certificates;
+ } else if (CFGetTypeID(ca_certificates) == CFArrayGetTypeID()) {
+ CFIndex reply_signer_count = CFArrayGetCount(ca_certificates);
+ if (reply_signer_count > 1) {
+ /* get the signer cert */
+ reply_signer = (SecCertificateRef)CFArrayGetValueAtIndex(ca_certificates, 1);
+ } else if (reply_signer_count == 1) {
+ /* if there is at least one we'll assume it's sign+encrypt */
+ reply_signer = (SecCertificateRef)CFArrayGetValueAtIndex(ca_certificates, 0);
+ }
+ }
+ require(reply_signer, out);
+
+ /* unwrap outer layer */
+ policy = SecPolicyCreateBasicX509();
+ CFArrayRef additional_certificates = CFArrayCreate(kCFAllocatorDefault, (const void **)&reply_signer, 1, &kCFTypeArrayCallBacks);
+ require_noerr(SecCMSVerifySignedData(reply, NULL,
+ policy, &trust, additional_certificates, &signed_content, &attributes), out);
+ CFReleaseSafe(additional_certificates);
+ if (attributes)
+ signed_attributes = CFDictionaryGetValue(attributes, kSecCMSSignedAttributes);
+
+ /* response should be signed by ra */
+ SecTrustResultType result;
+ require_noerr(SecTrustEvaluate(trust, &result), out);
+ require(signer_cert = SecTrustGetCertificateAtIndex(trust, 0), out);
+ require(CFEqual(reply_signer, signer_cert), out);
+
+ /* msgType should be certreq msg */
+ require(signed_attributes, out);
+ msg_type = copy_signed_attr_printable_string_value(signed_attributes, messageType);
+ pki_status = copy_signed_attr_printable_string_value(signed_attributes, pkiStatus);
+
+ if (msg_type || pki_status) {
+ require(msg_type && CFEqual(msg_type, CFSTR("3")), out);
+
+ require(pki_status, out);
+ if (CFEqual(pki_status, CFSTR("2"))) {
+ goto out; // FAILURE, the end (return NULL)
+ } else if (CFEqual(pki_status, CFSTR("3"))) {
+ CFDataRef transid_oid_data = NULL, transid_value = NULL;
+ CFDictionaryRef err_dict = NULL;
+ require(transid_oid_data = scep_oid(transId), inner_out);
+ require(transid_value = dictionary_array_value_1(signed_attributes, transid_oid_data), inner_out);
+ err_dict = CFDictionaryCreate(kCFAllocatorDefault, (const void **)&transid_oid_data, (const void **)&transid_value, 1, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
+ if (server_error)
+ *server_error = CFErrorCreate(kCFAllocatorDefault, CFSTR("PENDING"), 3, err_dict);
+ inner_out:
+ CFReleaseSafe(err_dict);
+ CFReleaseSafe(transid_oid_data);
+ goto out;
+ }
+ require(CFEqual(pki_status, CFSTR("0")), out);
+ }
+
+ // can we decode the request?
+ encrypted_content = CFDataCreateMutable(kCFAllocatorDefault, 0);
+ require_noerr(SecCMSDecryptEnvelopedData(signed_content, encrypted_content, &recipient), out);
+ require(recipient, out);
+ // verify recipient belongs with our private key
+
+ // verify CSR:
+ require(certificates = SecCMSCertificatesOnlyMessageCopyCertificates(encrypted_content), out);
+
+ // recipient is either our temporary self-signed cert or the old cert we just used
+ // to recertify. if we have new certificates and have stored them successfully we
+ // can now get rid of the cert.
+ /* XXX/cs
+ This should move outside of thise function when we force a signer
+ to be passed in */
+ CFDictionaryRef cert_delete = CFDictionaryCreate(NULL,
+ (const void **)&kSecValueRef, (const void **)&recipient, 1, NULL, NULL);
+ require_noerr_action(SecItemDelete(cert_delete), out,
+ CFReleaseSafe(cert_delete));
+ CFReleaseSafe(cert_delete);
+
+out:
+ CFReleaseSafe(ca_public_key);
+ CFReleaseSafe(cert);
+ CFReleaseSafe(cert_msg);
+ CFReleaseSafe(enc_cert_msg);
+ CFReleaseSafe(trust);
+ CFReleaseSafe(policy);
+ CFReleaseSafe(signed_content);
+ CFReleaseSafe(encrypted_content);
+ CFReleaseSafe(recipient);
+ CFReleaseSafe(msg_type);
+ CFReleaseSafe(pki_status);
+ CFReleaseSafe(attributes);
+
+ return certificates;
+}
+
+OSStatus SecSCEPValidateCACertMessage(CFArrayRef certs,
+ CFDataRef ca_fingerprint,
+ SecCertificateRef *ca_certificate,
+ SecCertificateRef *ra_signing_certificate,
+ SecCertificateRef *ra_encryption_certificate)
+{
+ OSStatus status = errSecParam;
+ SecCertificateRef _ca_certificate = NULL, _ra_signing_certificate = NULL,
+ _ra_encryption_certificate = NULL, _ra_certificate = NULL;
+ CFDataRef ca_cert_data = NULL;
+ CFDataRef ca_hash_cfdata = NULL;
+ CFIndex j, count = CFArrayGetCount(certs);
+ CFMutableArrayRef chain = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
+ SecPolicyRef policy = SecPolicyCreateBasicX509();
+ SecTrustRef trust = NULL;
+ require(chain, out);
+ for (j=0; j<count; j++) {
+ const void *candidate_leaf = CFArrayGetValueAtIndex(certs, j);
+ CFArrayRemoveAllValues(chain);
+ CFArraySetValueAtIndex(chain, 0, candidate_leaf);
+ CFArrayAppendArray(chain, certs, CFRangeMake(0, count));
+ CFArrayRemoveValueAtIndex(chain, 1 + j);
+ require_noerr(SecTrustCreateWithCertificates(chain,
+ policy, &trust), out);
+ SecTrustResultType trust_result;
+ SecTrustEvaluate(trust, &trust_result);
+ CFIndex chain_count = SecTrustGetCertificateCount(trust);
+ secdebug("scep", "candidate leaf: %@ forms chain of length %" PRIdCFIndex, candidate_leaf, chain_count);
+ if (chain_count > 1) {
+ SecCertificateRef leaf = SecTrustGetCertificateAtIndex(trust, 0);
+ SecCertificateRef ca_leaf = SecTrustGetCertificateAtIndex(trust, chain_count - 1);
+ if (!_ca_certificate) {
+ if (ca_fingerprint) {
+ secdebug("scep", "checking ca %@ against fingerprint %@", ca_leaf, ca_fingerprint);
+ uint8_t ca_hash[CC_SHA1_DIGEST_LENGTH]; /*max(md5,sha-1)*/
+ ca_cert_data = SecCertificateCopyData(ca_leaf);
+ require(ca_cert_data, out);
+ size_t ca_data_len = CFDataGetLength(ca_cert_data);
+ size_t ca_fingerprint_len = CFDataGetLength(ca_fingerprint);
+ const uint8_t *ca_data = CFDataGetBytePtr(ca_cert_data);
+ require(ca_data_len && ca_data, out);
+ require(ca_data_len<UINT32_MAX, out);
+ switch (ca_fingerprint_len) {
+ case CC_MD5_DIGEST_LENGTH:
+ CC_MD5(ca_data, (CC_LONG)ca_data_len, ca_hash);
+ break;
+
+ case CC_SHA1_DIGEST_LENGTH:
+ CCDigest(kCCDigestSHA1, ca_data, (CC_LONG)ca_data_len, ca_hash);
+ break;
+
+ default:
+ goto out;
+ }
+ CFReleaseNull(ca_cert_data);
+ ca_hash_cfdata = CFDataCreate(kCFAllocatorDefault, ca_hash, ca_fingerprint_len);
+ require(ca_hash_cfdata, out);
+ require(CFEqual(ca_fingerprint, ca_hash_cfdata), out);
+ CFReleaseNull(ca_hash_cfdata);
+ }
+ _ca_certificate = ca_leaf;
+ CFRetain(ca_leaf);
+ } else {
+ // if ca_certificate is already set, this should be the same
+ require(CFEqual(_ca_certificate, ca_leaf), out);
+ }
+
+ // is leaf allowed to sign and/or encrypt?
+ SecKeyUsage key_usage = SecCertificateGetKeyUsage(leaf);
+ bool can_sign = (key_usage & kSecKeyUsageDigitalSignature);
+ bool can_enc = (key_usage & kSecKeyUsageKeyEncipherment);
+ if (!_ra_certificate && can_sign && can_enc) {
+ _ra_certificate = leaf;
+ CFRetain(leaf);
+ }
+ else if (!_ra_encryption_certificate && !can_sign && can_enc) {
+ _ra_encryption_certificate = leaf;
+ CFRetain(leaf);
+ }
+ else if (!_ra_signing_certificate && !can_enc && can_sign) {
+ _ra_signing_certificate = leaf;
+ CFRetain(leaf);
+ }
+ }
+ if (trust) { CFRelease(trust); trust = NULL; }
+ }
+
+ // we should have both a ca certificate and at least one ra certificate now
+ require(_ca_certificate, out);
+ require(_ra_certificate ||
+ (_ra_signing_certificate && _ra_encryption_certificate), out);
+
+ if (ca_certificate) {
+ *ca_certificate = _ca_certificate;
+ _ca_certificate = NULL;
+ }
+ if (_ra_signing_certificate && _ra_encryption_certificate) {
+ if (ra_signing_certificate) {
+ *ra_signing_certificate = _ra_signing_certificate;
+ _ra_signing_certificate = NULL;
+ }
+ if (ra_encryption_certificate) {
+ *ra_encryption_certificate = _ra_encryption_certificate;
+ _ra_encryption_certificate = NULL;
+ }
+ } else if (_ra_certificate) {
+ if (ra_signing_certificate) {
+ *ra_signing_certificate = _ra_certificate;
+ _ra_certificate = NULL;
+ }
+ }
+
+ status = errSecSuccess;
+
+out:
+ CFReleaseSafe(_ra_encryption_certificate);
+ CFReleaseSafe(_ra_signing_certificate);
+ CFReleaseSafe(_ra_certificate);
+ CFReleaseSafe(_ca_certificate);
+ CFReleaseSafe(ca_cert_data);
+ CFReleaseSafe(ca_hash_cfdata);
+ CFReleaseSafe(policy);
+ CFReleaseSafe(trust);
+ CFReleaseSafe(chain);
+ return status;
+
+}
+
+
+/*!
+ @function SecSCEPGetCertInitial
+ @abstract generate a scep cert initial request, to be presented to
+ a scep server, in case the first request timed out
+ */
+
+// XXX/cs pass CA/RA certificates as a CFTypeRef: one or more certificates for ca_certificate and recipient
+
+CF_RETURNS_RETAINED CFDataRef
+SecSCEPGetCertInitial(SecCertificateRef ca_certificate, CFArrayRef subject, CFDictionaryRef parameters,
+ CFDictionaryRef signed_attrs, SecIdentityRef signer, CFTypeRef recipient)
+{
+ CFMutableDataRef signed_request = NULL;
+ CFMutableDictionaryRef simple_attr = NULL;
+ CFDataRef pki_message_contents = NULL;
+ CFMutableDataRef enveloped_data = NULL;
+ CFDataRef msgtype_value_data = NULL;
+ CFDataRef msgtype_oid_data = NULL;
+
+ require(signed_attrs, out);
+ require(pki_message_contents = SecGenerateCertificateRequestSubject(ca_certificate, subject), out);
+ require(enveloped_data = CFDataCreateMutable(kCFAllocatorDefault, 0), out);
+ require_noerr(SecCMSCreateEnvelopedData(recipient, parameters, pki_message_contents, enveloped_data), out);
+
+ /* remember transaction id just for reuse */
+ simple_attr = CFDictionaryCreateMutableCopy(kCFAllocatorDefault, 3, signed_attrs);
+
+ /* message type: GetCertInitial (20) */
+ require(msgtype_oid_data = scep_oid(messageType), out);
+ require(msgtype_value_data = printable_string_data(sizeof(GetCertInitial) - 1, GetCertInitial), out);
+ CFDictionarySetValue(simple_attr, msgtype_oid_data, msgtype_value_data);
+ CFReleaseNull(msgtype_oid_data);
+ CFReleaseNull(msgtype_value_data);
+
+ /* random sender nonce, to be verified against recipient nonce in reply */
+ generate_sender_nonce(simple_attr);
+ signed_request = CFDataCreateMutable(kCFAllocatorDefault, 0);
+ require_noerr_action(SecCMSCreateSignedData(signer, enveloped_data,
+ parameters, simple_attr, signed_request), out, CFReleaseNull(signed_request));
+
+out:
+ CFReleaseSafe(simple_attr);
+ CFReleaseSafe(pki_message_contents);
+ CFReleaseSafe(enveloped_data);
+ CFReleaseSafe(msgtype_oid_data);
+ CFReleaseSafe(msgtype_value_data);
+ return signed_request;
+}
+
+
+/*
+ +----------------+-----------------+---------------------------+
+ | Attribute | Encoding | Comment |
+ +----------------+-----------------+---------------------------+
+ | transactionID | PrintableString | Decimal value as a string |
+ | messageType | PrintableString | Decimal value as a string |
+ | pkiStatus | PrintableString | Decimal value as a string |
+ | failInfo | PrintableString | Decimal value as a string |
+ | senderNonce | OctetString | |
+ | recipientNonce | OctetString | |
+ +----------------+-----------------+---------------------------+
+
+4.2.1. transactionID
+
+ The transactionID is an attribute which uniquely identifies a
+ transaction. This attribute is required in all PKI messages.
+
+ Because the enrollment transaction could be interrupted by various
+ errors, including network connection errors or client reboot, the
+ SCEP client generates a transaction identifier by calculating a hash
+ on the public key value for which the enrollment is requested. This
+ retains the same transaction identifier throughout the enrollment
+ transaction, even if the client has rebooted or timed out, and issues
+ a new enrollment request for the same key pair.
+
+ It also provides the way for the CA to uniquely identify a
+ transaction in its database. At the requester side, it generates a
+ transaction identifier which is included in PKCSReq. If the CA
+ returns a response of PENDING, the requester will poll by
+ periodically sending out GetCertInitial with the same transaction
+ identifier until either a response other than PENDING is obtained, or
+ the configured maximum time has elapsed.
+
+ For non-enrollment message (for example GetCert and GetCRL), the
+ transactionID should be a number unique to the client.
+
+
+4.2.2. messageType
+
+ The messageType attribute specify the type of operation performed by
+ the transaction. This attribute is required in all PKI messages.
+ Currently, the following message types are defined:
+
+ o PKCSReq (19) -- PKCS#10 [RFC2986] certificate request
+
+ o CertRep (3) -- Response to certificate or CRL request
+
+ o GetCertInitial (20) -- Certificate polling in manual enrollment
+
+ o GetCert (21) -- Retrieve a certificate
+
+ o GetCRL (22) -- Retrieve a CRL
+
+4.2.3. pkiStatus
+
+ All response message will include transaction status information
+ which is defined as pkiStatus attribute:
+
+ o SUCCESS (0) -- request granted
+
+ o FAILURE (2) -- request rejected. This also requires a failInfo
+ attribute to be present, as defined in section 4.2.4.
+
+ o PENDING (3) -- request pending for manual approval
+
+
+4.2.4. failInfo
+
+ The failInfo attribute will contain one of the following failure
+ reasons:
+
+ o badAlg (0) -- Unrecognized or unsupported algorithm ident
+
+ o badMessageCheck (1) -- integrity check failed
+
+ o badRequest (2) -- transaction not permitted or supported
+
+ o badTime (3) -- Message time field was not sufficiently close to
+ the system time
+
+ o badCertId (4) -- No certificate could be identified matching the
+ provided criteria
+
+4.2.5. senderNonce and responderNonce
+
+ The attributes of senderNonce and recipientNonce are the 16 byte
+ random numbers generated for each transaction to prevent the replay
+ attack.
+
+ When a requester sends a PKI message to the server, a senderNonce is
+ included in the message. After the server processes the request, it
+ will send back the requester senderNonce as the recipientNonce and
+ generates another nonce as the senderNonce in the response message.
+ Because the proposed PKI protocol is a two-way communication
+ protocol, it is clear that the nonce can only be used by the
+ requester to prevent the replay. The server has to employ extra
+ state related information to prevent a replay attack.
+
+*/