+++ /dev/null
-/*
- * 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;
-
-}