+++ /dev/null
-/*
- * 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 <CoreFoundation/CFData.h>
-#include <openssl/bn.h>
-#include <openssl/crypto.h>
-#include <security_cdsa_utils/cuEnc64.h>
-
-#define SSH2_RSA_HEADER "ssh-rsa"
-#define SSH2_DSA_HEADER "ssh-dss"
-
-#ifndef NDEBUG
-#include <stdio.h>
-#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<sizeof(uint32_t); dex++) {
- r <<= 8;
- r |= *cp++;
- }
- len -= 4;
- return r;
-}
-
-void appendUint32(
- CFMutableDataRef cfOut,
- uint32_t ui)
-{
- UInt8 buf[sizeof(uint32_t)];
-
- for(int dex=(sizeof(uint32_t) - 1); 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<len; dex++) {
- char c = *cp;
- if((c < '0') || (c > '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
-{
- size_t 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 = (unsigned)(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
- * <space>
- * e, bignum in decimal
- * <space>
- * n, bignum in decimal
- * <space>
- * 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 = (unsigned)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 = (unsigned)(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 = (unsigned)(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, (unsigned)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, (unsigned)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), (unsigned)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 = (unsigned)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), (unsigned)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 = (unsigned)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;
-
-}
-