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 <security_cdsa_utils/cuCdsaUtils.h>
39 #define SecSSHDbg(args...) secdebug("openssh", ## args)
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"
45 #pragma mark --- Utility functions ---
48 static void skipWhite(
49 const unsigned char *&cp
,
52 while(bytesLeft
!= 0) {
53 if(isspace((int)(*cp
))) {
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
,
68 while(bytesLeft
!= 0) {
69 if(isspace((int)(*cp
))) {
78 /* obtain comment as the n'th whitespace-delimited field */
79 static char *commentAsNthField(
80 const unsigned char *key
,
87 skipWhite(key
, keyLen
);
91 for(dex
=0; dex
<(n
-1); dex
++) {
92 key
= findNextWhite(key
, keyLen
);
96 skipWhite(key
, keyLen
);
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';
110 rtnStr
[keyLen
] = '\0';
116 static uint32_t readUint32(
117 const unsigned char *&cp
, // IN/OUT
118 unsigned &len
) // IN/OUT
122 for(unsigned dex
=0; dex
<sizeof(uint32_t); dex
++) {
130 static uint16_t readUint16(
131 const unsigned char *&cp
, // IN/OUT
132 unsigned &len
) // IN/OUT
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
151 uint16 numBits
= readUint16(cp
, len
);
152 unsigned numBytes
= (numBits
+ 7) / 8;
162 static char *genPrintName(
163 const char *header
, // e.g. SSHv2_PUB_KEY_NAME
164 const char *comment
) // optional, from key
166 size_t totalLen
= strlen(header
) + 1;
168 /* append ": <comment>" */
169 totalLen
+= strlen(comment
);
172 char *rtnStr
= (char *)malloc(totalLen
);
174 snprintf(rtnStr
, totalLen
, "%s: %s", header
, comment
);
177 strcpy(rtnStr
, header
);
182 #pragma mark --- Infer PrintName attribute from raw keys ---
184 /* obtain comment from OpenSSHv2 public key */
185 static char *opensshV2PubComment(
186 const unsigned char *key
,
198 char *comment
= commentAsNthField(key
, keyLen
, 3);
199 char *rtnStr
= genPrintName(SSHv2_PUB_KEY_NAME
, comment
);
206 /* obtain comment from OpenSSHv1 public key */
207 static char *opensshV1PubComment(
208 const unsigned char *key
,
215 * e (bignum in decimal)
217 * n (bignum in decimal)
222 char *comment
= commentAsNthField(key
, keyLen
, 4);
223 char *rtnStr
= genPrintName(SSHv1_PUB_KEY_NAME
, comment
);
230 static const char *authfile_id_string
= "SSH PRIVATE KEY FILE FORMAT 1.1\n";
232 /* obtain comment from OpenSSHv1 private key, wrapped or clear */
233 static char *opensshV1PrivComment(
234 const unsigned char *key
,
239 * "SSH PRIVATE KEY FILE FORMAT 1.1\n"
245 * 4 byte comment length
247 * private key components, possibly encrypted
249 * A bignum is encoded like so:
251 * (numBits + 7)/8 bytes of data
253 /* length: ID string, NULL, Cipher, 4-byte spare */
254 size_t len
= strlen(authfile_id_string
);
255 if(keyLen
< (len
+ 6)) {
258 if(memcmp(authfile_id_string
, key
, len
)) {
264 /* key points to numBits */
271 /* key points to n */
272 skipBigNum(key
, keyLen
);
276 skipBigNum(key
, keyLen
);
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';
289 char *rtnStr
= genPrintName(SSHv1_PRIV_KEY_NAME
, comment
);
297 * Infer PrintName attribute from raw key's 'comment' field.
298 * Returned string is mallocd and must be freed by caller.
300 char *impExpOpensshInferPrintName(
302 SecExternalItemType externType
,
303 SecExternalFormat externFormat
)
305 const unsigned char *key
= (const unsigned char *)CFDataGetBytePtr(external
);
306 unsigned keyLen
= (unsigned)CFDataGetLength(external
);
308 case kSecItemTypePublicKey
:
309 switch(externFormat
) {
311 return opensshV1PubComment(key
, keyLen
);
312 case kSecFormatSSHv2
:
313 return opensshV2PubComment(key
, keyLen
);
315 /* impossible, right? */
319 case kSecItemTypePrivateKey
:
320 switch(externFormat
) {
322 case kSecFormatWrappedSSH
:
323 return opensshV1PrivComment(key
, keyLen
);
334 #pragma mark --- Infer DescriptiveData from PrintName ---
337 * Infer DescriptiveData (i.e., comment) from a SecKeyRef's PrintName
340 void impExpOpensshInferDescData(
342 CssmOwnedData
&descData
)
345 SecKeychainAttributeInfo attrInfo
;
346 SecKeychainAttrType attrType
= kSecKeyPrintName
;
348 attrInfo
.tag
= &attrType
;
349 attrInfo
.format
= NULL
;
350 SecKeychainAttributeList
*attrList
= NULL
;
352 ortn
= SecKeychainItemCopyAttributesAndData(
353 (SecKeychainItemRef
)keyRef
,
357 NULL
, // don't need the data
360 SecSSHDbg("SecKeychainItemCopyAttributesAndData returned %ld", (unsigned long)ortn
);
363 /* subsequent errors to errOut: */
364 SecKeychainAttribute
*attr
= attrList
->attr
;
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.
371 unsigned toStrip
= 0;
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
);
383 else if(strstr(printNameStr
, SSHv1_PUB_KEY_NAME
) == printNameStr
) {
384 toStrip
= strlen(SSHv1_PUB_KEY_NAME
);
386 else if(strstr(printNameStr
, SSHv1_PRIV_KEY_NAME
) == printNameStr
) {
387 toStrip
= strlen(SSHv1_PRIV_KEY_NAME
);
390 /* only strip if we have ": " after toStrip bytes */
391 if((printNameStr
[toStrip
] == ':') && (printNameStr
[toStrip
+1] == ' ')) {
401 unsigned char *attrVal
;
404 SecSSHDbg("impExpOpensshInferDescData: string parse screwup");
408 /* Normal case of stripping off leading header */
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.
419 attrVal
= ((unsigned char *)attr
->data
) + toStrip
;
420 descData
.copy(attrVal
, len
);
422 SecKeychainItemFreeAttributesAndData(attrList
, NULL
);
426 #pragma mark --- Derive SSHv1 wrap/unwrap key ---
429 * Common code to derive a wrap/unwrap key for OpenSSHv1.
430 * Caller must CSSM_FreeKey when done.
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
438 CSSM_KEY
*passKey
= NULL
;
439 CFDataRef cfPhrase
= NULL
;
442 CSSM_DATA dummyLabel
;
444 CSSM_CC_HANDLE ccHand
= 0;
445 CSSM_ACCESS_CREDENTIALS creds
;
446 CSSM_CRYPTO_DATA seed
;
447 CSSM_DATA nullParam
= {0, NULL
};
449 memset(symKey
, 0, sizeof(CSSM_KEY
));
451 /* passphrase or passkey? */
452 ortn
= impExpPassphraseCommon(keyParams
, cspHand
, SPF_Data
, verifyPhrase
,
453 (CFTypeRef
*)&cfPhrase
, &passKey
);
457 /* subsequent errors to errOut: */
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
);
469 memset(&creds
, 0, sizeof(CSSM_ACCESS_CREDENTIALS
));
470 crtn
= CSSM_CSP_CreateDeriveKeyContext(cspHand
,
473 CC_MD5_DIGEST_LENGTH
* 8,
481 SecSSHDbg("openSSHv1DeriveKey CSSM_CSP_CreateDeriveKeyContext failure");
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
);
490 crtn
= CSSM_DeriveKey(ccHand
,
495 NULL
, // cred and acl
498 SecSSHDbg("openSSHv1DeriveKey CSSM_DeriveKey failure");
502 CSSM_DeleteContext(ccHand
);
504 if(passKey
!= NULL
) {
505 CSSM_FreeKey(cspHand
, NULL
, passKey
, CSSM_FALSE
);
508 if(seed
.Param
.Data
) {
509 memset(seed
.Param
.Data
, 0, seed
.Param
.Length
);
510 free(seed
.Param
.Data
);
515 #pragma mark -- OpenSSHv1 Wrap/Unwrap ---
518 * If cspHand is provided instead of importKeychain, the CSP
519 * handle MUST be for the CSPDL, not for the raw CSP.
521 OSStatus
impExpWrappedOpenSSHImport(
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
531 impExpKeyUnwrapParams unwrapParams
;
533 assert(cspHand
!= 0);
534 if(keyParams
== NULL
) {
537 memset(&unwrapParams
, 0, sizeof(unwrapParams
));
539 /* derive unwrapping key */
540 CSSM_KEY unwrappingKey
;
542 ortn
= openSSHv1DeriveKey(cspHand
, keyParams
, VP_Import
, &unwrappingKey
);
547 /* set up key to unwrap */
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
;
561 wrappedKey
.KeyData
.Data
= (uint8
*)CFDataGetBytePtr(inData
);
562 wrappedKey
.KeyData
.Length
= CFDataGetLength(inData
);
564 unwrapParams
.unwrappingKey
= &unwrappingKey
;
565 unwrapParams
.encrAlg
= CSSM_ALGID_OPENSSH1
;
568 ortn
= impExpImportKeyCommon(&wrappedKey
, importKeychain
, cspHand
,
569 flags
, keyParams
, &unwrapParams
, printName
, outArray
);
571 if(unwrappingKey
.KeyData
.Data
!= NULL
) {
572 CSSM_FreeKey(cspHand
, NULL
, &unwrappingKey
, CSSM_FALSE
);
577 OSStatus
impExpWrappedOpenSSHExport(
579 SecItemImportExportFlags flags
,
580 const SecKeyImportExportParameters
*keyParams
, // optional
581 const CssmData
&descData
,
582 CFMutableDataRef outData
) // output appended here
584 CSSM_CSP_HANDLE cspdlHand
= 0;
586 bool releaseCspHand
= false;
589 if(keyParams
== NULL
) {
593 /* we need a CSPDL handle - try to get it from the key */
594 ortn
= SecKeyGetCSPHandle(secKey
, &cspdlHand
);
596 cspdlHand
= cuCspStartup(CSSM_FALSE
);
598 return CSSMERR_CSSM_ADDIN_LOAD_FAILED
;
600 releaseCspHand
= true;
602 /* subsequent errors to errOut: */
604 /* derive wrapping key */
605 CSSM_KEY wrappingKey
;
606 crtn
= openSSHv1DeriveKey(cspdlHand
, keyParams
, VP_Export
, &wrappingKey
);
613 memset(&wrappedKey
, 0, sizeof(CSSM_KEY
));
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
,
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
);
630 cuCspDetachUnload(cspdlHand
, CSSM_FALSE
);