2 * Copyright (c) 2006,2011-2014 Apple 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.
30 #include "SecImportExportOpenSSH.h"
31 #include "SecImportExportUtils.h"
32 #include "SecImportExportCrypto.h"
34 #include <CommonCrypto/CommonDigest.h> /* for CC_MD5_DIGEST_LENGTH */
35 #include <security_utilities/debugging.h>
36 #include <security_cdsa_utils/cuCdsaUtils.h>
38 #define SecSSHDbg(args...) secinfo("openssh", ## args)
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"
44 #pragma mark --- Utility functions ---
47 static void skipWhite(
48 const unsigned char *&cp
,
51 while(bytesLeft
!= 0) {
52 if(isspace((int)(*cp
))) {
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
,
67 while(bytesLeft
!= 0) {
68 if(isspace((int)(*cp
))) {
77 /* obtain comment as the n'th whitespace-delimited field */
78 static char *commentAsNthField(
79 const unsigned char *key
,
86 skipWhite(key
, keyLen
);
90 for(dex
=0; dex
<(n
-1); dex
++) {
91 key
= findNextWhite(key
, keyLen
);
95 skipWhite(key
, keyLen
);
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';
109 rtnStr
[keyLen
] = '\0';
115 static uint32_t readUint32(
116 const unsigned char *&cp
, // IN/OUT
117 unsigned &len
) // IN/OUT
121 for(unsigned dex
=0; dex
<sizeof(uint32_t); dex
++) {
129 static uint16_t readUint16(
130 const unsigned char *&cp
, // IN/OUT
131 unsigned &len
) // IN/OUT
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
150 uint16 numBits
= readUint16(cp
, len
);
151 unsigned numBytes
= (numBits
+ 7) / 8;
161 static char *genPrintName(
162 const char *header
, // e.g. SSHv2_PUB_KEY_NAME
163 const char *comment
) // optional, from key
165 size_t totalLen
= strlen(header
) + 1;
167 /* append ": <comment>" */
168 totalLen
+= strlen(comment
);
171 char *rtnStr
= (char *)malloc(totalLen
);
173 snprintf(rtnStr
, totalLen
, "%s: %s", header
, comment
);
176 strcpy(rtnStr
, header
);
181 #pragma mark --- Infer PrintName attribute from raw keys ---
183 /* obtain comment from OpenSSHv2 public key */
184 static char *opensshV2PubComment(
185 const unsigned char *key
,
197 char *comment
= commentAsNthField(key
, keyLen
, 3);
198 char *rtnStr
= genPrintName(SSHv2_PUB_KEY_NAME
, comment
);
205 /* obtain comment from OpenSSHv1 public key */
206 static char *opensshV1PubComment(
207 const unsigned char *key
,
214 * e (bignum in decimal)
216 * n (bignum in decimal)
221 char *comment
= commentAsNthField(key
, keyLen
, 4);
222 char *rtnStr
= genPrintName(SSHv1_PUB_KEY_NAME
, comment
);
229 static const char *authfile_id_string
= "SSH PRIVATE KEY FILE FORMAT 1.1\n";
231 /* obtain comment from OpenSSHv1 private key, wrapped or clear */
232 static char *opensshV1PrivComment(
233 const unsigned char *key
,
238 * "SSH PRIVATE KEY FILE FORMAT 1.1\n"
244 * 4 byte comment length
246 * private key components, possibly encrypted
248 * A bignum is encoded like so:
250 * (numBits + 7)/8 bytes of data
252 /* length: ID string, NULL, Cipher, 4-byte spare */
253 size_t len
= strlen(authfile_id_string
);
254 if(keyLen
< (len
+ 6)) {
257 if(memcmp(authfile_id_string
, key
, len
)) {
263 /* key points to numBits */
270 /* key points to n */
271 skipBigNum(key
, keyLen
);
275 skipBigNum(key
, keyLen
);
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';
288 char *rtnStr
= genPrintName(SSHv1_PRIV_KEY_NAME
, comment
);
296 * Infer PrintName attribute from raw key's 'comment' field.
297 * Returned string is mallocd and must be freed by caller.
299 char *impExpOpensshInferPrintName(
301 SecExternalItemType externType
,
302 SecExternalFormat externFormat
)
304 const unsigned char *key
= (const unsigned char *)CFDataGetBytePtr(external
);
305 unsigned keyLen
= (unsigned)CFDataGetLength(external
);
307 case kSecItemTypePublicKey
:
308 switch(externFormat
) {
310 return opensshV1PubComment(key
, keyLen
);
311 case kSecFormatSSHv2
:
312 return opensshV2PubComment(key
, keyLen
);
314 /* impossible, right? */
318 case kSecItemTypePrivateKey
:
319 switch(externFormat
) {
321 case kSecFormatWrappedSSH
:
322 return opensshV1PrivComment(key
, keyLen
);
333 #pragma mark --- Infer DescriptiveData from PrintName ---
336 * Infer DescriptiveData (i.e., comment) from a SecKeyRef's PrintName
339 void impExpOpensshInferDescData(
341 CssmOwnedData
&descData
)
344 SecKeychainAttributeInfo attrInfo
;
345 SecKeychainAttrType attrType
= kSecKeyPrintName
;
347 attrInfo
.tag
= &attrType
;
348 attrInfo
.format
= NULL
;
349 SecKeychainAttributeList
*attrList
= NULL
;
351 ortn
= SecKeychainItemCopyAttributesAndData(
352 (SecKeychainItemRef
)keyRef
,
356 NULL
, // don't need the data
359 SecSSHDbg("SecKeychainItemCopyAttributesAndData returned %ld", (unsigned long)ortn
);
362 /* subsequent errors to errOut: */
363 SecKeychainAttribute
*attr
= attrList
->attr
;
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.
370 unsigned toStrip
= 0;
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
);
382 else if(strstr(printNameStr
, SSHv1_PUB_KEY_NAME
) == printNameStr
) {
383 toStrip
= strlen(SSHv1_PUB_KEY_NAME
);
385 else if(strstr(printNameStr
, SSHv1_PRIV_KEY_NAME
) == printNameStr
) {
386 toStrip
= strlen(SSHv1_PRIV_KEY_NAME
);
389 /* only strip if we have ": " after toStrip bytes */
390 if((printNameStr
[toStrip
] == ':') && (printNameStr
[toStrip
+1] == ' ')) {
400 unsigned char *attrVal
;
403 SecSSHDbg("impExpOpensshInferDescData: string parse screwup");
407 /* Normal case of stripping off leading header */
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.
418 attrVal
= ((unsigned char *)attr
->data
) + toStrip
;
419 descData
.copy(attrVal
, len
);
421 SecKeychainItemFreeAttributesAndData(attrList
, NULL
);
425 #pragma mark --- Derive SSHv1 wrap/unwrap key ---
428 * Common code to derive a wrap/unwrap key for OpenSSHv1.
429 * Caller must CSSM_FreeKey when done.
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
437 CSSM_KEY
*passKey
= NULL
;
438 CFDataRef cfPhrase
= NULL
;
441 CSSM_DATA dummyLabel
;
443 CSSM_CC_HANDLE ccHand
= 0;
444 CSSM_ACCESS_CREDENTIALS creds
;
445 CSSM_CRYPTO_DATA seed
;
446 CSSM_DATA nullParam
= {0, NULL
};
448 memset(symKey
, 0, sizeof(CSSM_KEY
));
450 /* passphrase or passkey? */
451 ortn
= impExpPassphraseCommon(keyParams
, cspHand
, SPF_Data
, verifyPhrase
,
452 (CFTypeRef
*)&cfPhrase
, &passKey
);
456 /* subsequent errors to errOut: */
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
);
468 memset(&creds
, 0, sizeof(CSSM_ACCESS_CREDENTIALS
));
469 crtn
= CSSM_CSP_CreateDeriveKeyContext(cspHand
,
472 CC_MD5_DIGEST_LENGTH
* 8,
480 SecSSHDbg("openSSHv1DeriveKey CSSM_CSP_CreateDeriveKeyContext failure");
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
);
489 crtn
= CSSM_DeriveKey(ccHand
,
494 NULL
, // cred and acl
497 SecSSHDbg("openSSHv1DeriveKey CSSM_DeriveKey failure");
501 CSSM_DeleteContext(ccHand
);
503 if(passKey
!= NULL
) {
504 CSSM_FreeKey(cspHand
, NULL
, passKey
, CSSM_FALSE
);
507 if(seed
.Param
.Data
) {
508 memset(seed
.Param
.Data
, 0, seed
.Param
.Length
);
509 free(seed
.Param
.Data
);
514 #pragma mark -- OpenSSHv1 Wrap/Unwrap ---
517 * If cspHand is provided instead of importKeychain, the CSP
518 * handle MUST be for the CSPDL, not for the raw CSP.
520 OSStatus
impExpWrappedOpenSSHImport(
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
530 impExpKeyUnwrapParams unwrapParams
;
532 assert(cspHand
!= 0);
533 if(keyParams
== NULL
) {
536 memset(&unwrapParams
, 0, sizeof(unwrapParams
));
538 /* derive unwrapping key */
539 CSSM_KEY unwrappingKey
;
541 ortn
= openSSHv1DeriveKey(cspHand
, keyParams
, VP_Import
, &unwrappingKey
);
546 /* set up key to unwrap */
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
;
560 wrappedKey
.KeyData
.Data
= (uint8
*)CFDataGetBytePtr(inData
);
561 wrappedKey
.KeyData
.Length
= CFDataGetLength(inData
);
563 unwrapParams
.unwrappingKey
= &unwrappingKey
;
564 unwrapParams
.encrAlg
= CSSM_ALGID_OPENSSH1
;
567 ortn
= impExpImportKeyCommon(&wrappedKey
, importKeychain
, cspHand
,
568 flags
, keyParams
, &unwrapParams
, printName
, outArray
);
570 if(unwrappingKey
.KeyData
.Data
!= NULL
) {
571 CSSM_FreeKey(cspHand
, NULL
, &unwrappingKey
, CSSM_FALSE
);
576 OSStatus
impExpWrappedOpenSSHExport(
578 SecItemImportExportFlags flags
,
579 const SecKeyImportExportParameters
*keyParams
, // optional
580 const CssmData
&descData
,
581 CFMutableDataRef outData
) // output appended here
583 CSSM_CSP_HANDLE cspdlHand
= 0;
585 bool releaseCspHand
= false;
588 if(keyParams
== NULL
) {
592 /* we need a CSPDL handle - try to get it from the key */
593 ortn
= SecKeyGetCSPHandle(secKey
, &cspdlHand
);
595 cspdlHand
= cuCspStartup(CSSM_FALSE
);
597 return CSSMERR_CSSM_ADDIN_LOAD_FAILED
;
599 releaseCspHand
= true;
601 /* subsequent errors to errOut: */
603 /* derive wrapping key */
604 CSSM_KEY wrappingKey
;
605 crtn
= openSSHv1DeriveKey(cspdlHand
, keyParams
, VP_Export
, &wrappingKey
);
612 memset(&wrappedKey
, 0, sizeof(CSSM_KEY
));
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
,
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
);
629 cuCspDetachUnload(cspdlHand
, CSSM_FALSE
);