X-Git-Url: https://git.saurik.com/apple/security.git/blobdiff_plain/72a12576750f52947eb043106ba5c12c0d07decf..b1ab9ed8d0e0f1c3b66d7daa8fd5564444c56195:/libsecurity_apple_csp/lib/opensshCoding.cpp diff --git a/libsecurity_apple_csp/lib/opensshCoding.cpp b/libsecurity_apple_csp/lib/opensshCoding.cpp new file mode 100644 index 00000000..15bc177e --- /dev/null +++ b/libsecurity_apple_csp/lib/opensshCoding.cpp @@ -0,0 +1,683 @@ +/* + * Copyright (c) 2006 Apple Computer, 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. + * + * Created 8/29/2006 by dmitch. + */ + +#include "opensshCoding.h" +#include +#include +#include +#include + +#define SSH2_RSA_HEADER "ssh-rsa" +#define SSH2_DSA_HEADER "ssh-dss" + +#ifndef NDEBUG +#include +#define dprintf(s...) printf(s) +#else +#define dprintf(...) +#endif + +#pragma mark --- commmon code --- + +uint32_t readUint32( + const unsigned char *&cp, // IN/OUT + unsigned &len) // IN/OUT +{ + uint32_t r = 0; + + for(unsigned dex=0; dex=0; dex--) { + buf[dex] = ui & 0xff; + ui >>= 8; + } + CFDataAppendBytes(cfOut, buf, sizeof(uint32_t)); +} + + +/* parse text as decimal, return BIGNUM */ +static BIGNUM *parseDecimalBn( + const unsigned char *cp, + unsigned len) +{ + for(unsigned dex=0; dex '9')) { + return NULL; + } + } + char *str = (char *)malloc(len + 1); + memmove(str, cp, len); + str[len] = '\0'; + BIGNUM *bn = NULL; + BN_dec2bn(&bn, str); + free(str); + return bn; +} + +/* write BIGNUM, OpenSSH v2 format (with a 4-byte byte count) */ +static CSSM_RETURN appendBigNum2( + CFMutableDataRef cfOut, + const BIGNUM *bn) +{ + if(bn == NULL) { + dprintf("appendBigNum2: NULL bn"); + return CSSMERR_CSP_INTERNAL_ERROR; + } + if (BN_is_zero(bn)) { + appendUint32(cfOut, 0); + return 0; + } + if(bn->neg) { + dprintf("appendBigNum2: negative numbers not supported\n"); + return CSSMERR_CSP_INTERNAL_ERROR; + } + int numBytes = BN_num_bytes(bn); + unsigned char buf[numBytes]; + int moved = BN_bn2bin(bn, buf); + if(moved != numBytes) { + dprintf("appendBigNum: BN_bn2bin() screwup\n"); + return CSSMERR_CSP_INTERNAL_ERROR; + } + bool appendZero = false; + if(buf[0] & 0x80) { + /* prepend leading zero to make it positive */ + appendZero = true; + numBytes++; // to encode the correct 4-byte length + } + appendUint32(cfOut, (uint32_t)numBytes); + if(appendZero) { + UInt8 z = 0; + CFDataAppendBytes(cfOut, &z, 1); + numBytes--; // to append the correct number of bytes + } + CFDataAppendBytes(cfOut, buf, numBytes); + memset(buf, 0, numBytes); + return CSSM_OK; +} + +/* read BIGNUM, OpenSSH-2 mpint version */ +static BIGNUM *readBigNum2( + const unsigned char *&cp, // IN/OUT + unsigned &remLen) // IN/OUT +{ + if(remLen < 4) { + dprintf("readBigNum2: short record(1)\n"); + return NULL; + } + uint32_t bytes = readUint32(cp, remLen); + if(remLen < bytes) { + dprintf("readBigNum2: short record(2)\n"); + return NULL; + } + BIGNUM *bn = BN_bin2bn(cp, bytes, NULL); + if(bn == NULL) { + dprintf("readBigNum2: BN_bin2bn error\n"); + return NULL; + } + cp += bytes; + remLen -= bytes; + return bn; +} + +/* Write BIGNUM, OpenSSH-1 decimal (public key) version */ +static CSSM_RETURN appendBigNumDec( + CFMutableDataRef cfOut, + const BIGNUM *bn) +{ + char *buf = BN_bn2dec(bn); + if(buf == NULL) { + dprintf("appendBigNumDec: BN_bn2dec() error"); + return CSSMERR_CSP_INTERNAL_ERROR; + } + CFDataAppendBytes(cfOut, (const UInt8 *)buf, strlen(buf)); + Free(buf); + return CSSM_OK; +} + +/* write string, OpenSSH v2 format (with a 4-byte byte count) */ +static void appendString( + CFMutableDataRef cfOut, + const char *str, + unsigned strLen) +{ + appendUint32(cfOut, (uint32_t)strLen); + CFDataAppendBytes(cfOut, (UInt8 *)str, strLen); +} + +/* 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; +} + + +/* + * Decode components from an SSHv2 public key. + * Also verifies the leading header, e.g. "ssh-rsa". + * The returned decodedBlob is algorithm-specific. + */ +static CSSM_RETURN parseSSH2PubKey( + const unsigned char *key, + unsigned keyLen, + const char *header, // SSH2_RSA_HEADER, SSH2_DSA_HEADER + unsigned char **decodedBlob, // mallocd and RETURNED + unsigned *decodedBlobLen) // RETURNED +{ + unsigned len = strlen(header); + *decodedBlob = NULL; + + /* ID string plus at least one space */ + if(keyLen < (len + 1)) { + dprintf("parseSSH2PubKey: short record(1)\n"); + return CSSMERR_CSP_INVALID_KEY; + } + + if(memcmp(header, key, len)) { + dprintf("parseSSH2PubKey: bad header (1)\n"); + return CSSMERR_CSP_INVALID_KEY; + } + key += len; + if(*key++ != ' ') { + dprintf("parseSSH2PubKey: bad header (2)\n"); + return CSSMERR_CSP_INVALID_KEY; + } + keyLen -= (len + 1); + + /* key points to first whitespace after header */ + skipWhite(key, keyLen); + if(keyLen == 0) { + dprintf("parseSSH2PubKey: short key\n"); + return CSSMERR_CSP_INVALID_KEY; + } + + /* key is start of base64 blob */ + const unsigned char *encodedBlob = key; + const unsigned char *endBlob = findNextWhite(key, keyLen); + unsigned encodedBlobLen = endBlob - encodedBlob; + + /* decode base 64 */ + *decodedBlob = cuDec64(encodedBlob, encodedBlobLen, decodedBlobLen); + if(*decodedBlob == NULL) { + dprintf("parseSSH2PubKey: base64 decode error\n"); + return CSSMERR_CSP_INVALID_KEY; + } + + /* skip remainder; it's comment */ + + return CSSM_OK; +} + + +#pragma mark -- RSA OpenSSHv1 --- + +CSSM_RETURN RSAPublicKeyEncodeOpenSSH1( + RSA *rsa, + const CssmData &descData, + CssmOwnedData &encodedKey) +{ + CFMutableDataRef cfOut = CFDataCreateMutable(NULL, 0); + CSSM_RETURN ourRtn = CSSM_OK; + + /* + * Format is + * num_bits in decimal + * + * e, bignum in decimal + * + * n, bignum in decimal + * + * optional comment + * newline + */ + unsigned numBits = BN_num_bits(rsa->n); + char bitString[20]; + UInt8 c = ' '; + + snprintf(bitString, sizeof(bitString), "%u ", numBits); + CFDataAppendBytes(cfOut, (const UInt8 *)bitString, strlen(bitString)); + if(ourRtn = appendBigNumDec(cfOut, rsa->e)) { + goto errOut; + } + CFDataAppendBytes(cfOut, &c, 1); + if(ourRtn = appendBigNumDec(cfOut, rsa->n)) { + goto errOut; + } + + if(descData.Length) { + /* optional comment */ + CFDataAppendBytes(cfOut, &c, 1); + CFDataAppendBytes(cfOut, (UInt8 *)descData.Data, descData.Length); + } + + c = '\n'; + CFDataAppendBytes(cfOut, &c, 1); + encodedKey.copy(CFDataGetBytePtr(cfOut), CFDataGetLength(cfOut)); +errOut: + CFRelease(cfOut); + return ourRtn; +} + +CSSM_RETURN RSAPublicKeyDecodeOpenSSH1( + RSA *rsa, + void *p, + size_t length) +{ + const unsigned char *cp = (const unsigned char *)p; + unsigned remLen = length; + + skipWhite(cp, remLen); + + /* + * cp points to start of size_in_bits in ASCII decimal; we really don't care about + * this field. Find next space. + */ + cp = findNextWhite(cp, remLen); + if(remLen == 0) { + dprintf("RSAPublicKeyDecodeOpenSSH1: short key (1)\n"); + return CSSMERR_CSP_INVALID_KEY; + } + skipWhite(cp, remLen); + if(remLen == 0) { + dprintf("RSAPublicKeyDecodeOpenSSH1: short key (2)\n"); + return CSSMERR_CSP_INVALID_KEY; + } + + /* + * cp points to start of e + */ + const unsigned char *ep = findNextWhite(cp, remLen); + if(remLen == 0) { + dprintf("RSAPublicKeyDecodeOpenSSH1: short key (3)\n"); + return CSSMERR_CSP_INVALID_KEY; + } + unsigned len = ep - cp; + rsa->e = parseDecimalBn(cp, len); + if(rsa->e == NULL) { + return CSSMERR_CSP_INVALID_KEY; + } + cp += len; + + skipWhite(cp, remLen); + if(remLen == 0) { + dprintf("RSAPublicKeyDecodeOpenSSH1: short key (4)\n"); + return -1; + } + + /* cp points to start of n */ + ep = findNextWhite(cp, remLen); + len = ep - cp; + rsa->n = parseDecimalBn(cp, len); + if(rsa->n == NULL) { + return CSSMERR_CSP_INVALID_KEY; + } + + /* remainder is comment, we ignore */ + return CSSM_OK; + +} + +CSSM_RETURN RSAPrivateKeyEncodeOpenSSH1( + RSA *rsa, + const CssmData &descData, + CssmOwnedData &encodedKey) +{ + CFDataRef cfOut; + CSSM_RETURN ourRtn; + + ourRtn = encodeOpenSSHv1PrivKey(rsa, descData.Data, descData.Length, NULL, &cfOut); + if(ourRtn) { + return ourRtn; + } + encodedKey.copy(CFDataGetBytePtr(cfOut), CFDataGetLength(cfOut)); + CFRelease(cfOut); + return CSSM_OK; +} + +extern CSSM_RETURN RSAPrivateKeyDecodeOpenSSH1( + RSA *openKey, + void *p, + size_t length) +{ + return decodeOpenSSHv1PrivKey((const unsigned char *)p, length, + openKey, NULL, NULL, NULL); +} + +#pragma mark -- RSA OpenSSHv2 --- + +CSSM_RETURN RSAPublicKeyEncodeOpenSSH2( + RSA *rsa, + const CssmData &descData, + CssmOwnedData &encodedKey) +{ + unsigned char *b64 = NULL; + unsigned b64Len; + UInt8 c; + + /* + * First, the inner base64-encoded blob, consisting of + * ssh-rsa + * e + * n + */ + CFMutableDataRef cfOut = CFDataCreateMutable(NULL, 0); + CSSM_RETURN ourRtn = CSSM_OK; + appendString(cfOut, SSH2_RSA_HEADER, strlen(SSH2_RSA_HEADER)); + if(ourRtn = appendBigNum2(cfOut, rsa->e)) { + goto errOut; + } + if(ourRtn = appendBigNum2(cfOut, rsa->n)) { + goto errOut; + } + + /* base64 encode that */ + b64 = cuEnc64((unsigned char *)CFDataGetBytePtr(cfOut), CFDataGetLength(cfOut), &b64Len); + + /* cuEnc64 added newline and NULL, which we really don't want */ + b64Len -= 2; + + /* Now start over, dropping that base64 into a public blob. */ + CFDataSetLength(cfOut, 0); + CFDataAppendBytes(cfOut, (UInt8 *)SSH2_RSA_HEADER, strlen(SSH2_RSA_HEADER)); + c = ' '; + CFDataAppendBytes(cfOut, &c, 1); + CFDataAppendBytes(cfOut, b64, b64Len); + + if(descData.Length) { + /* optional comment */ + CFDataAppendBytes(cfOut, &c, 1); + CFDataAppendBytes(cfOut, (UInt8 *)descData.Data, descData.Length); + } + + /* finish it with a newline */ + c = '\n'; + CFDataAppendBytes(cfOut, &c, 1); + + encodedKey.copy(CFDataGetBytePtr(cfOut), CFDataGetLength(cfOut)); +errOut: + CFRelease(cfOut); + if(b64) { + free(b64); + } + return ourRtn; +} + +CSSM_RETURN RSAPublicKeyDecodeOpenSSH2( + RSA *rsa, + void *p, + size_t length) +{ + const unsigned char *key = (const unsigned char *)p; + unsigned keyLen = length; + CSSM_RETURN ourRtn; + + /* + * Verify header + * get base64-decoded blob + */ + unsigned char *decodedBlob = NULL; + unsigned decodedBlobLen = 0; + if(ourRtn = parseSSH2PubKey(key, keyLen, SSH2_RSA_HEADER, &decodedBlob, &decodedBlobLen)) { + return ourRtn; + } + /* subsequent errors to errOut: */ + + /* + * The inner base64-decoded blob, consisting of + * ssh-rsa + * e + * n + */ + uint32_t decLen; + unsigned len; + + key = decodedBlob; + keyLen = decodedBlobLen; + if(keyLen < 12) { + /* three length fields at least */ + dprintf("RSAPublicKeyDecodeOpenSSH2: short record(2)\n"); + ourRtn = -1; + goto errOut; + } + decLen = readUint32(key, keyLen); + len = strlen(SSH2_RSA_HEADER); + if(decLen != len) { + dprintf("RSAPublicKeyDecodeOpenSSH2: bad header (2)\n"); + ourRtn = CSSMERR_CSP_INVALID_KEY; + goto errOut; + } + if(memcmp(SSH2_RSA_HEADER, key, len)) { + dprintf("RSAPublicKeyDecodeOpenSSH2: bad header (1)\n"); + return CSSMERR_CSP_INVALID_KEY; + } + key += len; + keyLen -= len; + + rsa->e = readBigNum2(key, keyLen); + if(rsa->e == NULL) { + ourRtn = CSSMERR_CSP_INVALID_KEY; + goto errOut; + } + rsa->n = readBigNum2(key, keyLen); + if(rsa->n == NULL) { + ourRtn = CSSMERR_CSP_INVALID_KEY; + goto errOut; + } + +errOut: + free(decodedBlob); + return ourRtn; +} + +#pragma mark -- DSA OpenSSHv2 --- + +CSSM_RETURN DSAPublicKeyEncodeOpenSSH2( + DSA *dsa, + const CssmData &descData, + CssmOwnedData &encodedKey) +{ + unsigned char *b64 = NULL; + unsigned b64Len; + UInt8 c; + + /* + * First, the inner base64-encoded blob, consisting of + * ssh-dss + * p + * q + * g + * pub_key + */ + CFMutableDataRef cfOut = CFDataCreateMutable(NULL, 0); + int ourRtn = 0; + appendString(cfOut, SSH2_DSA_HEADER, strlen(SSH2_DSA_HEADER)); + if(ourRtn = appendBigNum2(cfOut, dsa->p)) { + goto errOut; + } + if(ourRtn = appendBigNum2(cfOut, dsa->q)) { + goto errOut; + } + if(ourRtn = appendBigNum2(cfOut, dsa->g)) { + goto errOut; + } + if(ourRtn = appendBigNum2(cfOut, dsa->pub_key)) { + goto errOut; + } + + /* base64 encode that */ + b64 = cuEnc64((unsigned char *)CFDataGetBytePtr(cfOut), CFDataGetLength(cfOut), &b64Len); + + /* cuEnc64 added newline and NULL, which we really don't want */ + b64Len -= 2; + + /* Now start over, dropping that base64 into a public blob. */ + CFDataSetLength(cfOut, 0); + CFDataAppendBytes(cfOut, (UInt8 *)SSH2_DSA_HEADER, strlen(SSH2_DSA_HEADER)); + c = ' '; + CFDataAppendBytes(cfOut, &c, 1); + CFDataAppendBytes(cfOut, b64, b64Len); + + if(descData.Length) { + /* optional comment */ + CFDataAppendBytes(cfOut, &c, 1); + CFDataAppendBytes(cfOut, (UInt8 *)descData.Data, descData.Length); + } + + /* finish it with a newline */ + c = '\n'; + CFDataAppendBytes(cfOut, &c, 1); + + encodedKey.copy(CFDataGetBytePtr(cfOut), CFDataGetLength(cfOut)); + +errOut: + CFRelease(cfOut); + if(b64) { + free(b64); + } + return ourRtn; +} + +CSSM_RETURN DSAPublicKeyDecodeOpenSSH2( + DSA *dsa, + void *p, + size_t length) +{ + const unsigned char *key = (const unsigned char *)p; + unsigned keyLen = length; + CSSM_RETURN ourRtn; + + /* + * Verify header + * get base64-decoded blob + */ + unsigned char *decodedBlob = NULL; + unsigned decodedBlobLen = 0; + if(ourRtn = parseSSH2PubKey(key, keyLen, SSH2_DSA_HEADER, &decodedBlob, &decodedBlobLen)) { + return ourRtn; + } + /* subsequent errors to errOut: */ + + /* + * The inner base64-decoded blob, consisting of + * ssh-dss + * p + * q + * g + * pub_key + */ + uint32_t decLen; + unsigned len; + + key = decodedBlob; + keyLen = decodedBlobLen; + if(keyLen < 20) { + /* five length fields at least */ + dprintf("DSAPublicKeyDecodeOpenSSH2: short record(2)\n"); + ourRtn = CSSMERR_CSP_INVALID_KEY; + goto errOut; + } + decLen = readUint32(key, keyLen); + len = strlen(SSH2_DSA_HEADER); + if(decLen != len) { + dprintf("DSAPublicKeyDecodeOpenSSH2: bad header (2)\n"); + ourRtn = CSSMERR_CSP_INVALID_KEY; + goto errOut; + } + if(memcmp(SSH2_DSA_HEADER, key, len)) { + dprintf("DSAPublicKeyDecodeOpenSSH2: bad header (1)\n"); + return CSSMERR_CSP_INVALID_KEY; + } + key += len; + keyLen -= len; + + dsa->p = readBigNum2(key, keyLen); + if(dsa->p == NULL) { + ourRtn = CSSMERR_CSP_INVALID_KEY; + goto errOut; + } + dsa->q = readBigNum2(key, keyLen); + if(dsa->q == NULL) { + ourRtn = CSSMERR_CSP_INVALID_KEY; + goto errOut; + } + dsa->g = readBigNum2(key, keyLen); + if(dsa->g == NULL) { + ourRtn = CSSMERR_CSP_INVALID_KEY; + goto errOut; + } + dsa->pub_key = readBigNum2(key, keyLen); + if(dsa->pub_key == NULL) { + ourRtn = CSSMERR_CSP_INVALID_KEY; + goto errOut; + } + +errOut: + free(decodedBlob); + return ourRtn; + +} +