1/*
2 * Copyright (c) 2003-2004 Apple Computer, 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
167	return result;
168}
169
170int
171key_create_pair(int argc, char * const *argv)
172{
173	const char *keychainName = NULL;
174	CSSM_ALGORITHMS algorithm = CSSM_ALGID_RSA;
175	uint32 keySizeInBits = 512;
176	int ch, result = 0;
177	OSStatus status;
178	Boolean always_allow = FALSE;
179	Boolean print_hash = FALSE;
180	CFAbsoluteTime from_time = 0.0, to_time = 0.0;
181	double days = 0.0;
182	SecAccessRef access = NULL;
183	CFMutableArrayRef trusted_list = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
184	CFStringRef description = NULL;
185
186/*
187    { "create-keypair", key_create_pair,
188	  "[-a alg] [-s size] [-f date] [-t date] [-d days] [-k keychain] [-A|-T appPath] description\n"
189	  "    -a  Use alg as the algorithm, can be rsa, dh, dsa or fee (default rsa)\n"
190	  "    -s  Specify the keysize in bits (default 512)\n"
191	  "    -f  Make a key valid from the specified date\n"
192	  "    -t  Make a key valid to the specified date\n"
193	  "    -d  Make a key valid for the number of days specified from now\n"
194	  "    -k  Use the specified keychain rather than the default\n"
195	  "    -H  Print the public key hash attribute\n"
196	  "    -A  Allow any application to access without warning\n"
197	  "    -T  Allow the application specified to access without warning (multiple -T options are allowed)\n"
198	  "If no options are provided, ask the user interactively.",
199*/
200
201    while ((ch = getopt(argc, argv, "a:s:f:t:d:k:AHT:h")) != -1)
202	{
203		switch  (ch)
204		{
205        case 'a':
206			result = parse_algorithm(optarg, &algorithm);
207			if (result)
208				goto loser;
209			break;
210        case 's':
211			keySizeInBits = atoi(optarg);
212			break;
213		case 'k':
214			keychainName = optarg;
215			break;
216		case 'A':
217			always_allow = TRUE;
218			break;
219		case 'H':
220			print_hash = TRUE;
221			break;
222		case 'T':
223		{
224			if (optarg[0])
225			{
226				SecTrustedApplicationRef app = NULL;
227				status = SecTrustedApplicationCreateFromPath(optarg, &app);
228				if (status)
229				{
230					sec_error("SecTrustedApplicationCreateFromPath %s: %s", optarg, sec_errstr(status));
231					result = 1;
232					goto loser;
233				}
234
235				CFArrayAppendValue(trusted_list, app);
236				CFRelease(app);
237			}
238			break;
239		}
240		case 'f':
241			 result = parse_time(optarg, &from_time);
242			if (result)
243				goto loser;
244			break;
245		case 't':
246			 result = parse_time(optarg, &to_time);
247			if (result)
248				goto loser;
249			break;
250		case 'd':
251			days = atof(optarg);
252			if (days < 1)
253			{
254				result = 2;
255				goto loser;
256			}
257			from_time = CFAbsoluteTimeGetCurrent();
258			to_time = from_time + days * 86400.0;
259			break;
260		case '?':
261		default:
262			return 2; /* @@@ Return 2 triggers usage message. */
263		}
264	}
265
266	argc -= optind;
267	argv += optind;
268
269	if (argc == 1)
270	{
271		if (*argv[0] == '\0')
272		{
273			result = 2;
274			goto loser;
275		}
276		description  = CFStringCreateWithCString(NULL, argv[0], kCFStringEncodingUTF8);
277	}
278	else if (argc != 0)
279		return 2;
280	else
281		description = CFStringCreateWithCString(NULL, "<key>", kCFStringEncodingUTF8);
282
283	if (always_allow)
284	{
285		status = SecAccessCreate(description, NULL, &access);
286		if (status)
287		{
288			sec_perror("SecAccessCreate", status);
289			result = 1;
290		}
291	}
292	else
293	{
294		status = SecAccessCreate(description, trusted_list, &access);
295		if (status)
296		{
297			sec_perror("SecAccessCreate", status);
298			result = 1;
299		}
300	}
301
302	if (result)
303		goto loser;
304
305	result = do_key_create_pair(keychainName, access, algorithm, keySizeInBits, from_time, to_time, print_hash);
306
307loser:
308	if (description)
309		CFRelease(description);
310	if (trusted_list)
311		CFRelease(trusted_list);
312	if (access)
313		CFRelease(access);
314
315	return result;
316}
317
318#if 0
319static OSStatus
320createCertCsr(
321	CSSM_TP_HANDLE		tpHand,				// eventually, a SecKeychainRef
322	CSSM_CL_HANDLE		clHand,
323	CSSM_CSP_HANDLE		cspHand,
324	SecKeyRef			subjPubKey,
325	SecKeyRef			signerPrivKey,
326	CSSM_ALGORITHMS 	sigAlg,
327	const CSSM_OID		*sigOid,
328	/*
329	 * Issuer's RDN is obtained from the issuer cert, if present, or is
330	 * assumed to be the same as the subject name (i.e., we're creating
331	 * a self-signed root cert).
332	 */
333	CSSM_BOOL			useAllDefaults,
334	CSSM_DATA_PTR		csrData)			// CSR: mallocd and RETURNED
335{
336	CE_DataAndType 				exts[2];
337	CE_DataAndType 				*extp = exts;
338	unsigned					numExts;
339
340	CSSM_DATA					refId;		// mallocd by CSSM_TP_SubmitCredRequest
341	CSSM_APPLE_TP_CERT_REQUEST	certReq;
342	CSSM_TP_REQUEST_SET			reqSet;
343	sint32						estTime;
344	CSSM_BOOL					confirmRequired;
345	CSSM_TP_RESULT_SET_PTR		resultSet;
346	CSSM_ENCODED_CERT			*encCert;
347	CSSM_APPLE_TP_NAME_OID		subjectNames[MAX_NAMES];
348	uint32						numNames;
349	CSSM_TP_CALLERAUTH_CONTEXT 	CallerAuthContext;
350	CSSM_FIELD					policyId;
351
352	/* Note a lot of the CSSM_APPLE_TP_CERT_REQUEST fields are not
353	 * used for the createCsr option, but we'll fill in as much as is practical
354	 * for either case.
355	 */
356	numExts = 0;
357
358	char challengeBuf[400];
359	if(useAllDefaults) {
360		strcpy(challengeBuf, ZDEF_CHALLENGE);
361	}
362	else {
363		while(1) {
364			getStringWithPrompt("Enter challenge string: ",
365				challengeBuf, sizeof(challengeBuf));
366			if(challengeBuf[0] != '\0') {
367				break;
368			}
369		}
370	}
371	certReq.challengeString = challengeBuf;
372
373	/* name array, get from user. */
374	if(useAllDefaults) {
375		subjectNames[0].string 	= ZDEF_COMMON_NAME;
376		subjectNames[0].oid 	= &CSSMOID_CommonName;
377		subjectNames[1].string	= ZDEF_ORG_NAME;
378		subjectNames[1].oid 	= &CSSMOID_OrganizationName;
379		subjectNames[2].string	= ZDEF_COUNTRY;
380		subjectNames[2].oid 	= &CSSMOID_CountryName;
381		subjectNames[3].string	= ZDEF_STATE;
382		subjectNames[3].oid 	= &CSSMOID_StateProvinceName;
383		numNames = 4;
384	}
385	else {
386		getNameOids(subjectNames, &numNames);
387	}
388
389	/* certReq */
390	certReq.cspHand = cspHand;
391	certReq.clHand = clHand;
392	certReq.serialNumber = 0x12345678;		// TBD - random? From user?
393	certReq.numSubjectNames = numNames;
394	certReq.subjectNames = subjectNames;
395
396	/* TBD - if we're passed in a signing cert, certReq.issuerNameX509 will
397	 * be obtained from that cert. For now we specify "self-signed" cert
398	 * by not providing an issuer name at all. */
399	certReq.numIssuerNames = 0;				// root for now
400	certReq.issuerNames = NULL;
401	certReq.issuerNameX509 = NULL;
402	certReq.certPublicKey = subjPubKey;
403	certReq.issuerPrivateKey = signerPrivKey;
404	certReq.signatureAlg = sigAlg;
405	certReq.signatureOid = *sigOid;
406	certReq.notBefore = 0;					// TBD - from user
407	certReq.notAfter = 60 * 60 * 24 * 30;	// seconds from now
408	certReq.numExtensions = numExts;
409	certReq.extensions = exts;
410
411	reqSet.NumberOfRequests = 1;
412	reqSet.Requests = &certReq;
413
414	/* a CSSM_TP_CALLERAUTH_CONTEXT to specify an OID */
415	memset(&CallerAuthContext, 0, sizeof(CSSM_TP_CALLERAUTH_CONTEXT));
416	memset(&policyId, 0, sizeof(CSSM_FIELD));
417	policyId.FieldOid = CSSMOID_APPLE_TP_CSR_GEN;
418	CallerAuthContext.Policy.NumberOfPolicyIds = 1;
419	CallerAuthContext.Policy.PolicyIds = &policyId;
420
421	CSSM_RETURN crtn = CSSM_TP_SubmitCredRequest(tpHand,
422		NULL,				// PreferredAuthority
423		CSSM_TP_AUTHORITY_REQUEST_CERTISSUE,
424		&reqSet,
425		&CallerAuthContext,
426		&estTime,
427		&refId);
428
429	/* before proceeding, free resources allocated thus far */
430	if(!useAllDefaults) {
431		freeNameOids(subjectNames, numNames);
432	}
433
434	if(crtn) {
435		printError("***Error submitting credential request","CSSM_TP_SubmitCredRequest",crtn);
436		return crtn;
437	}
438	crtn = CSSM_TP_RetrieveCredResult(tpHand,
439		&refId,
440		NULL,				// CallerAuthCredentials
441		&estTime,
442		&confirmRequired,
443		&resultSet);
444	if(crtn) {
445		printError("***Error retreiving credential request","CSSM_TP_RetrieveCredResult",crtn);
446		return crtn;
447	}
448	if(resultSet == NULL) {
449		printf("***CSSM_TP_RetrieveCredResult returned NULL result set.\n");
450		return ioErr;
451	}
452	encCert = (CSSM_ENCODED_CERT *)resultSet->Results;
453	*csrData = encCert->CertBlob;
454
455	/* free resources allocated by TP */
456	APP_FREE(refId.Data);
457	APP_FREE(encCert);
458	APP_FREE(resultSet);
459	return noErr;
460}
461#endif
462
463#if 0
464/* this was added in Michael's integration of PR-3420772, but this is an unimplemented command */
465
466int
467csr_create(int argc, char * const *argv)
468{
469	int result = 0;
470	int ch;
471	const char *keychainName = NULL;
472	CSSM_ALGORITHMS algorithm = CSSM_ALGID_RSA;
473	uint32 keySizeInBits = 512;
474	OSStatus status;
475	Boolean always_allow = FALSE;
476	CFAbsoluteTime from_time = 0.0, to_time = 0.0;
477	double days = 0.0;
478	SecAccessRef access = NULL;
479	CFMutableArrayRef trusted_list = NULL;
480	CFStringRef description = NULL;
481
482/*
483    { "create-keypair", key_create_pair,
484	  "[-a alg] [-s size] [-f date] [-t date] [-d days] [-k keychain] [-u sdux] [-c challenge] description\n"
485	  " [-k keychain] [-u sewx] description\n"
486	  "    -a  Look for key with alg as the algorithm, can be rsa, dh, dsa or fee (default rsa)\n"
487	  "    -s  Look for key with keysize in bits\n"
488	  "    -c  Add challenge to the key as a challange string\n"
489	  "    -f  Look for a key at least valid from the specified date\n"
490	  "    -t  Look for a key at least valid to the specified date\n"
491	  "    -d  Look for a key at least valid for the number of days specified from now\n"
492	  "	   -u  Look for a key with the specified usage flags (s)igning (d)ecryption (u)nwrapping e(x)change\n"
493	  "    -k  Look in specified keychain rather than the default search list\n"
494	  "If no options are provided ask the user interactively",
495*/
496
497    while ((ch = getopt(argc, argv, "a:s:f:t:d:k:AT:h")) != -1)
498	{
499		switch  (ch)
500		{
501        case 'a':
502			result = parse_algorithm(optarg, &algorithm);
503			if (result)
504				goto loser;
505			break;
506        case 's':
507			keySizeInBits = atoi(optarg);
508			break;
509		case 'k':
510			keychainName = optarg;
511			break;
512		case 'A':
513			always_allow = TRUE;
514			break;
515		case 'T':
516		{
517			if (!trusted_list)
518			{
519				trusted_list = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
520			}
521
522			if (optarg[0])
523			{
524				SecTrustedApplicationRef app = NULL;
525				status = SecTrustedApplicationCreateFromPath(optarg, &app);
526				if (status)
527				{
528					sec_error("SecTrustedApplicationCreateFromPath %s: %s", optarg, sec_errstr(status));
529					result = 1;
530					goto loser;
531				}
532
533				CFArrayAppendValue(trusted_list, app);
534				CFRelease(app);
535				break;
536			}
537		}
538		case 'f':
539			 result = parse_time(optarg, &from_time);
540			if (result)
541				goto loser;
542			break;
543		case 't':
544			 result = parse_time(optarg, &to_time);
545			if (result)
546				goto loser;
547			break;
548		case 'd':
549			days = atof(optarg);
550			if (days < 1)
551			{
552				result = 2;
553				goto loser;
554			}
555			from_time = CFAbsoluteTimeGetCurrent();
556			to_time = from_time + days * 86400.0;
557			break;
558		case '?':
559		default:
560			return 2; /* @@@ Return 2 triggers usage message. */
561		}
562	}
563
564	argc -= optind;
565	argv += optind;
566
567	if (argc == 1)
568	{
569		if (*argv[0] == '\0')
570		{
571			result = 2;
572			goto loser;
573		}
574		description  = CFStringCreateWithCString(NULL, argv[0], kCFStringEncodingUTF8);
575	}
576	else if (argc != 0)
577		return 2;
578	else
579		description = CFStringCreateWithCString(NULL, "<key>", kCFStringEncodingUTF8);
580
581	if (always_allow)
582	{
583		status = SecAccessCreate(description, NULL, &access);
584		if (status)
585		{
586			sec_perror("SecAccessCreate", status);
587			result = 1;
588		}
589		// @@@ Make the acl always allow now.
590	}
591	else
592	{
593		status = SecAccessCreate(description, trusted_list, &access);
594		if (status)
595		{
596			sec_perror("SecAccessCreate", status);
597			result = 1;
598		}
599	}
600
601	if (result)
602		goto loser;
603
604	result = do_csr_create(keychainName, access, algorithm, keySizeInBits, from_time, to_time);
605
606loser:
607	if (description)
608		CFRelease(description);
609	if (trusted_list)
610		CFRelease(trusted_list);
611	if (access)
612		CFRelease(access);
613
614	return result;
615}
616#endif
617