--- /dev/null
+/*
+ * Copyright (c) 2008-2009 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 <libDER/oids.h>
+#include <libDER/DER_Encode.h>
+
+#include <security_asn1/SecAsn1Types.h>
+#include <security_asn1/csrTemplates.h>
+#include <security_asn1/certExtensionTemplates.h>
+#include <security_asn1/secasn1.h>
+#include <security_asn1/SecAsn1Types.h>
+#include <security_asn1/oidsalg.h>
+#include <security_asn1/nameTemplates.h>
+
+#include <TargetConditionals.h>
+#if (TARGET_OS_MAC && !(TARGET_OS_EMBEDDED || TARGET_OS_IPHONE))
+// ENABLE_CMS 0
+OSStatus SecCmsArraySortByDER(void **objs, const SecAsn1Template *objtemplate, void **objs2);
+#else
+// ENABLE_CMS 1
+#include <security_smime/cmspriv.h>
+#endif
+
+#include <Security/SecInternal.h>
+#include <Security/SecCertificatePriv.h>
+#include <Security/SecIdentity.h>
+#include <Security/SecCertificateInternal.h>
+#include <Security/SecItem.h>
+#include <Security/SecKey.h>
+#include <Security/SecRSAKey.h>
+#include <Security/SecKeyPriv.h>
+#include <CommonCrypto/CommonDigest.h>
+#include <CoreFoundation/CFNumber.h>
+#include <CoreFoundation/CFString.h>
+
+#include <AssertMacros.h>
+
+#include "SecCertificateRequest.h"
+
+CFTypeRef kSecOidCommonName = CFSTR("CN");
+CFTypeRef kSecOidCountryName = CFSTR("C");
+CFTypeRef kSecOidStateProvinceName = CFSTR("ST");
+CFTypeRef kSecOidLocalityName = CFSTR("L");
+CFTypeRef kSecOidOrganization = CFSTR("O");
+CFTypeRef kSecOidOrganizationalUnit = CFSTR("OU");
+//CFTypeRef kSecOidEmailAddress = CFSTR("1.2.840.113549.1.9.1");
+// keep natural order: C > ST > L > O > OU > CN > Email
+
+const unsigned char SecASN1PrintableString = SEC_ASN1_PRINTABLE_STRING;
+const unsigned char SecASN1UTF8String = SEC_ASN1_UTF8_STRING;
+
+static void mod128_oid_encoding(CFMutableDataRef dst, uint32_t src, bool final)
+{
+ if (src > 128)
+ mod128_oid_encoding(dst, src / 128, false);
+
+ unsigned char octet = src % 128;
+ if (!final)
+ octet |= 128;
+ CFDataAppendBytes(dst, &octet, 1);
+}
+
+static CFDataRef oid_der(CFStringRef oid_string)
+{
+ CFMutableDataRef oid_der_data = CFDataCreateMutable(kCFAllocatorDefault, 0);
+ CFArrayRef oid = CFStringCreateArrayBySeparatingStrings(kCFAllocatorDefault,
+ oid_string, CFSTR("."));
+ CFIndex i = 0, count = CFArrayGetCount(oid);
+ SInt32 first_digit = 0, digit;
+ for (i = 0; i < count; i++) {
+ CFStringRef oid_octet = CFArrayGetValueAtIndex(oid, i);
+ SInt32 oid_octet_int_value = CFStringGetIntValue(oid_octet);
+ require(abs(oid_octet_int_value) != INT32_MAX, out);
+ if (i == 0)
+ first_digit = oid_octet_int_value;
+ else {
+ if (i == 1)
+ digit = 40 * first_digit + oid_octet_int_value;
+ else
+ digit = oid_octet_int_value;
+ mod128_oid_encoding(oid_der_data, digit, true);
+ }
+ }
+ CFReleaseSafe(oid);
+ return oid_der_data;
+out:
+ CFReleaseSafe(oid_der_data);
+ return NULL;
+}
+
+/*
+Get challenge password conversion and apply this:
+
+ASCII ? => PrintableString subset: [A-Za-z0-9 '()+,-./:=?] ?
+
+PrintableString > IA5String > UTF8String
+
+Consider using IA5String for email address
+*/
+
+static inline bool printable_string(CFStringRef string)
+{
+ bool result = true;
+
+ CFCharacterSetRef printable_charset =
+ CFCharacterSetCreateWithCharactersInString(kCFAllocatorDefault,
+ CFSTR("ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "abcdefghijklmnopqrstuvwxyz"
+ "0123456789 '()+,-./:=?"));
+ CFCharacterSetRef not_printable_charset =
+ CFCharacterSetCreateInvertedSet(kCFAllocatorDefault, printable_charset);
+ CFRange found;
+ if (CFStringFindCharacterFromSet(string, not_printable_charset,
+ CFRangeMake(0, CFStringGetLength(string)), 0, &found))
+ result = false;
+
+ CFReleaseSafe(printable_charset);
+ CFReleaseSafe(not_printable_charset);
+
+ return result;
+}
+
+static bool make_nss_atv(PRArenaPool *poolp,
+ const void * oid, const void * value, const unsigned char type_in, NSS_ATV *nss_atv)
+{
+ size_t length = 0;
+ char *buffer = NULL;
+ unsigned char type = type_in;
+ if (CFGetTypeID(value) == CFStringGetTypeID()) {
+ length = CFStringGetMaximumSizeForEncoding(CFStringGetLength(value),
+ kCFStringEncodingUTF8);
+ buffer = PORT_ArenaAlloc(poolp, length);
+ /* TODO: Switch to using CFStringGetBytes,since this code will do the wrong thing for embedded 0's */
+ if (!CFStringGetCString(value, buffer, length, kCFStringEncodingASCII)) {
+ if (!CFStringGetCString(value, buffer, length, kCFStringEncodingUTF8))
+ return false;
+ if (type && type != SecASN1UTF8String)
+ return false;
+ type = SecASN1UTF8String;
+ }
+ else {
+ if (!type || type == SecASN1PrintableString) {
+ if (!printable_string(value))
+ type = SEC_ASN1_IA5_STRING;
+ else
+ type = SEC_ASN1_PRINTABLE_STRING;
+ }
+ }
+ length = strlen(buffer);
+ }
+ else if (CFGetTypeID(value) == CFDataGetTypeID()) {
+ /* will remain valid for the duration of the operation, still maybe copy into pool */
+ length = CFDataGetLength(value);
+ buffer = (char *)CFDataGetBytePtr(value);
+ }
+ size_t oid_length = 0;
+ uint8_t *oid_data = NULL;
+ if (CFGetTypeID(oid) == CFStringGetTypeID()) {
+ if (CFEqual(kSecOidCommonName, oid)) {
+ oid_length = oidCommonName.length; oid_data = oidCommonName.data;
+ } else if (CFEqual(kSecOidCountryName, oid)) {
+ oid_length = oidCountryName.length; oid_data = oidCountryName.data;
+ } else if (CFEqual(kSecOidStateProvinceName, oid)) {
+ oid_length = oidStateOrProvinceName.length; oid_data = oidStateOrProvinceName.data;
+ } else if (CFEqual(kSecOidLocalityName, oid)) {
+ oid_length = oidLocalityName.length; oid_data = oidLocalityName.data;
+ } else if (CFEqual(kSecOidOrganization, oid)) {
+ oid_length = oidOrganizationName.length; oid_data = oidOrganizationName.data;
+ } else if (CFEqual(kSecOidOrganizationalUnit, oid)) {
+ oid_length = oidOrganizationalUnitName.length; oid_data = oidOrganizationalUnitName.data;
+ } else {
+ CFDataRef oid_der_data = oid_der(oid);
+ require(oid_der_data, out);
+ oid_length = CFDataGetLength(oid_der_data);
+ oid_data = PORT_ArenaAlloc(poolp, oid_length);
+ require(oid_length && oid_data, out);
+ memcpy(oid_data, CFDataGetBytePtr(oid_der_data), oid_length);
+ CFReleaseSafe(oid_der_data);
+ }
+ } else if (CFGetTypeID(oid) == CFDataGetTypeID()) {
+ /* will remain valid for the duration of the operation, still maybe copy into pool */
+ oid_length = CFDataGetLength(oid);
+ oid_data = (uint8_t *)CFDataGetBytePtr(oid);
+ }
+ NSS_ATV stage_nss_atv = { { oid_length, oid_data },
+ { { length, (uint8_t*)buffer }, type } };
+ *nss_atv = stage_nss_atv;
+ return true;
+out:
+ return false;
+}
+
+static NSS_RDN **make_subject(PRArenaPool *poolp, CFArrayRef subject)
+{
+ if (!subject)
+ return NULL;
+ CFIndex rdn_ix, rdn_count = CFArrayGetCount(subject);
+ NSS_RDN **rdnps = PORT_ArenaZNewArray(poolp, NSS_RDN *, rdn_count + 1);
+ NSS_RDN *rdns = PORT_ArenaZNewArray(poolp, NSS_RDN, rdn_count);
+ for (rdn_ix = 0; rdn_ix < rdn_count; rdn_ix++) {
+ rdnps[rdn_ix] = &rdns[rdn_ix];
+ CFArrayRef rdn = CFArrayGetValueAtIndex(subject, rdn_ix);
+ CFIndex atv_ix, atv_count = CFArrayGetCount(rdn);
+ rdns[rdn_ix].atvs = PORT_ArenaZNewArray(poolp, NSS_ATV *, atv_count + 1);
+ NSS_ATV *atvs = PORT_ArenaZNewArray(poolp, NSS_ATV, atv_count);
+ for (atv_ix = 0; atv_ix < atv_count; atv_ix++) {
+ rdns[rdn_ix].atvs[atv_ix] = &atvs[atv_ix];
+ CFArrayRef atv = CFArrayGetValueAtIndex(rdn, atv_ix);
+ if ((CFArrayGetCount(atv) != 2)
+ || !make_nss_atv(poolp, CFArrayGetValueAtIndex(atv, 0),
+ CFArrayGetValueAtIndex(atv, 1), 0, &atvs[atv_ix]))
+ return NULL;
+ }
+ }
+ return rdnps;
+}
+
+struct make_general_names_context {
+ PRArenaPool *poolp;
+ SecAsn1Item *names;
+ uint32_t count;
+ uint32_t capacity;
+};
+
+static void make_general_names(const void *key, const void *value, void *context)
+{
+ struct make_general_names_context *gn = (struct make_general_names_context *)context;
+ require(value,out);
+ CFArrayRef gn_values = NULL;
+ CFStringRef gn_value = NULL;
+ CFIndex entry_ix, entry_count = 0;
+ if (CFGetTypeID(value) == CFArrayGetTypeID()) {
+ gn_values = (CFArrayRef)value;
+ entry_count = CFArrayGetCount(value);
+ } else if (CFGetTypeID(value) == CFStringGetTypeID()) {
+ gn_value = (CFStringRef)value;
+ entry_count = 1;
+ }
+
+ require(entry_count > 0, out);
+
+ require(key,out);
+ require(CFGetTypeID(key) == CFStringGetTypeID(), out);
+
+ if (!gn->names || (gn->count == gn->capacity)) {
+ uint32_t capacity = gn->capacity;
+ if (capacity)
+ capacity *= 2;
+ else
+ capacity = 10;
+
+ void * new_array = PORT_ArenaZNewArray(gn->poolp, SecAsn1Item, capacity);
+ if (gn->names)
+ memcpy(new_array, gn->names, gn->capacity);
+ gn->names = new_array;
+ gn->capacity = capacity;
+ }
+
+ NSS_GeneralName general_name_item = { { }, -1 };
+ if (kCFCompareEqualTo == CFStringCompare(CFSTR("dNSName"), key, kCFCompareCaseInsensitive))
+ general_name_item.tag = NGT_DNSName;
+ else if (kCFCompareEqualTo == CFStringCompare(CFSTR("rfc822Name"), key, kCFCompareCaseInsensitive))
+ general_name_item.tag = NGT_RFC822Name;
+ else if (kCFCompareEqualTo == CFStringCompare(CFSTR("uniformResourceIdentifier"), key, kCFCompareCaseInsensitive))
+ general_name_item.tag = NGT_URI;
+ else if (kCFCompareEqualTo == CFStringCompare(CFSTR("ntPrincipalName"), key, kCFCompareCaseInsensitive))
+ {
+ /*
+ NT Principal in SubjectAltName is defined in the context of Smartcards:
+
+ http://www.oid-info.com/get/1.3.6.1.4.1.311.20.2.3
+ http://support.microsoft.com/default.aspx?scid=kb;en-us;281245
+
+ Subject Alternative Name = Other Name: Principal Name= (UPN). For example:
+ UPN = user1@name.com
+ The UPN OtherName OID is : "1.3.6.1.4.1.311.20.2.3"
+ The UPN OtherName value: Must be ASN1-encoded UTF8 string
+ Subject = Distinguished name of user. This field is a mandatory extension, but the population of this field is optional.
+ */
+
+ /* OtherName ::= SEQUENCE {
+ type-id OBJECT IDENTIFIER,
+ value [0] EXPLICIT ANY DEFINED BY type-id
+ } */
+ uint8_t nt_principal_oid[] = { 0x2B, 0x06, 0x01, 0x04, 0x01, 0x82, 0x37, 0x14, 0x02, 0x03 };
+ typedef struct {
+ SecAsn1Oid typeId;
+ SecAsn1Item value;
+ } nt_principal_other_name;
+ nt_principal_other_name name = {};
+
+ const SecAsn1Template my_other_name_template[] = {
+ { SEC_ASN1_SEQUENCE,
+ 0, NULL, sizeof(nt_principal_other_name) },
+ { SEC_ASN1_OBJECT_ID,
+ offsetof(nt_principal_other_name,typeId), },
+ { SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_CONSTRUCTED | SEC_ASN1_EXPLICIT | 0, offsetof(nt_principal_other_name,value), kSecAsn1UTF8StringTemplate, },
+ { 0, }
+ };
+ const SecAsn1Template my_other_name_template_cons[] = {
+ { SEC_ASN1_CONTEXT_SPECIFIC | SEC_ASN1_CONSTRUCTED | NGT_OtherName,
+ 0, my_other_name_template, sizeof(nt_principal_other_name) }
+ };
+
+ size_t length = 0;
+ char *buffer = NULL;
+
+ require(gn_value, out);
+ require(CFGetTypeID(gn_value) == CFStringGetTypeID(), out);
+ length = CFStringGetMaximumSizeForEncoding(CFStringGetLength(value),
+ kCFStringEncodingUTF8);
+ buffer = PORT_ArenaAlloc(gn->poolp, length);
+ if (!CFStringGetCString(value, buffer, length, kCFStringEncodingUTF8))
+ goto out;
+
+ name.typeId.Length = sizeof(nt_principal_oid);
+ name.typeId.Data = nt_principal_oid;
+ name.value.Length = strlen(buffer);
+ name.value.Data = (uint8_t*)buffer;
+ SEC_ASN1EncodeItem(gn->poolp, &gn->names[gn->count], &name, my_other_name_template_cons);
+ gn->count++;
+
+ /* We already encoded the value for the general name */
+ goto out;
+ }
+ else
+ goto out;
+
+ if (gn_values) {
+ for (entry_ix = 0; entry_ix < entry_count; entry_ix++) {
+ CFTypeRef entry_value = CFArrayGetValueAtIndex(gn_values, entry_ix);
+ CFIndex buffer_size = CFStringGetMaximumSizeForEncoding(CFStringGetLength((CFStringRef)entry_value),
+ kCFStringEncodingUTF8); /* we only allow ASCII => only expect IA5Strings */
+ char *buffer = (char *)PORT_ArenaZNewArray(gn->poolp, uint8_t, buffer_size);
+ require(CFStringGetCString((CFStringRef)entry_value, buffer, buffer_size, kCFStringEncodingASCII), out);
+ general_name_item.item.Data = (uint8_t*)buffer;
+ general_name_item.item.Length = strlen(buffer);
+ SEC_ASN1EncodeItem(gn->poolp, &gn->names[gn->count], &general_name_item, kSecAsn1GeneralNameTemplate);
+ gn->count++;
+ }
+ } else if (gn_value) {
+ CFIndex buffer_size = CFStringGetMaximumSizeForEncoding(CFStringGetLength(gn_value),
+ kCFStringEncodingUTF8);
+ char *buffer = (char *)PORT_ArenaZNewArray(gn->poolp, uint8_t, buffer_size);
+ require(CFStringGetCString(gn_value, buffer, buffer_size, kCFStringEncodingASCII), out);
+ general_name_item.item.Data = (uint8_t*)buffer;
+ general_name_item.item.Length = strlen(buffer);
+ SEC_ASN1EncodeItem(gn->poolp, &gn->names[gn->count], &general_name_item, kSecAsn1GeneralNameTemplate);
+ gn->count++;
+ }
+out:
+ return;
+}
+
+static SecAsn1Item make_subjectAltName_extension(PRArenaPool *poolp, CFDictionaryRef subjectAltNames)
+{
+ SecAsn1Item subjectAltExt = {};
+
+ struct make_general_names_context context = { poolp, NULL, 0 };
+ CFDictionaryApplyFunction(subjectAltNames, make_general_names, &context);
+
+ // all general names in a sequence:
+ uint32_t ix;
+ SecAsn1Item **general_names = PORT_ArenaZNewArray(poolp, SecAsn1Item *, context.count + 1);
+ for (ix = 0; ix < context.count; ix++)
+ general_names[ix] = &context.names[ix];
+ NSS_GeneralNames gnames = { general_names };
+ SEC_ASN1EncodeItem(poolp, &subjectAltExt, &gnames, kSecAsn1GeneralNamesTemplate);
+
+ return subjectAltExt;
+}
+
+CFTypeRef kSecCSRChallengePassword = CFSTR("csrChallengePassword");
+CFTypeRef kSecSubjectAltName = CFSTR("subjectAltName");
+CFTypeRef kSecCertificateKeyUsage = CFSTR("keyUsage");
+CFTypeRef kSecCSRBasicContraintsPathLen = CFSTR("basicConstraints");
+
+static const uint8_t pkcs9ExtensionsRequested[] = { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 14 };
+static const uint8_t pkcs9ChallengePassword[] = { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 7 };
+
+static const uint8_t encoded_asn1_true = 0xFF;
+static const SecAsn1Item asn1_true =
+ { sizeof(encoded_asn1_true), (uint8_t*)&encoded_asn1_true };
+
+static inline uint32_t highest_bit(uint32_t n)
+{
+ return ((n) >> 16 ? ((n)>>=16, 16) : 0) + \
+ ((n) >> 8 ? ((n)>>=8, 8) : 0) + \
+ ((n) >> 4 ? ((n)>>=4, 4) : 0) + \
+ ((n) >> 2 ? ((n)>>=2, 2) : 0) + \
+ ((n) >> 1 ? ((n)>>=1, 1) : 0) + \
+ (n);
+}
+
+static
+NSS_CertExtension **
+extensions_from_parameters(PRArenaPool *poolp, CFDictionaryRef parameters)
+{
+ uint32_t num_extensions = 0, max_extensions = 4;
+ NSS_CertExtension **csr_extensions = PORT_ArenaZNewArray(poolp, NSS_CertExtension *, max_extensions);
+ NSS_CertExtension *csr_extension = PORT_ArenaZNewArray(poolp, NSS_CertExtension, max_extensions);
+
+ CFNumberRef basic_contraints_num = CFDictionaryGetValue(parameters, kSecCSRBasicContraintsPathLen);
+ if (basic_contraints_num) {
+ NSS_BasicConstraints basic_contraints = { asn1_true, {} };
+ uint8_t path_len;
+
+ int basic_contraints_path_len = 0;
+ require(CFNumberGetValue(basic_contraints_num, kCFNumberIntType, &basic_contraints_path_len), out);
+ if (basic_contraints_path_len >= 0 && basic_contraints_path_len < 256) {
+ path_len = basic_contraints_path_len;
+ basic_contraints.pathLenConstraint.Length = sizeof(path_len);
+ basic_contraints.pathLenConstraint.Data = &path_len;
+ }
+
+ csr_extension[num_extensions].extnId.Data = oidBasicConstraints.data;
+ csr_extension[num_extensions].extnId.Length = oidBasicConstraints.length;
+ csr_extension[num_extensions].critical = asn1_true;
+
+ SEC_ASN1EncodeItem(poolp, &csr_extension[num_extensions].value, &basic_contraints,
+ kSecAsn1BasicConstraintsTemplate);
+ require(num_extensions++ < max_extensions, out);
+ }
+
+ CFDictionaryRef subject_alternate_names = CFDictionaryGetValue(parameters, kSecSubjectAltName);
+ if (subject_alternate_names) {
+ require(CFGetTypeID(subject_alternate_names) == CFDictionaryGetTypeID(), out);
+ csr_extension[num_extensions].value = make_subjectAltName_extension(poolp, subject_alternate_names);
+ /* set up subjectAltName cert request value */
+ csr_extension[num_extensions].extnId.Length = oidSubjectAltName.length;
+ csr_extension[num_extensions].extnId.Data = oidSubjectAltName.data;
+ require(num_extensions++ < max_extensions, out);
+ }
+
+ CFNumberRef key_usage_requested = CFDictionaryGetValue(parameters, kSecCertificateKeyUsage);
+ SecAsn1Item key_usage_asn1_value = { 0 };
+ if (key_usage_requested) {
+ int key_usage_value;
+ require(CFNumberGetValue(key_usage_requested, kCFNumberIntType, &key_usage_value), out);
+ if (key_usage_value > 0) {
+ uint32_t key_usage_value_be = 0, key_usage_mask = 1<<31;
+ uint32_t key_usage_value_max_bitlen = 9, key_usage_value_bitlen = 0;
+ while(key_usage_value_max_bitlen) {
+ if (key_usage_value & 1) {
+ key_usage_value_be |= key_usage_mask;
+ key_usage_value_bitlen = 10 - key_usage_value_max_bitlen;
+ }
+ key_usage_value >>= 1;
+ key_usage_value_max_bitlen--;
+ key_usage_mask >>= 1;
+ }
+
+ SecAsn1Item key_usage_input = { key_usage_value_bitlen,
+ ((uint8_t*)&key_usage_value_be) + 3 - (key_usage_value_bitlen >> 3) };
+ SEC_ASN1EncodeItem(poolp, &key_usage_asn1_value, &key_usage_input, kSecAsn1BitStringTemplate);
+
+ csr_extension[num_extensions].extnId.Data = oidKeyUsage.data;
+ csr_extension[num_extensions].extnId.Length = oidKeyUsage.length;
+ csr_extension[num_extensions].critical = asn1_true;
+ csr_extension[num_extensions].value = key_usage_asn1_value;
+ require(num_extensions++ < max_extensions, out);
+ }
+ }
+
+ /* extensions requested (subjectAltName, keyUsage) sequence of extension sequences */
+ uint32_t ix = 0;
+ for (ix = 0; ix <= num_extensions; ix++)
+ csr_extensions[ix] = csr_extension[ix].extnId.Length ? &csr_extension[ix] : NULL;
+
+out:
+ return csr_extensions;
+}
+
+static
+NSS_Attribute **nss_attributes_from_parameters_dict(PRArenaPool *poolp, CFDictionaryRef parameters)
+{
+ /* A challenge-password attribute must have a single attribute value.
+
+ ChallengePassword attribute values generated in accordance with this
+ version of this document SHOULD use the PrintableString encoding
+ whenever possible. If internationalization issues make this
+ impossible, the UTF8String alternative SHOULD be used. PKCS #9-
+ attribute processing systems MUST be able to recognize and process
+ all string types in DirectoryString values.
+
+ Upperbound of 255 defined for all PKCS#9 attributes.
+
+ pkcs-9 OBJECT IDENTIFIER ::= {iso(1) member-body(2) us(840)
+ rsadsi(113549) pkcs(1) 9}
+ pkcs-9-at-challengePassword OBJECT IDENTIFIER ::= {pkcs-9 7}
+
+ */
+ if (!parameters)
+ return NULL;
+ uint32_t num_attrs = 0;
+
+ CFStringRef challenge = CFDictionaryGetValue(parameters, kSecCSRChallengePassword);
+ NSS_Attribute challenge_password_attr = {};
+ if (challenge) {
+ CFIndex buffer_size = CFStringGetMaximumSizeForEncoding(CFStringGetLength(challenge),
+ kCFStringEncodingUTF8); /* we only allow UTF8 or ASCII */
+ char *buffer = (char *)PORT_ArenaZNewArray(poolp, uint8_t, buffer_size);
+ bool utf8 = false;
+ if (!CFStringGetCString(challenge, buffer, buffer_size, kCFStringEncodingASCII)) {
+ if (!CFStringGetCString(challenge, buffer, buffer_size, kCFStringEncodingUTF8))
+ return NULL;
+ utf8 = true;
+ } else
+ if (!printable_string(challenge))
+ utf8 = true;
+
+ SecAsn1Item *challenge_password_value = PORT_ArenaZNewArray(poolp, SecAsn1Item, 1);
+ SecAsn1Item challenge_password_raw = { strlen(buffer), (uint8_t*)buffer };
+ SEC_ASN1EncodeItem(poolp, challenge_password_value, &challenge_password_raw,
+ utf8 ? kSecAsn1UTF8StringTemplate : kSecAsn1PrintableStringTemplate);
+ SecAsn1Item **challenge_password_values = PORT_ArenaZNewArray(poolp, SecAsn1Item *, 2);
+ challenge_password_values[0] = challenge_password_value;
+ challenge_password_attr.attrType.Length = sizeof(pkcs9ChallengePassword);
+ challenge_password_attr.attrType.Data = (uint8_t*)&pkcs9ChallengePassword;
+ challenge_password_attr.attrValue = challenge_password_values;
+ num_attrs++;
+ }
+
+ NSS_CertExtension **extensions = extensions_from_parameters(poolp, parameters);
+ NSS_Attribute extensions_requested_attr = {};
+ if (extensions) {
+ SecAsn1Item *extensions_requested_value = PORT_ArenaZNewArray(poolp, SecAsn1Item, 1);
+ SEC_ASN1EncodeItem(poolp, extensions_requested_value, &extensions, kSecAsn1SequenceOfCertExtensionTemplate);
+ SecAsn1Item **extensions_requested_values = PORT_ArenaZNewArray(poolp, SecAsn1Item *, 2);
+ extensions_requested_values[0] = extensions_requested_value;
+ extensions_requested_values[1] = NULL;
+ extensions_requested_attr.attrType.Length = sizeof(pkcs9ExtensionsRequested);
+ extensions_requested_attr.attrType.Data = (uint8_t*)pkcs9ExtensionsRequested;
+ extensions_requested_attr.attrValue = extensions_requested_values;
+ num_attrs++;
+ }
+
+ NSS_Attribute **attributes_ptr = PORT_ArenaZNewArray(poolp, NSS_Attribute *, num_attrs + 1);
+ NSS_Attribute *attributes = PORT_ArenaZNewArray(poolp, NSS_Attribute, num_attrs);
+ if (challenge_password_attr.attrType.Length) {
+ --num_attrs;
+ attributes[num_attrs] = challenge_password_attr;
+ attributes_ptr[num_attrs] = &attributes[num_attrs];
+ }
+ if (extensions_requested_attr.attrType.Length) {
+ --num_attrs;
+ attributes[num_attrs] = extensions_requested_attr;
+ attributes_ptr[num_attrs] = &attributes[num_attrs];
+ }
+ return attributes_ptr;
+#if 0
+out:
+ return NULL;
+#endif
+}
+
+static const uint8_t encoded_null[2] = { SEC_ASN1_NULL, 0 };
+static const SecAsn1Item asn1_null = { sizeof(encoded_null), (uint8_t*)encoded_null };
+
+CFDataRef SecGenerateCertificateRequestWithParameters(SecRDN *subject,
+ CFDictionaryRef parameters, SecKeyRef publicKey, SecKeyRef privateKey)
+{
+ CFDataRef csr = NULL;
+ PRArenaPool *poolp = PORT_NewArena(1024);
+ CFDictionaryRef pubkey_attrs = NULL;
+
+ if (!poolp)
+ return NULL;
+
+ NSSCertRequest certReq;
+ memset(&certReq, 0, sizeof(certReq));
+
+ /* version */
+ unsigned char version = 0;
+ certReq.reqInfo.version.Length = sizeof(version);
+ certReq.reqInfo.version.Data = &version;
+
+ /* subject */
+ unsigned atv_num = 0, num = 0;
+ SecRDN *one_rdn;
+ SecATV *one_atv;
+ for (one_rdn = subject; *one_rdn; one_rdn++) {
+ for (one_atv = *one_rdn; one_atv->oid; one_atv++)
+ atv_num++;
+ atv_num++; /* one more */
+ num++;
+ }
+ NSS_ATV atvs[atv_num];
+ NSS_ATV *atvps[atv_num];
+ NSS_RDN rdns[num];
+ NSS_RDN *rdnps[num+1];
+ atv_num = 0;
+ unsigned rdn_num = 0;
+ for (one_rdn = subject; *one_rdn; one_rdn++) {
+ rdns[rdn_num].atvs = &atvps[atv_num];
+ rdnps[rdn_num] = &rdns[rdn_num];
+ rdn_num++;
+ for (one_atv = *one_rdn; one_atv->oid; one_atv++) {
+ if (!make_nss_atv(poolp, one_atv->oid, one_atv->value,
+ one_atv->type, &atvs[atv_num]))
+ return NULL;
+ atvps[atv_num] = &atvs[atv_num];
+ atv_num++;
+ }
+ atvps[atv_num++] = NULL;
+ }
+ rdnps[rdn_num] = NULL;
+ certReq.reqInfo.subject.rdns = rdnps;
+
+ /* public key info */
+ certReq.reqInfo.subjectPublicKeyInfo.algorithm.algorithm.Length = oidRsa.length;
+ certReq.reqInfo.subjectPublicKeyInfo.algorithm.algorithm.Data = oidRsa.data;
+ certReq.reqInfo.subjectPublicKeyInfo.algorithm.parameters = asn1_null;
+
+ pubkey_attrs = SecKeyCopyAttributeDictionary(publicKey);
+ CFDataRef pkcs1_pubkey = (CFDataRef)CFDictionaryGetValue(pubkey_attrs, kSecValueData);
+ uint8_t signature[8 * CFDataGetLength(pkcs1_pubkey)];
+ size_t signature_length = sizeof(signature);
+
+ certReq.reqInfo.subjectPublicKeyInfo.subjectPublicKey.Length = 8 * CFDataGetLength(pkcs1_pubkey);
+ certReq.reqInfo.subjectPublicKeyInfo.subjectPublicKey.Data = (uint8_t*)CFDataGetBytePtr(pkcs1_pubkey);
+
+ certReq.reqInfo.attributes = nss_attributes_from_parameters_dict(poolp, parameters);
+ SecCmsArraySortByDER((void **)certReq.reqInfo.attributes, kSecAsn1AttributeTemplate, NULL);
+
+ /* encode request info by itself to calculate signature */
+ SecAsn1Item reqinfo = {};
+ SEC_ASN1EncodeItem(poolp, &reqinfo, &certReq.reqInfo, kSecAsn1CertRequestInfoTemplate);
+ require(reqinfo.Length<=UINT32_MAX, out);
+
+ /* calculate signature */
+ uint8_t reqinfo_hash[CC_SHA1_DIGEST_LENGTH];
+ CC_SHA1(reqinfo.Data, (CC_LONG)reqinfo.Length, reqinfo_hash);
+ require_noerr_quiet(SecKeyRawSign(privateKey, kSecPaddingPKCS1SHA1,
+ reqinfo_hash, sizeof(reqinfo_hash), signature, &signature_length), out);
+
+ /* signature and info */
+ certReq.signatureAlgorithm.algorithm.Length = oidSha1Rsa.length;
+ certReq.signatureAlgorithm.algorithm.Data = oidSha1Rsa.data;
+ certReq.signatureAlgorithm.parameters = asn1_null;
+ certReq.signature.Data = signature;
+ certReq.signature.Length = signature_length * 8;
+
+ /* encode csr */
+ SecAsn1Item cert_request = {};
+ require_quiet(SEC_ASN1EncodeItem(poolp, &cert_request, &certReq,
+ kSecAsn1CertRequestTemplate), out);
+ csr = CFDataCreate(kCFAllocatorDefault, cert_request.Data, cert_request.Length);
+
+out:
+ if (poolp)
+ PORT_FreeArena(poolp, PR_TRUE);
+ CFReleaseSafe(pubkey_attrs);
+ return csr;
+}
+
+CFDataRef SecGenerateCertificateRequest(CFArrayRef subject,
+ CFDictionaryRef parameters, SecKeyRef publicKey, SecKeyRef privateKey)
+{
+ CFDataRef csr = NULL;
+ PRArenaPool *poolp = PORT_NewArena(1024);
+ CFDictionaryRef pubkey_attrs = NULL;
+
+ if (!poolp)
+ return NULL;
+
+ NSSCertRequest certReq;
+ memset(&certReq, 0, sizeof(certReq));
+
+ /* version */
+ unsigned char version = 0;
+ certReq.reqInfo.version.Length = sizeof(version);
+ certReq.reqInfo.version.Data = &version;
+
+ /* subject */
+ certReq.reqInfo.subject.rdns = make_subject(poolp, (CFArrayRef)subject);
+
+ /* public key info */
+ certReq.reqInfo.subjectPublicKeyInfo.algorithm.algorithm.Length = oidRsa.length;
+ certReq.reqInfo.subjectPublicKeyInfo.algorithm.algorithm.Data = oidRsa.data;
+ certReq.reqInfo.subjectPublicKeyInfo.algorithm.parameters = asn1_null;
+
+ pubkey_attrs = SecKeyCopyAttributeDictionary(publicKey);
+ CFDataRef pkcs1_pubkey = (CFDataRef)CFDictionaryGetValue(pubkey_attrs, kSecValueData);
+ uint8_t signature[8 * CFDataGetLength(pkcs1_pubkey)];
+ size_t signature_length = sizeof(signature);
+
+ certReq.reqInfo.subjectPublicKeyInfo.subjectPublicKey.Length = 8 * CFDataGetLength(pkcs1_pubkey);
+ certReq.reqInfo.subjectPublicKeyInfo.subjectPublicKey.Data = (uint8_t*)CFDataGetBytePtr(pkcs1_pubkey);
+
+ certReq.reqInfo.attributes = nss_attributes_from_parameters_dict(poolp, parameters);
+ SecCmsArraySortByDER((void **)certReq.reqInfo.attributes, kSecAsn1AttributeTemplate, NULL);
+
+ /* encode request info by itself to calculate signature */
+ SecAsn1Item reqinfo = {};
+ SEC_ASN1EncodeItem(poolp, &reqinfo, &certReq.reqInfo, kSecAsn1CertRequestInfoTemplate);
+ require(reqinfo.Length<=UINT32_MAX, out);
+
+ /* calculate signature */
+ uint8_t reqinfo_hash[CC_SHA1_DIGEST_LENGTH];
+ CC_SHA1(reqinfo.Data, reqinfo.Length, reqinfo_hash);
+ require_noerr_quiet(SecKeyRawSign(privateKey, kSecPaddingPKCS1SHA1,
+ reqinfo_hash, sizeof(reqinfo_hash), signature, &signature_length), out);
+
+ /* signature and info */
+ certReq.signatureAlgorithm.algorithm.Length = oidSha1Rsa.length;
+ certReq.signatureAlgorithm.algorithm.Data = oidSha1Rsa.data;
+ certReq.signatureAlgorithm.parameters = asn1_null;
+ certReq.signature.Data = signature;
+ certReq.signature.Length = signature_length * 8;
+
+ /* encode csr */
+ SecAsn1Item cert_request = {};
+ require_quiet(SEC_ASN1EncodeItem(poolp, &cert_request, &certReq,
+ kSecAsn1CertRequestTemplate), out);
+ csr = CFDataCreate(kCFAllocatorDefault, cert_request.Data, cert_request.Length);
+
+out:
+ if (poolp)
+ PORT_FreeArena(poolp, PR_TRUE);
+ CFReleaseSafe(pubkey_attrs);
+ return csr;
+}
+
+bool SecVerifyCertificateRequest(CFDataRef csr, SecKeyRef *publicKey,
+ CFStringRef *challenge, CFDataRef *subject, CFDataRef *extensions)
+{
+ PRArenaPool *poolp = PORT_NewArena(1024);
+ SecKeyRef candidatePublicKey = NULL;
+ bool valid = false;
+ NSSCertRequest certReq;
+ memset(&certReq, 0, sizeof(certReq));
+ SecAsn1Item csr_item = { CFDataGetLength(csr), (uint8_t*)CFDataGetBytePtr(csr) };
+ require_noerr_quiet(SEC_ASN1DecodeItem(poolp, &certReq, kSecAsn1CertRequestTemplate,
+ &csr_item), out);
+
+ /* signature and info */
+ require(certReq.signatureAlgorithm.algorithm.Length == oidSha1Rsa.length, out);
+ require_noerr(memcmp(oidSha1Rsa.data, certReq.signatureAlgorithm.algorithm.Data,
+ oidSha1Rsa.length), out);
+ require(certReq.signatureAlgorithm.parameters.Length == asn1_null.Length, out);
+ require_noerr(memcmp(asn1_null.Data, certReq.signatureAlgorithm.parameters.Data,
+ asn1_null.Length), out);
+
+ /* encode request info by itself to calculate signature */
+ SecAsn1Item reqinfo = {};
+ SEC_ASN1EncodeItem(poolp, &reqinfo, &certReq.reqInfo, kSecAsn1CertRequestInfoTemplate);
+
+ /* calculate signature */
+ uint8_t reqinfo_hash[CC_SHA1_DIGEST_LENGTH];
+ require(reqinfo.Length<=UINT32_MAX, out);
+ CC_SHA1(reqinfo.Data, (CC_LONG)reqinfo.Length, reqinfo_hash);
+
+ /* @@@ check for version 0 */
+
+ require(candidatePublicKey = SecKeyCreateRSAPublicKey(kCFAllocatorDefault,
+ certReq.reqInfo.subjectPublicKeyInfo.subjectPublicKey.Data,
+ certReq.reqInfo.subjectPublicKeyInfo.subjectPublicKey.Length / 8,
+ kSecKeyEncodingPkcs1), out);
+
+ require_noerr_quiet(SecKeyRawVerify(candidatePublicKey, kSecPaddingPKCS1SHA1,
+ reqinfo_hash, sizeof(reqinfo_hash),
+ certReq.signature.Data, certReq.signature.Length / 8), out);
+
+ SecAsn1Item subject_item = { 0 }, extensions_item = { 0 }, challenge_item = { 0 };
+ require_quiet(SEC_ASN1EncodeItem(poolp, &subject_item,
+ &certReq.reqInfo.subject, kSecAsn1NameTemplate), out);
+
+ if (*certReq.reqInfo.attributes) {
+ uint32_t ix;
+ for (ix = 0; certReq.reqInfo.attributes[ix]; ix++) {
+ NSS_Attribute *attr = certReq.reqInfo.attributes[ix];
+ if ( (sizeof(pkcs9ChallengePassword) == attr->attrType.Length) &&
+ !memcmp(pkcs9ChallengePassword, attr->attrType.Data, sizeof(pkcs9ChallengePassword)))
+ challenge_item = *attr->attrValue[0];
+ else if ( (sizeof(pkcs9ExtensionsRequested) == attr->attrType.Length) &&
+ !memcmp(pkcs9ExtensionsRequested, attr->attrType.Data, sizeof(pkcs9ExtensionsRequested)))
+ extensions_item = *attr->attrValue[0];
+ }
+ }
+
+ if (subject && subject_item.Length)
+ *subject = CFDataCreate(kCFAllocatorDefault, subject_item.Data, subject_item.Length);
+ if (extensions && extensions_item.Length)
+ *extensions = CFDataCreate(kCFAllocatorDefault, extensions_item.Data, extensions_item.Length);
+ if (challenge && challenge_item.Length) {
+ SecAsn1Item string = { 0 };
+ SECStatus rv = SEC_ASN1DecodeItem(poolp, &string, kSecAsn1UTF8StringTemplate, &challenge_item);
+ if (rv)
+ rv = SEC_ASN1DecodeItem(poolp, &string, kSecAsn1PrintableStringTemplate, &challenge_item);
+ if (!rv)
+ *challenge = CFStringCreateWithBytes(kCFAllocatorDefault, string.Data, string.Length, kCFStringEncodingUTF8, false);
+ else
+ *challenge = NULL;
+ }
+ if (publicKey) {
+ *publicKey = candidatePublicKey;
+ candidatePublicKey = NULL;
+ }
+ valid = true;
+out:
+ CFReleaseSafe(candidatePublicKey);
+ if (poolp)
+ PORT_FreeArena(poolp, PR_TRUE);
+ return valid;
+}
+
+#define HIDIGIT(v) (((v) / 10) + '0')
+#define LODIGIT(v) (((v) % 10) + '0')
+
+static OSStatus
+DER_CFDateToUTCTime(PRArenaPool *poolp, CFAbsoluteTime date, SecAsn1Item * utcTime)
+{
+ CFGregorianDate gdate = CFAbsoluteTimeGetGregorianDate(date, NULL /* GMT */);
+ unsigned char *d;
+ SInt8 second;
+
+ utcTime->Length = 13;
+ utcTime->Data = d = PORT_ArenaAlloc(poolp, 13);
+ if (!utcTime->Data)
+ return SECFailure;
+
+ /* UTC time does not handle the years before 1950 */
+ if (gdate.year < 1950)
+ return SECFailure;
+
+ /* remove the century since it's added to the year by the
+ CFAbsoluteTimeGetGregorianDate routine, but is not needed for UTC time */
+ gdate.year %= 100;
+ second = gdate.second;
+
+ d[0] = HIDIGIT(gdate.year);
+ d[1] = LODIGIT(gdate.year);
+ d[2] = HIDIGIT(gdate.month);
+ d[3] = LODIGIT(gdate.month);
+ d[4] = HIDIGIT(gdate.day);
+ d[5] = LODIGIT(gdate.day);
+ d[6] = HIDIGIT(gdate.hour);
+ d[7] = LODIGIT(gdate.hour);
+ d[8] = HIDIGIT(gdate.minute);
+ d[9] = LODIGIT(gdate.minute);
+ d[10] = HIDIGIT(second);
+ d[11] = LODIGIT(second);
+ d[12] = 'Z';
+ return SECSuccess;
+}
+
+SecCertificateRef
+SecGenerateSelfSignedCertificate(CFArrayRef subject, CFDictionaryRef parameters,
+ SecKeyRef publicKey, SecKeyRef privateKey)
+{
+ SecCertificateRef cert = NULL;
+ PRArenaPool *poolp = PORT_NewArena(1024);
+ CFDictionaryRef pubkey_attrs = NULL;
+ if (!poolp)
+ return NULL;
+
+ NSS_Certificate cert_tmpl;
+ memset(&cert_tmpl, 0, sizeof(cert_tmpl));
+
+ /* version */
+ unsigned char version = 2;
+ cert_tmpl.tbs.version.Length = sizeof(version);
+ cert_tmpl.tbs.version.Data = &version;
+
+ /* serialno */
+ unsigned char serialNumber = 1;
+ cert_tmpl.tbs.serialNumber.Length = sizeof(serialNumber);
+ cert_tmpl.tbs.serialNumber.Data = &serialNumber;
+
+ /* subject/issuer */
+ cert_tmpl.tbs.issuer.rdns = make_subject(poolp, (CFArrayRef)subject);
+ cert_tmpl.tbs.subject.rdns = cert_tmpl.tbs.issuer.rdns;
+
+ DER_CFDateToUTCTime(poolp, CFAbsoluteTimeGetCurrent(), &cert_tmpl.tbs.validity.notBefore.item);
+ cert_tmpl.tbs.validity.notBefore.tag = SEC_ASN1_UTC_TIME;
+ DER_CFDateToUTCTime(poolp, CFAbsoluteTimeGetCurrent() + 3600*24*365, &cert_tmpl.tbs.validity.notAfter.item);
+ cert_tmpl.tbs.validity.notAfter.tag = SEC_ASN1_UTC_TIME;
+
+ /* extensions */
+ cert_tmpl.tbs.extensions = extensions_from_parameters(poolp, parameters);
+
+ /* @@@ we only handle rsa keys */
+ pubkey_attrs = SecKeyCopyAttributeDictionary(publicKey);
+ CFTypeRef key_type = CFDictionaryGetValue(pubkey_attrs, kSecAttrKeyType);
+ if (key_type && CFEqual(key_type, kSecAttrKeyTypeRSA)) {
+ /* public key data and algorithm */
+ cert_tmpl.tbs.subjectPublicKeyInfo.algorithm.algorithm = CSSMOID_RSA;
+ cert_tmpl.tbs.subjectPublicKeyInfo.algorithm.parameters = asn1_null;
+
+ CFDataRef pkcs1_pubkey = (CFDataRef)CFDictionaryGetValue(pubkey_attrs, kSecValueData);
+ cert_tmpl.tbs.subjectPublicKeyInfo.subjectPublicKey.Length = 8 * CFDataGetLength(pkcs1_pubkey);
+ cert_tmpl.tbs.subjectPublicKeyInfo.subjectPublicKey.Data = (uint8_t*)CFDataGetBytePtr(pkcs1_pubkey);
+
+ /* signature algorithm */
+ cert_tmpl.tbs.signature.algorithm = CSSMOID_SHA1WithRSA;
+ cert_tmpl.tbs.signature.parameters = asn1_null;
+ cert_tmpl.signatureAlgorithm.algorithm = CSSMOID_SHA1WithRSA;
+ cert_tmpl.signatureAlgorithm.parameters = asn1_null;
+
+ /* encode request info by itself to calculate signature */
+ SecAsn1Item tbscert = {};
+ SEC_ASN1EncodeItem(poolp, &tbscert, &cert_tmpl.tbs, kSecAsn1TBSCertificateTemplate);
+
+ /* calculate signature */
+ uint8_t tbscert_hash[CC_SHA1_DIGEST_LENGTH];
+ require(tbscert.Length<=UINT32_MAX, out);
+ CC_SHA1(tbscert.Data, tbscert.Length, tbscert_hash);
+ uint8_t signature[8 * CFDataGetLength(pkcs1_pubkey)];
+ size_t signature_length = sizeof(signature);
+ require_noerr_quiet(SecKeyRawSign(privateKey, kSecPaddingPKCS1SHA1,
+ tbscert_hash, sizeof(tbscert_hash), signature, &signature_length), out);
+
+ /* signature */
+ cert_tmpl.signature.Data = signature;
+ cert_tmpl.signature.Length = signature_length * 8;
+
+ /* encode cert */
+ SecAsn1Item signed_cert = {};
+ require_quiet(SEC_ASN1EncodeItem(poolp, &signed_cert, &cert_tmpl,
+ kSecAsn1SignedCertTemplate), out);
+ cert = SecCertificateCreateWithBytes(kCFAllocatorDefault,
+ signed_cert.Data, signed_cert.Length);
+ }
+out:
+ if (poolp)
+ PORT_FreeArena(poolp, PR_TRUE);
+ CFReleaseSafe(pubkey_attrs);
+ return cert;
+}
+
+
+SecCertificateRef
+SecIdentitySignCertificate(SecIdentityRef issuer, CFDataRef serialno,
+ SecKeyRef publicKey, CFTypeRef subject, CFTypeRef extensions)
+{
+ SecCertificateRef cert = NULL;
+ SecKeyRef privateKey = NULL;
+
+ PRArenaPool *poolp = PORT_NewArena(1024);
+ CFDictionaryRef pubkey_attrs = NULL;
+ if (!poolp)
+ return NULL;
+
+ NSS_Certificate cert_tmpl;
+ memset(&cert_tmpl, 0, sizeof(cert_tmpl));
+
+ /* version */
+ unsigned char version = 2;
+ cert_tmpl.tbs.version.Length = sizeof(version);
+ cert_tmpl.tbs.version.Data = &version;
+
+ /* serialno */
+ cert_tmpl.tbs.serialNumber.Length = CFDataGetLength(serialno);
+ cert_tmpl.tbs.serialNumber.Data = (uint8_t*)CFDataGetBytePtr(serialno);
+
+ /* subject/issuer */
+ if (CFArrayGetTypeID() == CFGetTypeID(subject))
+ cert_tmpl.tbs.subject.rdns = make_subject(poolp, (CFArrayRef)subject);
+ else if (CFDataGetTypeID() == CFGetTypeID(subject)) {
+ SecAsn1Item subject_item = { CFDataGetLength(subject), (uint8_t*)CFDataGetBytePtr(subject) };
+ require_noerr_quiet(SEC_ASN1DecodeItem(poolp, &cert_tmpl.tbs.subject.rdns, kSecAsn1NameTemplate, &subject_item), out);
+ } else
+ goto out;
+
+ SecCertificateRef issuer_cert = NULL;
+ require_noerr(SecIdentityCopyCertificate(issuer, &issuer_cert), out);
+ CFDataRef issuer_name = SecCertificateCopyIssuerSequence(issuer_cert);
+ SecAsn1Item issuer_item = { CFDataGetLength(issuer_name), (uint8_t*)CFDataGetBytePtr(issuer_name) };
+ require_noerr_action_quiet(SEC_ASN1DecodeItem(poolp, &cert_tmpl.tbs.issuer.rdns,
+ kSecAsn1NameTemplate, &issuer_item), out, CFReleaseNull(issuer_name));
+ CFReleaseNull(issuer_name);
+
+ DER_CFDateToUTCTime(poolp, CFAbsoluteTimeGetCurrent(), &cert_tmpl.tbs.validity.notBefore.item);
+ cert_tmpl.tbs.validity.notBefore.tag = SEC_ASN1_UTC_TIME;
+ DER_CFDateToUTCTime(poolp, CFAbsoluteTimeGetCurrent() + 3600*24*365, &cert_tmpl.tbs.validity.notAfter.item);
+ cert_tmpl.tbs.validity.notAfter.tag = SEC_ASN1_UTC_TIME;
+
+ /* extensions */
+ if (extensions) {
+ if (CFDataGetTypeID() == CFGetTypeID(extensions)) {
+ SecAsn1Item requested_extensions = { CFDataGetLength(extensions), (uint8_t*)CFDataGetBytePtr(extensions) };
+ //NSS_CertExtension **requested_extensions_decoded;
+ require_noerr_quiet(SEC_ASN1DecodeItem(poolp, &cert_tmpl.tbs.extensions,
+ kSecAsn1SequenceOfCertExtensionTemplate, &requested_extensions), out);
+ } else if (CFDictionaryGetTypeID() == CFGetTypeID(extensions)) {
+ cert_tmpl.tbs.extensions = extensions_from_parameters(poolp, extensions);
+ }
+ }
+
+ /* @@@ we only handle rsa keys */
+ pubkey_attrs = SecKeyCopyAttributeDictionary(publicKey);
+ CFTypeRef key_type = CFDictionaryGetValue(pubkey_attrs, kSecAttrKeyType);
+ if (key_type && CFEqual(key_type, kSecAttrKeyTypeRSA)) {
+ /* public key data and algorithm */
+ cert_tmpl.tbs.subjectPublicKeyInfo.algorithm.algorithm = CSSMOID_RSA;
+ cert_tmpl.tbs.subjectPublicKeyInfo.algorithm.parameters = asn1_null;
+
+ CFDataRef pkcs1_pubkey = (CFDataRef)CFDictionaryGetValue(pubkey_attrs, kSecValueData);
+ cert_tmpl.tbs.subjectPublicKeyInfo.subjectPublicKey.Length = 8 * CFDataGetLength(pkcs1_pubkey);
+ cert_tmpl.tbs.subjectPublicKeyInfo.subjectPublicKey.Data = (uint8_t*)CFDataGetBytePtr(pkcs1_pubkey);
+
+ /* signature algorithm */
+ cert_tmpl.tbs.signature.algorithm = CSSMOID_SHA1WithRSA;
+ cert_tmpl.tbs.signature.parameters = asn1_null;
+ cert_tmpl.signatureAlgorithm.algorithm = CSSMOID_SHA1WithRSA;
+ cert_tmpl.signatureAlgorithm.parameters = asn1_null;
+
+ /* encode request info by itself to calculate signature */
+ SecAsn1Item tbscert = {};
+ SEC_ASN1EncodeItem(poolp, &tbscert, &cert_tmpl.tbs, kSecAsn1TBSCertificateTemplate);
+
+ /* calculate signature */
+ uint8_t tbscert_hash[CC_SHA1_DIGEST_LENGTH];
+ require(tbscert.Length<=UINT32_MAX, out);
+ CC_SHA1(tbscert.Data, tbscert.Length, tbscert_hash);
+ uint8_t signature[8 * CFDataGetLength(pkcs1_pubkey)];
+ size_t signature_length = sizeof(signature);
+
+ require_noerr_quiet(SecIdentityCopyPrivateKey(issuer, &privateKey), out);
+ require_noerr_quiet(SecKeyRawSign(privateKey, kSecPaddingPKCS1SHA1,
+ tbscert_hash, sizeof(tbscert_hash), signature, &signature_length), out);
+
+ /* signature */
+ cert_tmpl.signature.Data = signature;
+ cert_tmpl.signature.Length = signature_length * 8;
+
+ /* encode cert */
+ SecAsn1Item signed_cert = {};
+ require_quiet(SEC_ASN1EncodeItem(poolp, &signed_cert, &cert_tmpl,
+ kSecAsn1SignedCertTemplate), out);
+ cert = SecCertificateCreateWithBytes(kCFAllocatorDefault,
+ signed_cert.Data, signed_cert.Length);
+ }
+out:
+ CFReleaseSafe(privateKey);
+ if (poolp)
+ PORT_FreeArena(poolp, PR_TRUE);
+ CFReleaseSafe(pubkey_attrs);
+ return cert;
+}
+
+CFDataRef
+SecGenerateCertificateRequestSubject(SecCertificateRef ca_certificate, CFArrayRef subject)
+{
+ CFMutableDataRef sequence = NULL;
+ PRArenaPool *poolp = PORT_NewArena(1024);
+ if (!poolp)
+ return NULL;
+
+ /*
+ Going agains the spec here:
+
+ 3.2.3. GetCertInitial
+
+ The messageData for this type consists of a DER-encoded
+ IssuerAndSubject (Section 3.2.3.1). The issuer is set to the
+ issuerName from the certification authority from which we are issued
+ certificates. The Subject is set to the SubjectName we used when
+ requesting the certificate.
+
+ That clearly says use the issuer of the cert issuing certificate. Since
+ it is combined with the subject of the to-be-issued certificate, that
+ seems a mistake. If we take the subject of the issuer and the subject
+ of the certificate we're interested in, we get the issuer and subject
+ the certificate to be returned will have.
+
+ */
+ CFDataRef issuer_sequence = SecCertificateCopySubjectSequence(ca_certificate);
+ SecAsn1Item subject_item = { 0 };
+ SecAsn1Item issuer_item = { CFDataGetLength(issuer_sequence), (uint8_t*)CFDataGetBytePtr(issuer_sequence) };
+ NSS_Name nss_subject = { make_subject(poolp, subject) };
+ require_quiet(SEC_ASN1EncodeItem(poolp, &subject_item, &nss_subject, kSecAsn1NameTemplate), out);
+
+ DERSize sequence_length = DERLengthOfLength(subject_item.Length + issuer_item.Length);
+ DERSize seq_len_length = subject_item.Length + issuer_item.Length + 1 /* SEQUENCE */ +
+ sequence_length;
+ sequence = CFDataCreateMutable(kCFAllocatorDefault, 0);
+ CFDataSetLength(sequence, seq_len_length);
+ uint8_t *sequence_ptr = CFDataGetMutableBytePtr(sequence);
+ *sequence_ptr++ = 0x30; //ASN1_CONSTR_SEQUENCE;
+ require_noerr_quiet(DEREncodeLength(subject_item.Length + issuer_item.Length, sequence_ptr, &sequence_length), out);
+ sequence_ptr += sequence_length;
+ memcpy(sequence_ptr, issuer_item.Data, issuer_item.Length);
+ memcpy(sequence_ptr + issuer_item.Length, subject_item.Data, subject_item.Length);
+
+out:
+ CFReleaseSafe(issuer_sequence);
+ if (poolp)
+ PORT_FreeArena(poolp, PR_TRUE);
+ return sequence;
+}