2 * Copyright (c) 2006 Apple Computer, Inc. All Rights Reserved.
4 * @APPLE_LICENSE_HEADER_START@
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
21 * @APPLE_LICENSE_HEADER_END@
26 * opensshCoding.cpp - Encoding and decoding of OpenSSH format public keys.
28 * Created 8/29/2006 by dmitch.
31 #include "opensshCoding.h"
32 #include <CoreFoundation/CFData.h>
33 #include <openssl/bn.h>
34 #include <openssl/crypto.h>
35 #include <security_cdsa_utils/cuEnc64.h>
37 #define SSH2_RSA_HEADER "ssh-rsa"
38 #define SSH2_DSA_HEADER "ssh-dss"
42 #define dprintf(s...) printf(s)
47 #pragma mark --- commmon code ---
50 const unsigned char *&cp
, // IN/OUT
51 unsigned &len
) // IN/OUT
55 for(unsigned dex
=0; dex
<sizeof(uint32_t); dex
++) {
64 CFMutableDataRef cfOut
,
67 UInt8 buf
[sizeof(uint32_t)];
69 for(int dex
=(sizeof(uint32_t) - 1); dex
>=0; dex
--) {
73 CFDataAppendBytes(cfOut
, buf
, sizeof(uint32_t));
77 /* parse text as decimal, return BIGNUM */
78 static BIGNUM
*parseDecimalBn(
79 const unsigned char *cp
,
82 for(unsigned dex
=0; dex
<len
; dex
++) {
84 if((c
< '0') || (c
> '9')) {
88 char *str
= (char *)malloc(len
+ 1);
89 memmove(str
, cp
, len
);
97 /* write BIGNUM, OpenSSH v2 format (with a 4-byte byte count) */
98 static CSSM_RETURN
appendBigNum2(
99 CFMutableDataRef cfOut
,
103 dprintf("appendBigNum2: NULL bn");
104 return CSSMERR_CSP_INTERNAL_ERROR
;
106 if (BN_is_zero(bn
)) {
107 appendUint32(cfOut
, 0);
111 dprintf("appendBigNum2: negative numbers not supported\n");
112 return CSSMERR_CSP_INTERNAL_ERROR
;
114 int numBytes
= BN_num_bytes(bn
);
115 unsigned char buf
[numBytes
];
116 int moved
= BN_bn2bin(bn
, buf
);
117 if(moved
!= numBytes
) {
118 dprintf("appendBigNum: BN_bn2bin() screwup\n");
119 return CSSMERR_CSP_INTERNAL_ERROR
;
121 bool appendZero
= false;
123 /* prepend leading zero to make it positive */
125 numBytes
++; // to encode the correct 4-byte length
127 appendUint32(cfOut
, (uint32_t)numBytes
);
130 CFDataAppendBytes(cfOut
, &z
, 1);
131 numBytes
--; // to append the correct number of bytes
133 CFDataAppendBytes(cfOut
, buf
, numBytes
);
134 memset(buf
, 0, numBytes
);
138 /* read BIGNUM, OpenSSH-2 mpint version */
139 static BIGNUM
*readBigNum2(
140 const unsigned char *&cp
, // IN/OUT
141 unsigned &remLen
) // IN/OUT
144 dprintf("readBigNum2: short record(1)\n");
147 uint32_t bytes
= readUint32(cp
, remLen
);
149 dprintf("readBigNum2: short record(2)\n");
152 BIGNUM
*bn
= BN_bin2bn(cp
, bytes
, NULL
);
154 dprintf("readBigNum2: BN_bin2bn error\n");
162 /* Write BIGNUM, OpenSSH-1 decimal (public key) version */
163 static CSSM_RETURN
appendBigNumDec(
164 CFMutableDataRef cfOut
,
167 char *buf
= BN_bn2dec(bn
);
169 dprintf("appendBigNumDec: BN_bn2dec() error");
170 return CSSMERR_CSP_INTERNAL_ERROR
;
172 CFDataAppendBytes(cfOut
, (const UInt8
*)buf
, strlen(buf
));
177 /* write string, OpenSSH v2 format (with a 4-byte byte count) */
178 static void appendString(
179 CFMutableDataRef cfOut
,
183 appendUint32(cfOut
, (uint32_t)strLen
);
184 CFDataAppendBytes(cfOut
, (UInt8
*)str
, strLen
);
187 /* skip whitespace */
188 static void skipWhite(
189 const unsigned char *&cp
,
192 while(bytesLeft
!= 0) {
193 if(isspace((int)(*cp
))) {
203 /* find next whitespace or EOF - if EOF, rtn pointer points to one past EOF */
204 static const unsigned char *findNextWhite(
205 const unsigned char *cp
,
208 while(bytesLeft
!= 0) {
209 if(isspace((int)(*cp
))) {
220 * Decode components from an SSHv2 public key.
221 * Also verifies the leading header, e.g. "ssh-rsa".
222 * The returned decodedBlob is algorithm-specific.
224 static CSSM_RETURN
parseSSH2PubKey(
225 const unsigned char *key
,
227 const char *header
, // SSH2_RSA_HEADER, SSH2_DSA_HEADER
228 unsigned char **decodedBlob
, // mallocd and RETURNED
229 unsigned *decodedBlobLen
) // RETURNED
231 unsigned len
= strlen(header
);
234 /* ID string plus at least one space */
235 if(keyLen
< (len
+ 1)) {
236 dprintf("parseSSH2PubKey: short record(1)\n");
237 return CSSMERR_CSP_INVALID_KEY
;
240 if(memcmp(header
, key
, len
)) {
241 dprintf("parseSSH2PubKey: bad header (1)\n");
242 return CSSMERR_CSP_INVALID_KEY
;
246 dprintf("parseSSH2PubKey: bad header (2)\n");
247 return CSSMERR_CSP_INVALID_KEY
;
251 /* key points to first whitespace after header */
252 skipWhite(key
, keyLen
);
254 dprintf("parseSSH2PubKey: short key\n");
255 return CSSMERR_CSP_INVALID_KEY
;
258 /* key is start of base64 blob */
259 const unsigned char *encodedBlob
= key
;
260 const unsigned char *endBlob
= findNextWhite(key
, keyLen
);
261 unsigned encodedBlobLen
= endBlob
- encodedBlob
;
264 *decodedBlob
= cuDec64(encodedBlob
, encodedBlobLen
, decodedBlobLen
);
265 if(*decodedBlob
== NULL
) {
266 dprintf("parseSSH2PubKey: base64 decode error\n");
267 return CSSMERR_CSP_INVALID_KEY
;
270 /* skip remainder; it's comment */
276 #pragma mark -- RSA OpenSSHv1 ---
278 CSSM_RETURN
RSAPublicKeyEncodeOpenSSH1(
280 const CssmData
&descData
,
281 CssmOwnedData
&encodedKey
)
283 CFMutableDataRef cfOut
= CFDataCreateMutable(NULL
, 0);
284 CSSM_RETURN ourRtn
= CSSM_OK
;
288 * num_bits in decimal
290 * e, bignum in decimal
292 * n, bignum in decimal
297 unsigned numBits
= BN_num_bits(rsa
->n
);
301 snprintf(bitString
, sizeof(bitString
), "%u ", numBits
);
302 CFDataAppendBytes(cfOut
, (const UInt8
*)bitString
, strlen(bitString
));
303 if(ourRtn
= appendBigNumDec(cfOut
, rsa
->e
)) {
306 CFDataAppendBytes(cfOut
, &c
, 1);
307 if(ourRtn
= appendBigNumDec(cfOut
, rsa
->n
)) {
311 if(descData
.Length
) {
312 /* optional comment */
313 CFDataAppendBytes(cfOut
, &c
, 1);
314 CFDataAppendBytes(cfOut
, (UInt8
*)descData
.Data
, descData
.Length
);
318 CFDataAppendBytes(cfOut
, &c
, 1);
319 encodedKey
.copy(CFDataGetBytePtr(cfOut
), CFDataGetLength(cfOut
));
325 CSSM_RETURN
RSAPublicKeyDecodeOpenSSH1(
330 const unsigned char *cp
= (const unsigned char *)p
;
331 unsigned remLen
= length
;
333 skipWhite(cp
, remLen
);
336 * cp points to start of size_in_bits in ASCII decimal; we really don't care about
337 * this field. Find next space.
339 cp
= findNextWhite(cp
, remLen
);
341 dprintf("RSAPublicKeyDecodeOpenSSH1: short key (1)\n");
342 return CSSMERR_CSP_INVALID_KEY
;
344 skipWhite(cp
, remLen
);
346 dprintf("RSAPublicKeyDecodeOpenSSH1: short key (2)\n");
347 return CSSMERR_CSP_INVALID_KEY
;
351 * cp points to start of e
353 const unsigned char *ep
= findNextWhite(cp
, remLen
);
355 dprintf("RSAPublicKeyDecodeOpenSSH1: short key (3)\n");
356 return CSSMERR_CSP_INVALID_KEY
;
358 unsigned len
= ep
- cp
;
359 rsa
->e
= parseDecimalBn(cp
, len
);
361 return CSSMERR_CSP_INVALID_KEY
;
365 skipWhite(cp
, remLen
);
367 dprintf("RSAPublicKeyDecodeOpenSSH1: short key (4)\n");
371 /* cp points to start of n */
372 ep
= findNextWhite(cp
, remLen
);
374 rsa
->n
= parseDecimalBn(cp
, len
);
376 return CSSMERR_CSP_INVALID_KEY
;
379 /* remainder is comment, we ignore */
384 CSSM_RETURN
RSAPrivateKeyEncodeOpenSSH1(
386 const CssmData
&descData
,
387 CssmOwnedData
&encodedKey
)
392 ourRtn
= encodeOpenSSHv1PrivKey(rsa
, descData
.Data
, descData
.Length
, NULL
, &cfOut
);
396 encodedKey
.copy(CFDataGetBytePtr(cfOut
), CFDataGetLength(cfOut
));
401 extern CSSM_RETURN
RSAPrivateKeyDecodeOpenSSH1(
406 return decodeOpenSSHv1PrivKey((const unsigned char *)p
, length
,
407 openKey
, NULL
, NULL
, NULL
);
410 #pragma mark -- RSA OpenSSHv2 ---
412 CSSM_RETURN
RSAPublicKeyEncodeOpenSSH2(
414 const CssmData
&descData
,
415 CssmOwnedData
&encodedKey
)
417 unsigned char *b64
= NULL
;
422 * First, the inner base64-encoded blob, consisting of
427 CFMutableDataRef cfOut
= CFDataCreateMutable(NULL
, 0);
428 CSSM_RETURN ourRtn
= CSSM_OK
;
429 appendString(cfOut
, SSH2_RSA_HEADER
, strlen(SSH2_RSA_HEADER
));
430 if(ourRtn
= appendBigNum2(cfOut
, rsa
->e
)) {
433 if(ourRtn
= appendBigNum2(cfOut
, rsa
->n
)) {
437 /* base64 encode that */
438 b64
= cuEnc64((unsigned char *)CFDataGetBytePtr(cfOut
), CFDataGetLength(cfOut
), &b64Len
);
440 /* cuEnc64 added newline and NULL, which we really don't want */
443 /* Now start over, dropping that base64 into a public blob. */
444 CFDataSetLength(cfOut
, 0);
445 CFDataAppendBytes(cfOut
, (UInt8
*)SSH2_RSA_HEADER
, strlen(SSH2_RSA_HEADER
));
447 CFDataAppendBytes(cfOut
, &c
, 1);
448 CFDataAppendBytes(cfOut
, b64
, b64Len
);
450 if(descData
.Length
) {
451 /* optional comment */
452 CFDataAppendBytes(cfOut
, &c
, 1);
453 CFDataAppendBytes(cfOut
, (UInt8
*)descData
.Data
, descData
.Length
);
456 /* finish it with a newline */
458 CFDataAppendBytes(cfOut
, &c
, 1);
460 encodedKey
.copy(CFDataGetBytePtr(cfOut
), CFDataGetLength(cfOut
));
469 CSSM_RETURN
RSAPublicKeyDecodeOpenSSH2(
474 const unsigned char *key
= (const unsigned char *)p
;
475 unsigned keyLen
= length
;
480 * get base64-decoded blob
482 unsigned char *decodedBlob
= NULL
;
483 unsigned decodedBlobLen
= 0;
484 if(ourRtn
= parseSSH2PubKey(key
, keyLen
, SSH2_RSA_HEADER
, &decodedBlob
, &decodedBlobLen
)) {
487 /* subsequent errors to errOut: */
490 * The inner base64-decoded blob, consisting of
499 keyLen
= decodedBlobLen
;
501 /* three length fields at least */
502 dprintf("RSAPublicKeyDecodeOpenSSH2: short record(2)\n");
506 decLen
= readUint32(key
, keyLen
);
507 len
= strlen(SSH2_RSA_HEADER
);
509 dprintf("RSAPublicKeyDecodeOpenSSH2: bad header (2)\n");
510 ourRtn
= CSSMERR_CSP_INVALID_KEY
;
513 if(memcmp(SSH2_RSA_HEADER
, key
, len
)) {
514 dprintf("RSAPublicKeyDecodeOpenSSH2: bad header (1)\n");
515 return CSSMERR_CSP_INVALID_KEY
;
520 rsa
->e
= readBigNum2(key
, keyLen
);
522 ourRtn
= CSSMERR_CSP_INVALID_KEY
;
525 rsa
->n
= readBigNum2(key
, keyLen
);
527 ourRtn
= CSSMERR_CSP_INVALID_KEY
;
536 #pragma mark -- DSA OpenSSHv2 ---
538 CSSM_RETURN
DSAPublicKeyEncodeOpenSSH2(
540 const CssmData
&descData
,
541 CssmOwnedData
&encodedKey
)
543 unsigned char *b64
= NULL
;
548 * First, the inner base64-encoded blob, consisting of
555 CFMutableDataRef cfOut
= CFDataCreateMutable(NULL
, 0);
557 appendString(cfOut
, SSH2_DSA_HEADER
, strlen(SSH2_DSA_HEADER
));
558 if(ourRtn
= appendBigNum2(cfOut
, dsa
->p
)) {
561 if(ourRtn
= appendBigNum2(cfOut
, dsa
->q
)) {
564 if(ourRtn
= appendBigNum2(cfOut
, dsa
->g
)) {
567 if(ourRtn
= appendBigNum2(cfOut
, dsa
->pub_key
)) {
571 /* base64 encode that */
572 b64
= cuEnc64((unsigned char *)CFDataGetBytePtr(cfOut
), CFDataGetLength(cfOut
), &b64Len
);
574 /* cuEnc64 added newline and NULL, which we really don't want */
577 /* Now start over, dropping that base64 into a public blob. */
578 CFDataSetLength(cfOut
, 0);
579 CFDataAppendBytes(cfOut
, (UInt8
*)SSH2_DSA_HEADER
, strlen(SSH2_DSA_HEADER
));
581 CFDataAppendBytes(cfOut
, &c
, 1);
582 CFDataAppendBytes(cfOut
, b64
, b64Len
);
584 if(descData
.Length
) {
585 /* optional comment */
586 CFDataAppendBytes(cfOut
, &c
, 1);
587 CFDataAppendBytes(cfOut
, (UInt8
*)descData
.Data
, descData
.Length
);
590 /* finish it with a newline */
592 CFDataAppendBytes(cfOut
, &c
, 1);
594 encodedKey
.copy(CFDataGetBytePtr(cfOut
), CFDataGetLength(cfOut
));
604 CSSM_RETURN
DSAPublicKeyDecodeOpenSSH2(
609 const unsigned char *key
= (const unsigned char *)p
;
610 unsigned keyLen
= length
;
615 * get base64-decoded blob
617 unsigned char *decodedBlob
= NULL
;
618 unsigned decodedBlobLen
= 0;
619 if(ourRtn
= parseSSH2PubKey(key
, keyLen
, SSH2_DSA_HEADER
, &decodedBlob
, &decodedBlobLen
)) {
622 /* subsequent errors to errOut: */
625 * The inner base64-decoded blob, consisting of
636 keyLen
= decodedBlobLen
;
638 /* five length fields at least */
639 dprintf("DSAPublicKeyDecodeOpenSSH2: short record(2)\n");
640 ourRtn
= CSSMERR_CSP_INVALID_KEY
;
643 decLen
= readUint32(key
, keyLen
);
644 len
= strlen(SSH2_DSA_HEADER
);
646 dprintf("DSAPublicKeyDecodeOpenSSH2: bad header (2)\n");
647 ourRtn
= CSSMERR_CSP_INVALID_KEY
;
650 if(memcmp(SSH2_DSA_HEADER
, key
, len
)) {
651 dprintf("DSAPublicKeyDecodeOpenSSH2: bad header (1)\n");
652 return CSSMERR_CSP_INVALID_KEY
;
657 dsa
->p
= readBigNum2(key
, keyLen
);
659 ourRtn
= CSSMERR_CSP_INVALID_KEY
;
662 dsa
->q
= readBigNum2(key
, keyLen
);
664 ourRtn
= CSSMERR_CSP_INVALID_KEY
;
667 dsa
->g
= readBigNum2(key
, keyLen
);
669 ourRtn
= CSSMERR_CSP_INVALID_KEY
;
672 dsa
->pub_key
= readBigNum2(key
, keyLen
);
673 if(dsa
->pub_key
== NULL
) {
674 ourRtn
= CSSMERR_CSP_INVALID_KEY
;