]> git.saurik.com Git - apple/security.git/blobdiff - Security/libsecurity_keychain/lib/SecImportExportOpenSSH.cpp
Security-57031.1.35.tar.gz
[apple/security.git] / Security / libsecurity_keychain / lib / SecImportExportOpenSSH.cpp
diff --git a/Security/libsecurity_keychain/lib/SecImportExportOpenSSH.cpp b/Security/libsecurity_keychain/lib/SecImportExportOpenSSH.cpp
new file mode 100644 (file)
index 0000000..d066c49
--- /dev/null
@@ -0,0 +1,633 @@
+/*
+ * Copyright (c) 2006,2011-2014 Apple Inc. All Rights Reserved.
+ * 
+ * @APPLE_LICENSE_HEADER_START@
+ * 
+ * This file contains Original Code and/or Modifications of Original Code
+ * as defined in and that are subject to the Apple Public Source License
+ * Version 2.0 (the 'License'). You may not use this file except in
+ * compliance with the License. Please obtain a copy of the License at
+ * http://www.opensource.apple.com/apsl/ and read it before using this
+ * file.
+ * 
+ * The Original Code and all software distributed under the License are
+ * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
+ * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
+ * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
+ * Please see the License for the specific language governing rights and
+ * limitations under the License.
+ * 
+ * @APPLE_LICENSE_HEADER_END@
+ */
+
+
+/*
+ * opensshCoding.cpp - Encoding and decoding of OpenSSH format public keys.
+ *
+ */
+
+#include "SecImportExportOpenSSH.h"
+#include "SecImportExportUtils.h"
+#include "SecImportExportCrypto.h"
+#include <ctype.h>
+#include <CommonCrypto/CommonDigest.h> /* for CC_MD5_DIGEST_LENGTH */
+#include <security_utilities/debugging.h>
+#include <security_cdsa_utils/cuCdsaUtils.h>
+
+#define SecSSHDbg(args...)     secdebug("openssh", ## args)
+
+#define SSHv2_PUB_KEY_NAME             "OpenSSHv2 Public Key"
+#define SSHv1_PUB_KEY_NAME             "OpenSSHv1 Public Key"
+#define SSHv1_PRIV_KEY_NAME            "OpenSSHv1 Private Key"
+
+#pragma mark --- Utility functions ---
+
+/* skip whitespace */
+static void skipWhite(
+       const unsigned char *&cp,
+       unsigned &bytesLeft)
+{
+       while(bytesLeft != 0) {
+               if(isspace((int)(*cp))) {
+                       cp++;
+                       bytesLeft--;
+               }
+               else {
+                       return;
+               }
+       }
+}
+
+/* find next whitespace or EOF - if EOF, rtn pointer points to one past EOF */
+static const unsigned char *findNextWhite(
+       const unsigned char *cp,
+       unsigned &bytesLeft)
+{
+       while(bytesLeft != 0) {
+               if(isspace((int)(*cp))) {
+                       return cp;
+               }
+               cp++;
+               bytesLeft--;
+       }
+       return cp;
+}
+
+/* obtain comment as the n'th whitespace-delimited field */
+static char *commentAsNthField(
+       const unsigned char *key,
+       unsigned keyLen,
+       unsigned n)
+
+{
+       unsigned dex;
+       
+       skipWhite(key, keyLen);
+       if(keyLen == 0) {
+               return NULL;
+       }
+       for(dex=0; dex<(n-1); dex++) {
+               key = findNextWhite(key, keyLen);
+               if(keyLen == 0) {
+                       return NULL;
+               }
+               skipWhite(key, keyLen);
+               if(keyLen == 0) {
+                       return NULL;
+               }
+       }
+       
+       /* cp points to start of nth field */
+       char *rtnStr = (char *)malloc(keyLen + 1);
+       memmove(rtnStr, key, keyLen);
+       if(rtnStr[keyLen - 1] == '\n') {
+               /* normal terminator - snip it off */
+               rtnStr[keyLen - 1] = '\0';
+       }
+       else {
+               rtnStr[keyLen] = '\0';
+       }
+       return rtnStr;
+
+}
+
+static uint32_t readUint32(
+       const unsigned char *&cp,               // IN/OUT
+       unsigned &len)                                  // IN/OUT 
+{
+       uint32_t r = 0;
+       
+       for(unsigned dex=0; dex<sizeof(uint32_t); dex++) {
+               r <<= 8;
+               r |= *cp++;
+       }
+       len -= 4;
+       return r;
+}
+
+static uint16_t readUint16(
+       const unsigned char *&cp,               // IN/OUT
+       unsigned &len)                                  // IN/OUT 
+{
+       uint16_t r = *cp++;
+       r <<= 8;
+       r |= *cp++;
+       len -= 2;
+       return r;
+}
+
+/* Skip over an SSHv1 private key formatted bignum */
+static void skipBigNum(
+       const unsigned char *&cp,               // IN/OUT
+       unsigned &len)                                  // IN/OUT 
+{
+       if(len < 2) {
+               cp += len;
+               len = 0;
+               return;
+       }
+       uint16 numBits = readUint16(cp, len);
+       unsigned numBytes = (numBits + 7) / 8;
+       if(numBytes > len) {
+               cp += len;
+               len = 0;
+               return;
+       }
+       cp += numBytes;
+       len -= numBytes;
+}
+
+static char *genPrintName(
+       const char *header,             // e.g. SSHv2_PUB_KEY_NAME
+       const char *comment)    // optional, from key 
+{
+       size_t totalLen = strlen(header) + 1;
+       if(comment) {
+               /* append ": <comment>" */
+               totalLen += strlen(comment);
+               totalLen += 2;
+       }
+       char *rtnStr = (char *)malloc(totalLen);
+       if(comment) {
+               snprintf(rtnStr, totalLen, "%s: %s", header, comment);
+       }
+       else {
+               strcpy(rtnStr, header);
+       }
+       return rtnStr;
+}
+
+#pragma mark --- Infer PrintName attribute from raw keys ---
+
+/* obtain comment from OpenSSHv2 public key */
+static char *opensshV2PubComment(
+       const unsigned char *key,
+       unsigned keyLen)
+{
+       /*
+        * The format here is
+        * header
+        * <space>
+        * keyblob
+        * <space>
+        * optional comment
+        * \n
+        */
+       char *comment = commentAsNthField(key, keyLen, 3);
+       char *rtnStr = genPrintName(SSHv2_PUB_KEY_NAME, comment);
+       if(comment) {
+               free(comment);
+       }
+       return rtnStr;
+}
+
+/* obtain comment from OpenSSHv1 public key */
+static char *opensshV1PubComment(
+       const unsigned char *key,
+       unsigned keyLen)
+{
+       /*
+        * Format:
+        * numbits
+        * <space>
+        * e (bignum in decimal)
+        * <space>
+        * n (bignum in decimal) 
+        * <space>
+        * optional comment 
+        * \n
+        */
+       char *comment = commentAsNthField(key, keyLen, 4);
+       char *rtnStr = genPrintName(SSHv1_PUB_KEY_NAME, comment);
+       if(comment) {
+               free(comment);
+       }
+       return rtnStr;
+}
+
+static const char *authfile_id_string = "SSH PRIVATE KEY FILE FORMAT 1.1\n";
+
+/* obtain comment from OpenSSHv1 private key, wrapped or clear */
+static char *opensshV1PrivComment(
+       const unsigned char *key,
+       unsigned keyLen)
+{
+       /* 
+        * Format:
+        * "SSH PRIVATE KEY FILE FORMAT 1.1\n"
+        * 1 byte cipherSpec
+        * 4 byte spares
+        * 4 bytes numBits
+        * bignum n
+        * bignum e
+        * 4 byte comment length 
+        * comment
+        * private key components, possibly encrypted 
+        *
+        * A bignum is encoded like so:
+        * 2 bytes numBits
+        * (numBits + 7)/8 bytes of data
+        */
+       /* length: ID string, NULL, Cipher, 4-byte spare */
+       size_t len = strlen(authfile_id_string);
+       if(keyLen < (len + 6)) {
+               return NULL;
+       }
+       if(memcmp(authfile_id_string, key, len)) {
+               return NULL;
+       }
+       key    += (len + 6);
+       keyLen -= (len + 6);
+       
+       /* key points to numBits */
+       if(keyLen < 4) {
+               return NULL;
+       }
+       key += 4;
+       keyLen -= 4;
+       
+       /* key points to n */
+       skipBigNum(key, keyLen);
+       if(keyLen == 0) {
+               return NULL;
+       }
+       skipBigNum(key, keyLen);
+       if(keyLen == 0) {
+               return NULL;
+       }
+       
+       char *comment = NULL;
+       uint32 commentLen = readUint32(key, keyLen);
+       if((commentLen != 0) && (commentLen <= keyLen)) {
+               comment = (char *)malloc(commentLen + 1);
+               memmove(comment, key, commentLen);
+               comment[commentLen] = '\0';
+       }
+       
+       char *rtnStr = genPrintName(SSHv1_PRIV_KEY_NAME, comment);
+       if(comment) {
+               free(comment);
+       }
+       return rtnStr;
+}
+
+/* 
+ * Infer PrintName attribute from raw key's 'comment' field. 
+ * Returned string is mallocd and must be freed by caller. 
+ */
+char *impExpOpensshInferPrintName(
+       CFDataRef external, 
+       SecExternalItemType externType, 
+       SecExternalFormat externFormat)
+{
+       const unsigned char *key = (const unsigned char *)CFDataGetBytePtr(external);
+       unsigned keyLen = (unsigned)CFDataGetLength(external);
+       switch(externType) {
+               case kSecItemTypePublicKey:
+                       switch(externFormat) {
+                               case kSecFormatSSH:
+                                       return opensshV1PubComment(key, keyLen);
+                               case kSecFormatSSHv2:
+                                       return opensshV2PubComment(key, keyLen);
+                               default:
+                                       /* impossible, right? */
+                                       break;
+                       }
+                       break;
+               case kSecItemTypePrivateKey:
+                       switch(externFormat) {
+                               case kSecFormatSSH:
+                               case kSecFormatWrappedSSH:
+                                       return opensshV1PrivComment(key, keyLen);
+                               default:
+                                       break;
+                       }
+                       break;
+               default:
+                       break;
+       }
+       return NULL;
+}
+
+#pragma mark --- Infer DescriptiveData from PrintName ---
+
+/* 
+ * Infer DescriptiveData (i.e., comment) from a SecKeyRef's PrintName
+ * attribute.
+ */
+void impExpOpensshInferDescData(
+       SecKeyRef keyRef,
+       CssmOwnedData &descData)
+{
+       OSStatus ortn;
+       SecKeychainAttributeInfo attrInfo;
+       SecKeychainAttrType     attrType = kSecKeyPrintName;
+       attrInfo.count = 1;
+       attrInfo.tag = &attrType;
+       attrInfo.format = NULL; 
+       SecKeychainAttributeList *attrList = NULL;
+       
+       ortn = SecKeychainItemCopyAttributesAndData(
+               (SecKeychainItemRef)keyRef, 
+               &attrInfo,
+               NULL,                   // itemClass
+               &attrList, 
+               NULL,                   // don't need the data
+               NULL);
+       if(ortn) {
+               SecSSHDbg("SecKeychainItemCopyAttributesAndData returned %ld", (unsigned long)ortn);
+               return;
+       }
+       /* subsequent errors to errOut: */
+       SecKeychainAttribute *attr = attrList->attr;
+               
+       /*
+        * On a previous import, we would have set this to something like 
+        * "OpenSSHv2 Public Key: comment".
+        * We want to strip off everything up to the actual comment.
+        */
+       unsigned toStrip = 0;   
+       
+       /* min length of attribute value for this code to be meaningful */
+       unsigned len = strlen(SSHv2_PUB_KEY_NAME) + 1;  
+       char *printNameStr = NULL;
+       if(len < attr->length) {
+               printNameStr = (char *)malloc(attr->length + 1);
+               memmove(printNameStr, attr->data, attr->length);
+               printNameStr[attr->length] = '\0';
+               if(strstr(printNameStr, SSHv2_PUB_KEY_NAME) == printNameStr) {
+                       toStrip = strlen(SSHv2_PUB_KEY_NAME);
+               }
+               else if(strstr(printNameStr, SSHv1_PUB_KEY_NAME) == printNameStr) {
+                       toStrip = strlen(SSHv1_PUB_KEY_NAME);
+               }
+               else if(strstr(printNameStr, SSHv1_PRIV_KEY_NAME) == printNameStr) {
+                       toStrip = strlen(SSHv1_PRIV_KEY_NAME);
+               }
+               if(toStrip) {
+                       /* only strip if we have ": " after toStrip bytes */
+                       if((printNameStr[toStrip] == ':') && (printNameStr[toStrip+1] == ' ')) {
+                               toStrip += 2;
+                       }
+               }
+       }
+       if(printNameStr) {
+               free(printNameStr);
+       }
+       len = attr->length;
+
+       unsigned char *attrVal;
+
+       if(len < toStrip) {
+               SecSSHDbg("impExpOpensshInferDescData: string parse screwup");
+               goto errOut;
+       }
+       if(len > toStrip) {
+               /* Normal case of stripping off leading header */
+               len -= toStrip;
+       }
+       else {
+               /* 
+                * If equal, then the attr value *is* "OpenSSHv2 Public Key: " with 
+                * no comment. Not sure how that could happen, but let's be careful.
+                */
+               toStrip = 0;
+       }
+       
+       attrVal = ((unsigned char *)attr->data) + toStrip;
+       descData.copy(attrVal, len);
+errOut:
+       SecKeychainItemFreeAttributesAndData(attrList, NULL);
+       return;
+}
+
+#pragma mark --- Derive SSHv1 wrap/unwrap key ---
+
+/* 
+ * Common code to derive a wrap/unwrap key for OpenSSHv1.
+ * Caller must CSSM_FreeKey when done.
+ */
+static CSSM_RETURN openSSHv1DeriveKey(
+       CSSM_CSP_HANDLE         cspHand,
+       const SecKeyImportExportParameters      *keyParams,             // required 
+       impExpVerifyPhrase  verifyPhrase,                                       // for secure passphrase
+       CSSM_KEY_PTR            symKey)                                                 // RETURNED
+{
+       CSSM_KEY                                        *passKey = NULL;
+       CFDataRef                                       cfPhrase = NULL;
+       CSSM_RETURN                                     crtn;
+       OSStatus                                        ortn;
+       CSSM_DATA                                       dummyLabel;
+       uint32                                          keyAttr;
+       CSSM_CC_HANDLE                          ccHand = 0;
+       CSSM_ACCESS_CREDENTIALS         creds;
+       CSSM_CRYPTO_DATA                        seed;
+       CSSM_DATA                                       nullParam = {0, NULL};
+       
+       memset(symKey, 0, sizeof(CSSM_KEY));
+       
+       /* passphrase or passkey? */
+       ortn = impExpPassphraseCommon(keyParams, cspHand, SPF_Data, verifyPhrase,
+               (CFTypeRef *)&cfPhrase, &passKey);
+       if(ortn) {
+               return ortn;
+       }
+       /* subsequent errors to errOut: */
+
+       memset(&seed, 0, sizeof(seed));
+       if(cfPhrase != NULL) {
+               /* TBD - caller-supplied empty passphrase means "export in the clear" */
+               size_t len = CFDataGetLength(cfPhrase);
+               seed.Param.Data = (uint8 *)malloc(len);
+               seed.Param.Length = len;
+               memmove(seed.Param.Data, CFDataGetBytePtr(cfPhrase), len);
+               CFRelease(cfPhrase);
+       }
+
+       memset(&creds, 0, sizeof(CSSM_ACCESS_CREDENTIALS));
+       crtn = CSSM_CSP_CreateDeriveKeyContext(cspHand,
+               CSSM_ALGID_OPENSSH1,
+               CSSM_ALGID_OPENSSH1,
+               CC_MD5_DIGEST_LENGTH * 8,
+               &creds,
+               passKey,                // BaseKey
+               0,                              // iterationCount
+               NULL,                   // salt
+               &seed,
+               &ccHand);
+       if(crtn) {
+               SecSSHDbg("openSSHv1DeriveKey CSSM_CSP_CreateDeriveKeyContext failure");
+               goto errOut;
+       }
+       
+       /* not extractable even for the short time this key lives */
+       keyAttr = CSSM_KEYATTR_RETURN_REF | CSSM_KEYATTR_SENSITIVE;
+       dummyLabel.Data = (uint8 *)"temp unwrap key";
+       dummyLabel.Length = strlen((char *)dummyLabel.Data);
+       
+       crtn = CSSM_DeriveKey(ccHand,
+               &nullParam,
+               CSSM_KEYUSE_ANY,
+               keyAttr,
+               &dummyLabel,
+               NULL,                   // cred and acl
+               symKey);
+       if(crtn) {
+               SecSSHDbg("openSSHv1DeriveKey CSSM_DeriveKey failure");
+       }
+errOut:
+       if(ccHand != 0) {
+               CSSM_DeleteContext(ccHand);
+       }
+       if(passKey != NULL) {
+               CSSM_FreeKey(cspHand, NULL, passKey, CSSM_FALSE);
+               free(passKey);
+       }
+       if(seed.Param.Data) {
+               memset(seed.Param.Data, 0, seed.Param.Length);
+               free(seed.Param.Data);
+       }
+       return crtn;
+}
+
+#pragma mark -- OpenSSHv1 Wrap/Unwrap ---
+
+/*
+ * If cspHand is provided instead of importKeychain, the CSP 
+ * handle MUST be for the CSPDL, not for the raw CSP.
+ */
+OSStatus impExpWrappedOpenSSHImport(
+       CFDataRef                                                       inData,
+       SecKeychainRef                                          importKeychain, // optional
+       CSSM_CSP_HANDLE                                         cspHand,                // required
+       SecItemImportExportFlags                        flags,
+       const SecKeyImportExportParameters      *keyParams,             // optional 
+       const char                                                      *printName,
+       CFMutableArrayRef                                       outArray)               // optional, append here 
+{
+       OSStatus                                ortn;
+       impExpKeyUnwrapParams   unwrapParams;
+
+       assert(cspHand != 0);
+       if(keyParams == NULL) {
+               return errSecParam;
+       }
+       memset(&unwrapParams, 0, sizeof(unwrapParams));
+
+       /* derive unwrapping key */
+       CSSM_KEY unwrappingKey;
+       
+       ortn = openSSHv1DeriveKey(cspHand, keyParams, VP_Import, &unwrappingKey);
+       if(ortn) {
+               return ortn;
+       }
+       
+       /* set up key to unwrap */
+       CSSM_KEY                                wrappedKey;
+       CSSM_KEYHEADER                  &hdr = wrappedKey.KeyHeader;
+       memset(&wrappedKey, 0, sizeof(CSSM_KEY));
+       hdr.HeaderVersion = CSSM_KEYHEADER_VERSION;
+       /* CspId : don't care */
+       hdr.BlobType = CSSM_KEYBLOB_WRAPPED;
+       hdr.Format = CSSM_KEYBLOB_WRAPPED_FORMAT_OPENSSH1;
+       hdr.AlgorithmId = CSSM_ALGID_RSA;               /* the oly algorithm supported in SSHv1 */
+       hdr.KeyClass = CSSM_KEYCLASS_PRIVATE_KEY;
+       /* LogicalKeySizeInBits : calculated by CSP during unwrap */
+       hdr.KeyAttr = CSSM_KEYATTR_EXTRACTABLE;
+       hdr.KeyUsage = CSSM_KEYUSE_ANY;
+
+       wrappedKey.KeyData.Data = (uint8 *)CFDataGetBytePtr(inData);
+       wrappedKey.KeyData.Length = CFDataGetLength(inData);
+       
+       unwrapParams.unwrappingKey = &unwrappingKey;
+       unwrapParams.encrAlg = CSSM_ALGID_OPENSSH1;
+       
+       /* GO */
+       ortn =  impExpImportKeyCommon(&wrappedKey, importKeychain, cspHand,
+               flags, keyParams, &unwrapParams, printName, outArray);
+               
+       if(unwrappingKey.KeyData.Data != NULL) {
+               CSSM_FreeKey(cspHand, NULL, &unwrappingKey, CSSM_FALSE);
+       }
+       return ortn;
+}
+
+OSStatus impExpWrappedOpenSSHExport(
+       SecKeyRef                                                       secKey,
+       SecItemImportExportFlags                        flags,          
+       const SecKeyImportExportParameters      *keyParams,             // optional 
+       const CssmData                                          &descData,
+       CFMutableDataRef                                        outData)                // output appended here
+{
+       CSSM_CSP_HANDLE cspdlHand = 0;
+       OSStatus ortn;
+       bool releaseCspHand = false;
+       CSSM_RETURN crtn;
+       
+       if(keyParams == NULL) {
+               return errSecParam;
+       }
+
+       /* we need a CSPDL handle - try to get it from the key */       
+       ortn = SecKeyGetCSPHandle(secKey, &cspdlHand);
+       if(ortn) {
+               cspdlHand = cuCspStartup(CSSM_FALSE);
+               if(cspdlHand == 0) {
+                       return CSSMERR_CSSM_ADDIN_LOAD_FAILED;
+               }
+               releaseCspHand = true;
+       }
+       /* subsequent errors to errOut: */
+
+       /* derive wrapping key */
+       CSSM_KEY wrappingKey;
+       crtn = openSSHv1DeriveKey(cspdlHand, keyParams, VP_Export, &wrappingKey);
+       if(crtn) {
+               goto errOut;
+       }
+       
+       /* GO */
+       CSSM_KEY wrappedKey;
+       memset(&wrappedKey, 0, sizeof(CSSM_KEY));
+       
+       crtn = impExpExportKeyCommon(cspdlHand, secKey, &wrappingKey, &wrappedKey,
+               CSSM_ALGID_OPENSSH1, CSSM_ALGMODE_NONE, CSSM_PADDING_NONE,
+               CSSM_KEYBLOB_WRAPPED_FORMAT_OPENSSH1, CSSM_ATTRIBUTE_NONE, CSSM_KEYBLOB_RAW_FORMAT_NONE, 
+               &descData,
+               NULL);  // IV
+       if(crtn) {
+               goto errOut;
+       }
+       
+       /* the wrappedKey's KeyData is out output */
+       CFDataAppendBytes(outData, wrappedKey.KeyData.Data, wrappedKey.KeyData.Length);
+       CSSM_FreeKey(cspdlHand, NULL, &wrappedKey, CSSM_FALSE);
+       
+errOut:
+       if(releaseCspHand) {
+               cuCspDetachUnload(cspdlHand, CSSM_FALSE);
+       }
+       return crtn;
+
+}