]> git.saurik.com Git - apple/security.git/blobdiff - libsecurity_apple_csp/lib/opensshCoding.cpp
Security-55163.44.tar.gz
[apple/security.git] / 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 (file)
index 0000000..15bc177
--- /dev/null
@@ -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 <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
+{
+       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
+        * <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 = 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;
+
+}
+