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