]> git.saurik.com Git - apple/security.git/blob - libsecurity_keychain/lib/SecImportExportOpenSSH.cpp
Security-55179.13.tar.gz
[apple/security.git] / libsecurity_keychain / lib / SecImportExportOpenSSH.cpp
1 /*
2 * Copyright (c) 2006 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
24
25 /*
26 * opensshCoding.cpp - Encoding and decoding of OpenSSH format public keys.
27 *
28 * Created 8/29/2006 by dmitch.
29 */
30
31 #include "SecImportExportOpenSSH.h"
32 #include "SecImportExportUtils.h"
33 #include "SecImportExportCrypto.h"
34 #include <ctype.h>
35 #include <CommonCrypto/CommonDigest.h> /* for CC_MD5_DIGEST_LENGTH */
36 #include <security_utilities/debugging.h>
37 #include <CoreServices/../Frameworks/CarbonCore.framework/Headers/MacErrors.h>
38 #include <security_cdsa_utils/cuCdsaUtils.h>
39
40 #define SecSSHDbg(args...) secdebug("openssh", ## args)
41
42 #define SSHv2_PUB_KEY_NAME "OpenSSHv2 Public Key"
43 #define SSHv1_PUB_KEY_NAME "OpenSSHv1 Public Key"
44 #define SSHv1_PRIV_KEY_NAME "OpenSSHv1 Private Key"
45
46 #pragma mark --- Utility functions ---
47
48 /* skip whitespace */
49 static void skipWhite(
50 const unsigned char *&cp,
51 unsigned &bytesLeft)
52 {
53 while(bytesLeft != 0) {
54 if(isspace((int)(*cp))) {
55 cp++;
56 bytesLeft--;
57 }
58 else {
59 return;
60 }
61 }
62 }
63
64 /* find next whitespace or EOF - if EOF, rtn pointer points to one past EOF */
65 static const unsigned char *findNextWhite(
66 const unsigned char *cp,
67 unsigned &bytesLeft)
68 {
69 while(bytesLeft != 0) {
70 if(isspace((int)(*cp))) {
71 return cp;
72 }
73 cp++;
74 bytesLeft--;
75 }
76 return cp;
77 }
78
79 /* obtain comment as the n'th whitespace-delimited field */
80 static char *commentAsNthField(
81 const unsigned char *key,
82 unsigned keyLen,
83 unsigned n)
84
85 {
86 unsigned dex;
87
88 skipWhite(key, keyLen);
89 if(keyLen == 0) {
90 return NULL;
91 }
92 for(dex=0; dex<(n-1); dex++) {
93 key = findNextWhite(key, keyLen);
94 if(keyLen == 0) {
95 return NULL;
96 }
97 skipWhite(key, keyLen);
98 if(keyLen == 0) {
99 return NULL;
100 }
101 }
102
103 /* cp points to start of nth field */
104 char *rtnStr = (char *)malloc(keyLen + 1);
105 memmove(rtnStr, key, keyLen);
106 if(rtnStr[keyLen - 1] == '\n') {
107 /* normal terminator - snip it off */
108 rtnStr[keyLen - 1] = '\0';
109 }
110 else {
111 rtnStr[keyLen] = '\0';
112 }
113 return rtnStr;
114
115 }
116
117 static uint32_t readUint32(
118 const unsigned char *&cp, // IN/OUT
119 unsigned &len) // IN/OUT
120 {
121 uint32_t r = 0;
122
123 for(unsigned dex=0; dex<sizeof(uint32_t); dex++) {
124 r <<= 8;
125 r |= *cp++;
126 }
127 len -= 4;
128 return r;
129 }
130
131 static uint16_t readUint16(
132 const unsigned char *&cp, // IN/OUT
133 unsigned &len) // IN/OUT
134 {
135 uint16_t r = *cp++;
136 r <<= 8;
137 r |= *cp++;
138 len -= 2;
139 return r;
140 }
141
142 /* Skip over an SSHv1 private key formatted bignum */
143 static void skipBigNum(
144 const unsigned char *&cp, // IN/OUT
145 unsigned &len) // IN/OUT
146 {
147 if(len < 2) {
148 cp += len;
149 len = 0;
150 return;
151 }
152 uint16 numBits = readUint16(cp, len);
153 unsigned numBytes = (numBits + 7) / 8;
154 if(numBytes > len) {
155 cp += len;
156 len = 0;
157 return;
158 }
159 cp += numBytes;
160 len -= numBytes;
161 }
162
163 static char *genPrintName(
164 const char *header, // e.g. SSHv2_PUB_KEY_NAME
165 const char *comment) // optional, from key
166 {
167 unsigned totalLen = strlen(header) + 1;
168 if(comment) {
169 /* append ": <comment>" */
170 totalLen += strlen(comment);
171 totalLen += 2;
172 }
173 char *rtnStr = (char *)malloc(totalLen);
174 if(comment) {
175 snprintf(rtnStr, totalLen, "%s: %s", header, comment);
176 }
177 else {
178 strcpy(rtnStr, header);
179 }
180 return rtnStr;
181 }
182
183 #pragma mark --- Infer PrintName attribute from raw keys ---
184
185 /* obtain comment from OpenSSHv2 public key */
186 static char *opensshV2PubComment(
187 const unsigned char *key,
188 unsigned keyLen)
189 {
190 /*
191 * The format here is
192 * header
193 * <space>
194 * keyblob
195 * <space>
196 * optional comment
197 * \n
198 */
199 char *comment = commentAsNthField(key, keyLen, 3);
200 char *rtnStr = genPrintName(SSHv2_PUB_KEY_NAME, comment);
201 if(comment) {
202 free(comment);
203 }
204 return rtnStr;
205 }
206
207 /* obtain comment from OpenSSHv1 public key */
208 static char *opensshV1PubComment(
209 const unsigned char *key,
210 unsigned keyLen)
211 {
212 /*
213 * Format:
214 * numbits
215 * <space>
216 * e (bignum in decimal)
217 * <space>
218 * n (bignum in decimal)
219 * <space>
220 * optional comment
221 * \n
222 */
223 char *comment = commentAsNthField(key, keyLen, 4);
224 char *rtnStr = genPrintName(SSHv1_PUB_KEY_NAME, comment);
225 if(comment) {
226 free(comment);
227 }
228 return rtnStr;
229 }
230
231 static const char *authfile_id_string = "SSH PRIVATE KEY FILE FORMAT 1.1\n";
232
233 /* obtain comment from OpenSSHv1 private key, wrapped or clear */
234 static char *opensshV1PrivComment(
235 const unsigned char *key,
236 unsigned keyLen)
237 {
238 /*
239 * Format:
240 * "SSH PRIVATE KEY FILE FORMAT 1.1\n"
241 * 1 byte cipherSpec
242 * 4 byte spares
243 * 4 bytes numBits
244 * bignum n
245 * bignum e
246 * 4 byte comment length
247 * comment
248 * private key components, possibly encrypted
249 *
250 * A bignum is encoded like so:
251 * 2 bytes numBits
252 * (numBits + 7)/8 bytes of data
253 */
254 /* length: ID string, NULL, Cipher, 4-byte spare */
255 unsigned len = strlen(authfile_id_string);
256 if(keyLen < (len + 6)) {
257 return NULL;
258 }
259 if(memcmp(authfile_id_string, key, len)) {
260 return NULL;
261 }
262 key += (len + 6);
263 keyLen -= (len + 6);
264
265 /* key points to numBits */
266 if(keyLen < 4) {
267 return NULL;
268 }
269 key += 4;
270 keyLen -= 4;
271
272 /* key points to n */
273 skipBigNum(key, keyLen);
274 if(keyLen == 0) {
275 return NULL;
276 }
277 skipBigNum(key, keyLen);
278 if(keyLen == 0) {
279 return NULL;
280 }
281
282 char *comment = NULL;
283 uint32 commentLen = readUint32(key, keyLen);
284 if((commentLen != 0) && (commentLen <= keyLen)) {
285 comment = (char *)malloc(commentLen + 1);
286 memmove(comment, key, commentLen);
287 comment[commentLen] = '\0';
288 }
289
290 char *rtnStr = genPrintName(SSHv1_PRIV_KEY_NAME, comment);
291 if(comment) {
292 free(comment);
293 }
294 return rtnStr;
295 }
296
297 /*
298 * Infer PrintName attribute from raw key's 'comment' field.
299 * Returned string is mallocd and must be freed by caller.
300 */
301 char *impExpOpensshInferPrintName(
302 CFDataRef external,
303 SecExternalItemType externType,
304 SecExternalFormat externFormat)
305 {
306 const unsigned char *key = (const unsigned char *)CFDataGetBytePtr(external);
307 unsigned keyLen = CFDataGetLength(external);
308 switch(externType) {
309 case kSecItemTypePublicKey:
310 switch(externFormat) {
311 case kSecFormatSSH:
312 return opensshV1PubComment(key, keyLen);
313 case kSecFormatSSHv2:
314 return opensshV2PubComment(key, keyLen);
315 default:
316 /* impossible, right? */
317 break;
318 }
319 break;
320 case kSecItemTypePrivateKey:
321 switch(externFormat) {
322 case kSecFormatSSH:
323 case kSecFormatWrappedSSH:
324 return opensshV1PrivComment(key, keyLen);
325 default:
326 break;
327 }
328 break;
329 default:
330 break;
331 }
332 return NULL;
333 }
334
335 #pragma mark --- Infer DescriptiveData from PrintName ---
336
337 /*
338 * Infer DescriptiveData (i.e., comment) from a SecKeyRef's PrintName
339 * attribute.
340 */
341 void impExpOpensshInferDescData(
342 SecKeyRef keyRef,
343 CssmOwnedData &descData)
344 {
345 OSStatus ortn;
346 SecKeychainAttributeInfo attrInfo;
347 SecKeychainAttrType attrType = kSecKeyPrintName;
348 attrInfo.count = 1;
349 attrInfo.tag = &attrType;
350 attrInfo.format = NULL;
351 SecKeychainAttributeList *attrList = NULL;
352
353 ortn = SecKeychainItemCopyAttributesAndData(
354 (SecKeychainItemRef)keyRef,
355 &attrInfo,
356 NULL, // itemClass
357 &attrList,
358 NULL, // don't need the data
359 NULL);
360 if(ortn) {
361 SecSSHDbg("SecKeychainItemCopyAttributesAndData returned %ld", ortn);
362 return;
363 }
364 /* subsequent errors to errOut: */
365 SecKeychainAttribute *attr = attrList->attr;
366
367 /*
368 * On a previous import, we would have set this to something like
369 * "OpenSSHv2 Public Key: comment".
370 * We want to strip off everything up to the actual comment.
371 */
372 unsigned toStrip = 0;
373
374 /* min length of attribute value for this code to be meaningful */
375 unsigned len = strlen(SSHv2_PUB_KEY_NAME) + 1;
376 char *printNameStr = NULL;
377 if(len < attr->length) {
378 printNameStr = (char *)malloc(attr->length + 1);
379 memmove(printNameStr, attr->data, attr->length);
380 printNameStr[attr->length] = '\0';
381 if(strstr(printNameStr, SSHv2_PUB_KEY_NAME) == printNameStr) {
382 toStrip = strlen(SSHv2_PUB_KEY_NAME);
383 }
384 else if(strstr(printNameStr, SSHv1_PUB_KEY_NAME) == printNameStr) {
385 toStrip = strlen(SSHv1_PUB_KEY_NAME);
386 }
387 else if(strstr(printNameStr, SSHv1_PRIV_KEY_NAME) == printNameStr) {
388 toStrip = strlen(SSHv1_PRIV_KEY_NAME);
389 }
390 if(toStrip) {
391 /* only strip if we have ": " after toStrip bytes */
392 if((printNameStr[toStrip] == ':') && (printNameStr[toStrip+1] == ' ')) {
393 toStrip += 2;
394 }
395 }
396 }
397 if(printNameStr) {
398 free(printNameStr);
399 }
400 len = attr->length;
401
402 unsigned char *attrVal;
403
404 if(len < toStrip) {
405 SecSSHDbg("impExpOpensshInferDescData: string parse screwup");
406 goto errOut;
407 }
408 if(len > toStrip) {
409 /* Normal case of stripping off leading header */
410 len -= toStrip;
411 }
412 else {
413 /*
414 * If equal, then the attr value *is* "OpenSSHv2 Public Key: " with
415 * no comment. Not sure how that could happen, but let's be careful.
416 */
417 toStrip = 0;
418 }
419
420 attrVal = ((unsigned char *)attr->data) + toStrip;
421 descData.copy(attrVal, len);
422 errOut:
423 SecKeychainItemFreeAttributesAndData(attrList, NULL);
424 return;
425 }
426
427 #pragma mark --- Derive SSHv1 wrap/unwrap key ---
428
429 /*
430 * Common code to derive a wrap/unwrap key for OpenSSHv1.
431 * Caller must CSSM_FreeKey when done.
432 */
433 static CSSM_RETURN openSSHv1DeriveKey(
434 CSSM_CSP_HANDLE cspHand,
435 const SecKeyImportExportParameters *keyParams, // required
436 impExpVerifyPhrase verifyPhrase, // for secure passphrase
437 CSSM_KEY_PTR symKey) // RETURNED
438 {
439 CSSM_KEY *passKey = NULL;
440 CFDataRef cfPhrase = NULL;
441 CSSM_RETURN crtn;
442 OSStatus ortn;
443 CSSM_DATA dummyLabel;
444 uint32 keyAttr;
445 CSSM_CC_HANDLE ccHand = 0;
446 CSSM_ACCESS_CREDENTIALS creds;
447 CSSM_CRYPTO_DATA seed;
448 CSSM_DATA nullParam = {0, NULL};
449
450 memset(symKey, 0, sizeof(CSSM_KEY));
451
452 /* passphrase or passkey? */
453 ortn = impExpPassphraseCommon(keyParams, cspHand, SPF_Data, verifyPhrase,
454 (CFTypeRef *)&cfPhrase, &passKey);
455 if(ortn) {
456 return ortn;
457 }
458 /* subsequent errors to errOut: */
459
460 memset(&seed, 0, sizeof(seed));
461 if(cfPhrase != NULL) {
462 /* TBD - caller-supplied empty passphrase means "export in the clear" */
463 unsigned len = CFDataGetLength(cfPhrase);
464 seed.Param.Data = (uint8 *)malloc(len);
465 seed.Param.Length = len;
466 memmove(seed.Param.Data, CFDataGetBytePtr(cfPhrase), len);
467 CFRelease(cfPhrase);
468 }
469
470 memset(&creds, 0, sizeof(CSSM_ACCESS_CREDENTIALS));
471 crtn = CSSM_CSP_CreateDeriveKeyContext(cspHand,
472 CSSM_ALGID_OPENSSH1,
473 CSSM_ALGID_OPENSSH1,
474 CC_MD5_DIGEST_LENGTH * 8,
475 &creds,
476 passKey, // BaseKey
477 0, // iterationCount
478 NULL, // salt
479 &seed,
480 &ccHand);
481 if(crtn) {
482 SecSSHDbg("openSSHv1DeriveKey CSSM_CSP_CreateDeriveKeyContext failure");
483 goto errOut;
484 }
485
486 /* not extractable even for the short time this key lives */
487 keyAttr = CSSM_KEYATTR_RETURN_REF | CSSM_KEYATTR_SENSITIVE;
488 dummyLabel.Data = (uint8 *)"temp unwrap key";
489 dummyLabel.Length = strlen((char *)dummyLabel.Data);
490
491 crtn = CSSM_DeriveKey(ccHand,
492 &nullParam,
493 CSSM_KEYUSE_ANY,
494 keyAttr,
495 &dummyLabel,
496 NULL, // cred and acl
497 symKey);
498 if(crtn) {
499 SecSSHDbg("openSSHv1DeriveKey CSSM_DeriveKey failure");
500 }
501 errOut:
502 if(ccHand != 0) {
503 CSSM_DeleteContext(ccHand);
504 }
505 if(passKey != NULL) {
506 CSSM_FreeKey(cspHand, NULL, passKey, CSSM_FALSE);
507 free(passKey);
508 }
509 if(seed.Param.Data) {
510 memset(seed.Param.Data, 0, seed.Param.Length);
511 free(seed.Param.Data);
512 }
513 return crtn;
514 }
515
516 #pragma mark -- OpenSSHv1 Wrap/Unwrap ---
517
518 /*
519 * If cspHand is provided instead of importKeychain, the CSP
520 * handle MUST be for the CSPDL, not for the raw CSP.
521 */
522 OSStatus impExpWrappedOpenSSHImport(
523 CFDataRef inData,
524 SecKeychainRef importKeychain, // optional
525 CSSM_CSP_HANDLE cspHand, // required
526 SecItemImportExportFlags flags,
527 const SecKeyImportExportParameters *keyParams, // optional
528 const char *printName,
529 CFMutableArrayRef outArray) // optional, append here
530 {
531 OSStatus ortn;
532 impExpKeyUnwrapParams unwrapParams;
533
534 assert(cspHand != 0);
535 if(keyParams == NULL) {
536 return paramErr;
537 }
538 memset(&unwrapParams, 0, sizeof(unwrapParams));
539
540 /* derive unwrapping key */
541 CSSM_KEY unwrappingKey;
542
543 ortn = openSSHv1DeriveKey(cspHand, keyParams, VP_Import, &unwrappingKey);
544 if(ortn) {
545 return ortn;
546 }
547
548 /* set up key to unwrap */
549 CSSM_KEY wrappedKey;
550 CSSM_KEYHEADER &hdr = wrappedKey.KeyHeader;
551 memset(&wrappedKey, 0, sizeof(CSSM_KEY));
552 hdr.HeaderVersion = CSSM_KEYHEADER_VERSION;
553 /* CspId : don't care */
554 hdr.BlobType = CSSM_KEYBLOB_WRAPPED;
555 hdr.Format = CSSM_KEYBLOB_WRAPPED_FORMAT_OPENSSH1;
556 hdr.AlgorithmId = CSSM_ALGID_RSA; /* the oly algorithm supported in SSHv1 */
557 hdr.KeyClass = CSSM_KEYCLASS_PRIVATE_KEY;
558 /* LogicalKeySizeInBits : calculated by CSP during unwrap */
559 hdr.KeyAttr = CSSM_KEYATTR_EXTRACTABLE;
560 hdr.KeyUsage = CSSM_KEYUSE_ANY;
561
562 wrappedKey.KeyData.Data = (uint8 *)CFDataGetBytePtr(inData);
563 wrappedKey.KeyData.Length = CFDataGetLength(inData);
564
565 unwrapParams.unwrappingKey = &unwrappingKey;
566 unwrapParams.encrAlg = CSSM_ALGID_OPENSSH1;
567
568 /* GO */
569 ortn = impExpImportKeyCommon(&wrappedKey, importKeychain, cspHand,
570 flags, keyParams, &unwrapParams, printName, outArray);
571
572 if(unwrappingKey.KeyData.Data != NULL) {
573 CSSM_FreeKey(cspHand, NULL, &unwrappingKey, CSSM_FALSE);
574 }
575 return ortn;
576 }
577
578 OSStatus impExpWrappedOpenSSHExport(
579 SecKeyRef secKey,
580 SecItemImportExportFlags flags,
581 const SecKeyImportExportParameters *keyParams, // optional
582 const CssmData &descData,
583 CFMutableDataRef outData) // output appended here
584 {
585 CSSM_CSP_HANDLE cspdlHand = 0;
586 OSStatus ortn;
587 bool releaseCspHand = false;
588 CSSM_RETURN crtn;
589
590 if(keyParams == NULL) {
591 return paramErr;
592 }
593
594 /* we need a CSPDL handle - try to get it from the key */
595 ortn = SecKeyGetCSPHandle(secKey, &cspdlHand);
596 if(ortn) {
597 cspdlHand = cuCspStartup(CSSM_FALSE);
598 if(cspdlHand == 0) {
599 return CSSMERR_CSSM_ADDIN_LOAD_FAILED;
600 }
601 releaseCspHand = true;
602 }
603 /* subsequent errors to errOut: */
604
605 /* derive wrapping key */
606 CSSM_KEY wrappingKey;
607 crtn = openSSHv1DeriveKey(cspdlHand, keyParams, VP_Export, &wrappingKey);
608 if(crtn) {
609 goto errOut;
610 }
611
612 /* GO */
613 CSSM_KEY wrappedKey;
614 memset(&wrappedKey, 0, sizeof(CSSM_KEY));
615
616 crtn = impExpExportKeyCommon(cspdlHand, secKey, &wrappingKey, &wrappedKey,
617 CSSM_ALGID_OPENSSH1, CSSM_ALGMODE_NONE, CSSM_PADDING_NONE,
618 CSSM_KEYBLOB_WRAPPED_FORMAT_OPENSSH1, CSSM_ATTRIBUTE_NONE, CSSM_KEYBLOB_RAW_FORMAT_NONE,
619 &descData,
620 NULL); // IV
621 if(crtn) {
622 goto errOut;
623 }
624
625 /* the wrappedKey's KeyData is out output */
626 CFDataAppendBytes(outData, wrappedKey.KeyData.Data, wrappedKey.KeyData.Length);
627 CSSM_FreeKey(cspdlHand, NULL, &wrappedKey, CSSM_FALSE);
628
629 errOut:
630 if(releaseCspHand) {
631 cuCspDetachUnload(cspdlHand, CSSM_FALSE);
632 }
633 return crtn;
634
635 }