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