2 * Copyright (c) 2006 Apple Computer, Inc. All Rights Reserved.
4 * @APPLE_LICENSE_HEADER_START@
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
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.
21 * @APPLE_LICENSE_HEADER_END@
26 * opensshCoding.cpp - Encoding and decoding of OpenSSH format public keys.
28 * Created 8/29/2006 by dmitch.
31 #include "SecImportExportOpenSSH.h"
32 #include "SecImportExportUtils.h"
33 #include "SecImportExportCrypto.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>
40 #define SecSSHDbg(args...) secdebug("openssh", ## args)
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"
46 #pragma mark --- Utility functions ---
49 static void skipWhite(
50 const unsigned char *&cp
,
53 while(bytesLeft
!= 0) {
54 if(isspace((int)(*cp
))) {
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
,
69 while(bytesLeft
!= 0) {
70 if(isspace((int)(*cp
))) {
79 /* obtain comment as the n'th whitespace-delimited field */
80 static char *commentAsNthField(
81 const unsigned char *key
,
88 skipWhite(key
, keyLen
);
92 for(dex
=0; dex
<(n
-1); dex
++) {
93 key
= findNextWhite(key
, keyLen
);
97 skipWhite(key
, keyLen
);
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';
111 rtnStr
[keyLen
] = '\0';
117 static uint32_t readUint32(
118 const unsigned char *&cp
, // IN/OUT
119 unsigned &len
) // IN/OUT
123 for(unsigned dex
=0; dex
<sizeof(uint32_t); dex
++) {
131 static uint16_t readUint16(
132 const unsigned char *&cp
, // IN/OUT
133 unsigned &len
) // IN/OUT
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
152 uint16 numBits
= readUint16(cp
, len
);
153 unsigned numBytes
= (numBits
+ 7) / 8;
163 static char *genPrintName(
164 const char *header
, // e.g. SSHv2_PUB_KEY_NAME
165 const char *comment
) // optional, from key
167 unsigned totalLen
= strlen(header
) + 1;
169 /* append ": <comment>" */
170 totalLen
+= strlen(comment
);
173 char *rtnStr
= (char *)malloc(totalLen
);
175 snprintf(rtnStr
, totalLen
, "%s: %s", header
, comment
);
178 strcpy(rtnStr
, header
);
183 #pragma mark --- Infer PrintName attribute from raw keys ---
185 /* obtain comment from OpenSSHv2 public key */
186 static char *opensshV2PubComment(
187 const unsigned char *key
,
199 char *comment
= commentAsNthField(key
, keyLen
, 3);
200 char *rtnStr
= genPrintName(SSHv2_PUB_KEY_NAME
, comment
);
207 /* obtain comment from OpenSSHv1 public key */
208 static char *opensshV1PubComment(
209 const unsigned char *key
,
216 * e (bignum in decimal)
218 * n (bignum in decimal)
223 char *comment
= commentAsNthField(key
, keyLen
, 4);
224 char *rtnStr
= genPrintName(SSHv1_PUB_KEY_NAME
, comment
);
231 static const char *authfile_id_string
= "SSH PRIVATE KEY FILE FORMAT 1.1\n";
233 /* obtain comment from OpenSSHv1 private key, wrapped or clear */
234 static char *opensshV1PrivComment(
235 const unsigned char *key
,
240 * "SSH PRIVATE KEY FILE FORMAT 1.1\n"
246 * 4 byte comment length
248 * private key components, possibly encrypted
250 * A bignum is encoded like so:
252 * (numBits + 7)/8 bytes of data
254 /* length: ID string, NULL, Cipher, 4-byte spare */
255 unsigned len
= strlen(authfile_id_string
);
256 if(keyLen
< (len
+ 6)) {
259 if(memcmp(authfile_id_string
, key
, len
)) {
265 /* key points to numBits */
272 /* key points to n */
273 skipBigNum(key
, keyLen
);
277 skipBigNum(key
, keyLen
);
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';
290 char *rtnStr
= genPrintName(SSHv1_PRIV_KEY_NAME
, comment
);
298 * Infer PrintName attribute from raw key's 'comment' field.
299 * Returned string is mallocd and must be freed by caller.
301 char *impExpOpensshInferPrintName(
303 SecExternalItemType externType
,
304 SecExternalFormat externFormat
)
306 const unsigned char *key
= (const unsigned char *)CFDataGetBytePtr(external
);
307 unsigned keyLen
= CFDataGetLength(external
);
309 case kSecItemTypePublicKey
:
310 switch(externFormat
) {
312 return opensshV1PubComment(key
, keyLen
);
313 case kSecFormatSSHv2
:
314 return opensshV2PubComment(key
, keyLen
);
316 /* impossible, right? */
320 case kSecItemTypePrivateKey
:
321 switch(externFormat
) {
323 case kSecFormatWrappedSSH
:
324 return opensshV1PrivComment(key
, keyLen
);
335 #pragma mark --- Infer DescriptiveData from PrintName ---
338 * Infer DescriptiveData (i.e., comment) from a SecKeyRef's PrintName
341 void impExpOpensshInferDescData(
343 CssmOwnedData
&descData
)
346 SecKeychainAttributeInfo attrInfo
;
347 SecKeychainAttrType attrType
= kSecKeyPrintName
;
349 attrInfo
.tag
= &attrType
;
350 attrInfo
.format
= NULL
;
351 SecKeychainAttributeList
*attrList
= NULL
;
353 ortn
= SecKeychainItemCopyAttributesAndData(
354 (SecKeychainItemRef
)keyRef
,
358 NULL
, // don't need the data
361 SecSSHDbg("SecKeychainItemCopyAttributesAndData returned %ld", ortn
);
364 /* subsequent errors to errOut: */
365 SecKeychainAttribute
*attr
= attrList
->attr
;
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.
372 unsigned toStrip
= 0;
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
);
384 else if(strstr(printNameStr
, SSHv1_PUB_KEY_NAME
) == printNameStr
) {
385 toStrip
= strlen(SSHv1_PUB_KEY_NAME
);
387 else if(strstr(printNameStr
, SSHv1_PRIV_KEY_NAME
) == printNameStr
) {
388 toStrip
= strlen(SSHv1_PRIV_KEY_NAME
);
391 /* only strip if we have ": " after toStrip bytes */
392 if((printNameStr
[toStrip
] == ':') && (printNameStr
[toStrip
+1] == ' ')) {
402 unsigned char *attrVal
;
405 SecSSHDbg("impExpOpensshInferDescData: string parse screwup");
409 /* Normal case of stripping off leading header */
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.
420 attrVal
= ((unsigned char *)attr
->data
) + toStrip
;
421 descData
.copy(attrVal
, len
);
423 SecKeychainItemFreeAttributesAndData(attrList
, NULL
);
427 #pragma mark --- Derive SSHv1 wrap/unwrap key ---
430 * Common code to derive a wrap/unwrap key for OpenSSHv1.
431 * Caller must CSSM_FreeKey when done.
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
439 CSSM_KEY
*passKey
= NULL
;
440 CFDataRef cfPhrase
= NULL
;
443 CSSM_DATA dummyLabel
;
445 CSSM_CC_HANDLE ccHand
= 0;
446 CSSM_ACCESS_CREDENTIALS creds
;
447 CSSM_CRYPTO_DATA seed
;
448 CSSM_DATA nullParam
= {0, NULL
};
450 memset(symKey
, 0, sizeof(CSSM_KEY
));
452 /* passphrase or passkey? */
453 ortn
= impExpPassphraseCommon(keyParams
, cspHand
, SPF_Data
, verifyPhrase
,
454 (CFTypeRef
*)&cfPhrase
, &passKey
);
458 /* subsequent errors to errOut: */
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
);
470 memset(&creds
, 0, sizeof(CSSM_ACCESS_CREDENTIALS
));
471 crtn
= CSSM_CSP_CreateDeriveKeyContext(cspHand
,
474 CC_MD5_DIGEST_LENGTH
* 8,
482 SecSSHDbg("openSSHv1DeriveKey CSSM_CSP_CreateDeriveKeyContext failure");
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
);
491 crtn
= CSSM_DeriveKey(ccHand
,
496 NULL
, // cred and acl
499 SecSSHDbg("openSSHv1DeriveKey CSSM_DeriveKey failure");
503 CSSM_DeleteContext(ccHand
);
505 if(passKey
!= NULL
) {
506 CSSM_FreeKey(cspHand
, NULL
, passKey
, CSSM_FALSE
);
509 if(seed
.Param
.Data
) {
510 memset(seed
.Param
.Data
, 0, seed
.Param
.Length
);
511 free(seed
.Param
.Data
);
516 #pragma mark -- OpenSSHv1 Wrap/Unwrap ---
519 * If cspHand is provided instead of importKeychain, the CSP
520 * handle MUST be for the CSPDL, not for the raw CSP.
522 OSStatus
impExpWrappedOpenSSHImport(
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
532 impExpKeyUnwrapParams unwrapParams
;
534 assert(cspHand
!= 0);
535 if(keyParams
== NULL
) {
538 memset(&unwrapParams
, 0, sizeof(unwrapParams
));
540 /* derive unwrapping key */
541 CSSM_KEY unwrappingKey
;
543 ortn
= openSSHv1DeriveKey(cspHand
, keyParams
, VP_Import
, &unwrappingKey
);
548 /* set up key to unwrap */
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
;
562 wrappedKey
.KeyData
.Data
= (uint8
*)CFDataGetBytePtr(inData
);
563 wrappedKey
.KeyData
.Length
= CFDataGetLength(inData
);
565 unwrapParams
.unwrappingKey
= &unwrappingKey
;
566 unwrapParams
.encrAlg
= CSSM_ALGID_OPENSSH1
;
569 ortn
= impExpImportKeyCommon(&wrappedKey
, importKeychain
, cspHand
,
570 flags
, keyParams
, &unwrapParams
, printName
, outArray
);
572 if(unwrappingKey
.KeyData
.Data
!= NULL
) {
573 CSSM_FreeKey(cspHand
, NULL
, &unwrappingKey
, CSSM_FALSE
);
578 OSStatus
impExpWrappedOpenSSHExport(
580 SecItemImportExportFlags flags
,
581 const SecKeyImportExportParameters
*keyParams
, // optional
582 const CssmData
&descData
,
583 CFMutableDataRef outData
) // output appended here
585 CSSM_CSP_HANDLE cspdlHand
= 0;
587 bool releaseCspHand
= false;
590 if(keyParams
== NULL
) {
594 /* we need a CSPDL handle - try to get it from the key */
595 ortn
= SecKeyGetCSPHandle(secKey
, &cspdlHand
);
597 cspdlHand
= cuCspStartup(CSSM_FALSE
);
599 return CSSMERR_CSSM_ADDIN_LOAD_FAILED
;
601 releaseCspHand
= true;
603 /* subsequent errors to errOut: */
605 /* derive wrapping key */
606 CSSM_KEY wrappingKey
;
607 crtn
= openSSHv1DeriveKey(cspdlHand
, keyParams
, VP_Export
, &wrappingKey
);
614 memset(&wrappedKey
, 0, sizeof(CSSM_KEY
));
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
,
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
);
631 cuCspDetachUnload(cspdlHand
, CSSM_FALSE
);