1/*
2 * Copyright (c) 2003-2004,2006,2008,2012,2014 Apple Inc. All Rights Reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 *
23 * key_create.c
24 */
25
26#include "key_create.h"
27
28#include "keychain_utilities.h"
29#include "security.h"
30
31#include <CoreFoundation/CFDateFormatter.h>
32#include <CoreFoundation/CFString.h>
33#include <Security/SecAccess.h>
34#include <Security/SecKey.h>
35#include <Security/SecKeychainItem.h>
36#include <Security/SecTrustedApplication.h>
37#include <stdio.h>
38#include <stdlib.h>
39#include <string.h>
40#include <unistd.h>
41
42
43static int
44do_key_create_pair(const char *keychainName, SecAccessRef access, CSSM_ALGORITHMS algorithm, uint32 keySizeInBits, CFAbsoluteTime from_time, CFAbsoluteTime to_time, Boolean print_hash)
45{
46	SecKeychainRef keychain = NULL;
47	OSStatus status;
48	int result = 0;
49	CSSM_CC_HANDLE contextHandle = 0;
50	CSSM_KEYUSE publicKeyUsage = CSSM_KEYUSE_ENCRYPT | CSSM_KEYUSE_VERIFY | CSSM_KEYUSE_WRAP | CSSM_KEYUSE_DERIVE;
51	uint32 publicKeyAttr = CSSM_KEYATTR_PERMANENT | CSSM_KEYATTR_EXTRACTABLE;
52	CSSM_KEYUSE privateKeyUsage = CSSM_KEYUSE_DECRYPT | CSSM_KEYUSE_SIGN | CSSM_KEYUSE_UNWRAP | CSSM_KEYUSE_DERIVE;
53	uint32 privateKeyAttr = CSSM_KEYATTR_PERMANENT | CSSM_KEYATTR_SENSITIVE | CSSM_KEYATTR_EXTRACTABLE;
54	SecKeyRef publicKey = NULL;
55	SecKeyRef privateKey = NULL;
56	SecKeychainAttributeList *attrList = NULL;
57
58	if (keychainName)
59	{
60		keychain = keychain_open(keychainName);
61		if (!keychain)
62		{
63			result = 1;
64			goto loser;
65		}
66	}
67
68	status = SecKeyCreatePair(keychain, algorithm, keySizeInBits, contextHandle,
69        publicKeyUsage,
70        publicKeyAttr,
71        privateKeyUsage,
72        privateKeyAttr,
73        access,
74        &publicKey,
75        &privateKey);
76	if (status)
77	{
78		sec_error("SecKeyCreatePair %s: %s", keychainName ? keychainName : "<NULL>", sec_errstr(status));
79		result = 1;
80		goto loser;
81	}
82
83	if (print_hash)
84	{
85		SecItemClass itemClass = 0;
86		UInt32 tag = 0x00000006;
87		UInt32 format = CSSM_DB_ATTRIBUTE_FORMAT_BLOB;
88		SecKeychainAttributeInfo info = { 1, &tag, &format };
89
90		status = SecKeychainItemCopyAttributesAndData((SecKeychainItemRef)privateKey, &info, &itemClass, &attrList, NULL, NULL);
91		if (status)
92		{
93			sec_perror("SecKeychainItemCopyAttributesAndData", status);
94			result = 1;
95			goto loser;
96		}
97
98		if (info.count != attrList->count)
99		{
100			sec_error("info count: %ld != attribute count: %ld", info.count, attrList->count);
101			result = 1;
102			goto loser;
103		}
104
105		if (tag != attrList->attr[0].tag)
106		{
107			sec_error("attribute info tag: %ld != attribute tag: %ld", tag, attrList->attr[0].tag);
108			result = 1;
109			goto loser;
110		}
111
112		print_buffer_pem(stdout, "PUBLIC KEY HASH", attrList->attr[0].length, attrList->attr[0].data);
113	}
114
115loser:
116	if (attrList)
117	{
118		status = SecKeychainItemFreeAttributesAndData(attrList, NULL);
119		if (status)
120			sec_perror("SecKeychainItemFreeAttributesAndData", status);
121	}
122
123	if (keychain)
124		CFRelease(keychain);
125	if (publicKey)
126		CFRelease(publicKey);
127	if (privateKey)
128		CFRelease(privateKey);
129
130	return result;
131}
132
133static int
134parse_algorithm(const char *name, CSSM_ALGORITHMS *algorithm)
135{
136	size_t len = strlen(name);
137
138	if (!strncmp("rsa", name, len))
139		*algorithm = CSSM_ALGID_RSA;
140	else if (!strncmp("dsa", name, len))
141		*algorithm = CSSM_ALGID_DSA;
142	else if (!strncmp("dh", name, len))
143		*algorithm = CSSM_ALGID_DH;
144	else if (!strncmp("fee", name, len))
145		*algorithm = CSSM_ALGID_FEE;
146	else
147	{
148		sec_error("Invalid algorithm: %s", name);
149		return 2;
150	}
151
152	return 0;
153}
154
155static int
156parse_time(const char *time, CFAbsoluteTime *ptime)
157{
158    CFDateFormatterRef formatter = CFDateFormatterCreate(NULL, NULL, kCFDateFormatterShortStyle, kCFDateFormatterShortStyle);
159	CFStringRef time_string = CFStringCreateWithCString(NULL, time, kCFStringEncodingUTF8);
160	int result = 0;
161	if (!CFDateFormatterGetAbsoluteTimeFromString(formatter, time_string, NULL, ptime))
162	{
163		sec_error("%s is not a valid date", time);
164		result = 1;
165	}
166    if (formatter)
167        CFRelease(formatter);
168    if (time_string)
169        CFRelease(time_string);
170	return result;
171}
172
173int
174key_create_pair(int argc, char * const *argv)
175{
176	const char *keychainName = NULL;
177	CSSM_ALGORITHMS algorithm = CSSM_ALGID_RSA;
178	uint32 keySizeInBits = 512;
179	int ch, result = 0;
180	OSStatus status;
181	Boolean always_allow = FALSE;
182	Boolean print_hash = FALSE;
183	CFAbsoluteTime from_time = 0.0, to_time = 0.0;
184	double days = 0.0;
185	SecAccessRef access = NULL;
186	CFMutableArrayRef trusted_list = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
187	CFStringRef description = NULL;
188
189/*
190    { "create-keypair", key_create_pair,
191	  "[-a alg] [-s size] [-f date] [-t date] [-d days] [-k keychain] [-A|-T appPath] description\n"
192	  "    -a  Use alg as the algorithm, can be rsa, dh, dsa or fee (default rsa)\n"
193	  "    -s  Specify the keysize in bits (default 512)\n"
194	  "    -f  Make a key valid from the specified date\n"
195	  "    -t  Make a key valid to the specified date\n"
196	  "    -d  Make a key valid for the number of days specified from now\n"
197	  "    -k  Use the specified keychain rather than the default\n"
198	  "    -H  Print the public key hash attribute\n"
199	  "    -A  Allow any application to access without warning\n"
200	  "    -T  Allow the application specified to access without warning (multiple -T options are allowed)\n"
201	  "If no options are provided, ask the user interactively.",
202*/
203
204    while ((ch = getopt(argc, argv, "a:s:f:t:d:k:AHT:h")) != -1)
205	{
206		switch  (ch)
207		{
208        case 'a':
209			result = parse_algorithm(optarg, &algorithm);
210			if (result)
211				goto loser;
212			break;
213        case 's':
214			keySizeInBits = atoi(optarg);
215			break;
216		case 'k':
217			keychainName = optarg;
218			break;
219		case 'A':
220			always_allow = TRUE;
221			break;
222		case 'H':
223			print_hash = TRUE;
224			break;
225		case 'T':
226		{
227			if (optarg[0])
228			{
229				SecTrustedApplicationRef app = NULL;
230				status = SecTrustedApplicationCreateFromPath(optarg, &app);
231				if (status)
232				{
233					sec_error("SecTrustedApplicationCreateFromPath %s: %s", optarg, sec_errstr(status));
234					result = 1;
235					goto loser;
236				}
237
238				CFArrayAppendValue(trusted_list, app);
239				CFRelease(app);
240			}
241			break;
242		}
243		case 'f':
244			 result = parse_time(optarg, &from_time);
245			if (result)
246				goto loser;
247			break;
248		case 't':
249			 result = parse_time(optarg, &to_time);
250			if (result)
251				goto loser;
252			break;
253		case 'd':
254			days = atof(optarg);
255			if (days < 1)
256			{
257				result = 2;
258				goto loser;
259			}
260			from_time = CFAbsoluteTimeGetCurrent();
261			to_time = from_time + days * 86400.0;
262			break;
263		case '?':
264		default:
265			return 2; /* @@@ Return 2 triggers usage message. */
266		}
267	}
268
269	argc -= optind;
270	argv += optind;
271
272	if (argc == 1)
273	{
274		if (*argv[0] == '\0')
275		{
276			result = 2;
277			goto loser;
278		}
279		description  = CFStringCreateWithCString(NULL, argv[0], kCFStringEncodingUTF8);
280	}
281	else if (argc != 0)
282		return 2;
283	else
284		description = CFStringCreateWithCString(NULL, "<key>", kCFStringEncodingUTF8);
285
286	if (always_allow)
287	{
288		status = SecAccessCreate(description, NULL, &access);
289		if (status)
290		{
291			sec_perror("SecAccessCreate", status);
292			result = 1;
293		}
294	}
295	else
296	{
297		status = SecAccessCreate(description, trusted_list, &access);
298		if (status)
299		{
300			sec_perror("SecAccessCreate", status);
301			result = 1;
302		}
303	}
304
305	if (result)
306		goto loser;
307
308	result = do_key_create_pair(keychainName, access, algorithm, keySizeInBits, from_time, to_time, print_hash);
309
310loser:
311	if (description)
312		CFRelease(description);
313	if (trusted_list)
314		CFRelease(trusted_list);
315	if (access)
316		CFRelease(access);
317
318	return result;
319}
320
321#if 0
322static OSStatus
323createCertCsr(
324	CSSM_TP_HANDLE		tpHand,				// eventually, a SecKeychainRef
325	CSSM_CL_HANDLE		clHand,
326	CSSM_CSP_HANDLE		cspHand,
327	SecKeyRef			subjPubKey,
328	SecKeyRef			signerPrivKey,
329	CSSM_ALGORITHMS 	sigAlg,
330	const CSSM_OID		*sigOid,
331	/*
332	 * Issuer's RDN is obtained from the issuer cert, if present, or is
333	 * assumed to be the same as the subject name (i.e., we're creating
334	 * a self-signed root cert).
335	 */
336	CSSM_BOOL			useAllDefaults,
337	CSSM_DATA_PTR		csrData)			// CSR: mallocd and RETURNED
338{
339	CE_DataAndType 				exts[2];
340	CE_DataAndType 				*extp = exts;
341	unsigned					numExts;
342
343	CSSM_DATA					refId;		// mallocd by CSSM_TP_SubmitCredRequest
344	CSSM_APPLE_TP_CERT_REQUEST	certReq;
345	CSSM_TP_REQUEST_SET			reqSet;
346	sint32						estTime;
347	CSSM_BOOL					confirmRequired;
348	CSSM_TP_RESULT_SET_PTR		resultSet;
349	CSSM_ENCODED_CERT			*encCert;
350	CSSM_APPLE_TP_NAME_OID		subjectNames[MAX_NAMES];
351	uint32						numNames;
352	CSSM_TP_CALLERAUTH_CONTEXT 	CallerAuthContext;
353	CSSM_FIELD					policyId;
354
355	/* Note a lot of the CSSM_APPLE_TP_CERT_REQUEST fields are not
356	 * used for the createCsr option, but we'll fill in as much as is practical
357	 * for either case.
358	 */
359	numExts = 0;
360
361	char challengeBuf[400];
362	if(useAllDefaults) {
363		strcpy(challengeBuf, ZDEF_CHALLENGE);
364	}
365	else {
366		while(1) {
367			getStringWithPrompt("Enter challenge string: ",
368				challengeBuf, sizeof(challengeBuf));
369			if(challengeBuf[0] != '\0') {
370				break;
371			}
372		}
373	}
374	certReq.challengeString = challengeBuf;
375
376	/* name array, get from user. */
377	if(useAllDefaults) {
378		subjectNames[0].string 	= ZDEF_COMMON_NAME;
379		subjectNames[0].oid 	= &CSSMOID_CommonName;
380		subjectNames[1].string	= ZDEF_ORG_NAME;
381		subjectNames[1].oid 	= &CSSMOID_OrganizationName;
382		subjectNames[2].string	= ZDEF_COUNTRY;
383		subjectNames[2].oid 	= &CSSMOID_CountryName;
384		subjectNames[3].string	= ZDEF_STATE;
385		subjectNames[3].oid 	= &CSSMOID_StateProvinceName;
386		numNames = 4;
387	}
388	else {
389		getNameOids(subjectNames, &numNames);
390	}
391
392	/* certReq */
393	certReq.cspHand = cspHand;
394	certReq.clHand = clHand;
395	certReq.serialNumber = 0x12345678;		// TBD - random? From user?
396	certReq.numSubjectNames = numNames;
397	certReq.subjectNames = subjectNames;
398
399	/* TBD - if we're passed in a signing cert, certReq.issuerNameX509 will
400	 * be obtained from that cert. For now we specify "self-signed" cert
401	 * by not providing an issuer name at all. */
402	certReq.numIssuerNames = 0;				// root for now
403	certReq.issuerNames = NULL;
404	certReq.issuerNameX509 = NULL;
405	certReq.certPublicKey = subjPubKey;
406	certReq.issuerPrivateKey = signerPrivKey;
407	certReq.signatureAlg = sigAlg;
408	certReq.signatureOid = *sigOid;
409	certReq.notBefore = 0;					// TBD - from user
410	certReq.notAfter = 60 * 60 * 24 * 30;	// seconds from now
411	certReq.numExtensions = numExts;
412	certReq.extensions = exts;
413
414	reqSet.NumberOfRequests = 1;
415	reqSet.Requests = &certReq;
416
417	/* a CSSM_TP_CALLERAUTH_CONTEXT to specify an OID */
418	memset(&CallerAuthContext, 0, sizeof(CSSM_TP_CALLERAUTH_CONTEXT));
419	memset(&policyId, 0, sizeof(CSSM_FIELD));
420	policyId.FieldOid = CSSMOID_APPLE_TP_CSR_GEN;
421	CallerAuthContext.Policy.NumberOfPolicyIds = 1;
422	CallerAuthContext.Policy.PolicyIds = &policyId;
423
424	CSSM_RETURN crtn = CSSM_TP_SubmitCredRequest(tpHand,
425		NULL,				// PreferredAuthority
426		CSSM_TP_AUTHORITY_REQUEST_CERTISSUE,
427		&reqSet,
428		&CallerAuthContext,
429		&estTime,
430		&refId);
431
432	/* before proceeding, free resources allocated thus far */
433	if(!useAllDefaults) {
434		freeNameOids(subjectNames, numNames);
435	}
436
437	if(crtn) {
438		printError("***Error submitting credential request","CSSM_TP_SubmitCredRequest",crtn);
439		return crtn;
440	}
441	crtn = CSSM_TP_RetrieveCredResult(tpHand,
442		&refId,
443		NULL,				// CallerAuthCredentials
444		&estTime,
445		&confirmRequired,
446		&resultSet);
447	if(crtn) {
448		printError("***Error retreiving credential request","CSSM_TP_RetrieveCredResult",crtn);
449		return crtn;
450	}
451	if(resultSet == NULL) {
452		printf("***CSSM_TP_RetrieveCredResult returned NULL result set.\n");
453		return ioErr;
454	}
455	encCert = (CSSM_ENCODED_CERT *)resultSet->Results;
456	*csrData = encCert->CertBlob;
457
458	/* free resources allocated by TP */
459	APP_FREE(refId.Data);
460	APP_FREE(encCert);
461	APP_FREE(resultSet);
462	return noErr;
463}
464#endif
465
466#if 0
467/* this was added in Michael's integration of PR-3420772, but this is an unimplemented command */
468
469int
470csr_create(int argc, char * const *argv)
471{
472	int result = 0;
473	int ch;
474	const char *keychainName = NULL;
475	CSSM_ALGORITHMS algorithm = CSSM_ALGID_RSA;
476	uint32 keySizeInBits = 512;
477	OSStatus status;
478	Boolean always_allow = FALSE;
479	CFAbsoluteTime from_time = 0.0, to_time = 0.0;
480	double days = 0.0;
481	SecAccessRef access = NULL;
482	CFMutableArrayRef trusted_list = NULL;
483	CFStringRef description = NULL;
484
485/*
486    { "create-keypair", key_create_pair,
487	  "[-a alg] [-s size] [-f date] [-t date] [-d days] [-k keychain] [-u sdux] [-c challenge] description\n"
488	  " [-k keychain] [-u sewx] description\n"
489	  "    -a  Look for key with alg as the algorithm, can be rsa, dh, dsa or fee (default rsa)\n"
490	  "    -s  Look for key with keysize in bits\n"
491	  "    -c  Add challenge to the key as a challange string\n"
492	  "    -f  Look for a key at least valid from the specified date\n"
493	  "    -t  Look for a key at least valid to the specified date\n"
494	  "    -d  Look for a key at least valid for the number of days specified from now\n"
495	  "	   -u  Look for a key with the specified usage flags (s)igning (d)ecryption (u)nwrapping e(x)change\n"
496	  "    -k  Look in specified keychain rather than the default search list\n"
497	  "If no options are provided ask the user interactively",
498*/
499
500    while ((ch = getopt(argc, argv, "a:s:f:t:d:k:AT:h")) != -1)
501	{
502		switch  (ch)
503		{
504        case 'a':
505			result = parse_algorithm(optarg, &algorithm);
506			if (result)
507				goto loser;
508			break;
509        case 's':
510			keySizeInBits = atoi(optarg);
511			break;
512		case 'k':
513			keychainName = optarg;
514			break;
515		case 'A':
516			always_allow = TRUE;
517			break;
518		case 'T':
519		{
520			if (!trusted_list)
521			{
522				trusted_list = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
523			}
524
525			if (optarg[0])
526			{
527				SecTrustedApplicationRef app = NULL;
528				status = SecTrustedApplicationCreateFromPath(optarg, &app);
529				if (status)
530				{
531					sec_error("SecTrustedApplicationCreateFromPath %s: %s", optarg, sec_errstr(status));
532					result = 1;
533					goto loser;
534				}
535
536				CFArrayAppendValue(trusted_list, app);
537				CFRelease(app);
538				break;
539			}
540		}
541		case 'f':
542			 result = parse_time(optarg, &from_time);
543			if (result)
544				goto loser;
545			break;
546		case 't':
547			 result = parse_time(optarg, &to_time);
548			if (result)
549				goto loser;
550			break;
551		case 'd':
552			days = atof(optarg);
553			if (days < 1)
554			{
555				result = 2;
556				goto loser;
557			}
558			from_time = CFAbsoluteTimeGetCurrent();
559			to_time = from_time + days * 86400.0;
560			break;
561		case '?':
562		default:
563			return 2; /* @@@ Return 2 triggers usage message. */
564		}
565	}
566
567	argc -= optind;
568	argv += optind;
569
570	if (argc == 1)
571	{
572		if (*argv[0] == '\0')
573		{
574			result = 2;
575			goto loser;
576		}
577		description  = CFStringCreateWithCString(NULL, argv[0], kCFStringEncodingUTF8);
578	}
579	else if (argc != 0)
580		return 2;
581	else
582		description = CFStringCreateWithCString(NULL, "<key>", kCFStringEncodingUTF8);
583
584	if (always_allow)
585	{
586		status = SecAccessCreate(description, NULL, &access);
587		if (status)
588		{
589			sec_perror("SecAccessCreate", status);
590			result = 1;
591		}
592		// @@@ Make the acl always allow now.
593	}
594	else
595	{
596		status = SecAccessCreate(description, trusted_list, &access);
597		if (status)
598		{
599			sec_perror("SecAccessCreate", status);
600			result = 1;
601		}
602	}
603
604	if (result)
605		goto loser;
606
607	result = do_csr_create(keychainName, access, algorithm, keySizeInBits, from_time, to_time);
608
609loser:
610	if (description)
611		CFRelease(description);
612	if (trusted_list)
613		CFRelease(trusted_list);
614	if (access)
615		CFRelease(access);
616
617	return result;
618}
619#endif
620