2 * Copyright (c) 2006,2011-2012,2014 Apple 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.
30 #include "opensshCoding.h"
31 #include <CoreFoundation/CFData.h>
32 #include <openssl/bn_legacy.h>
33 #include <openssl/crypto_legacy.h>
34 #include <security_cdsa_utils/cuEnc64.h>
36 #define SSH2_RSA_HEADER "ssh-rsa"
37 #define SSH2_DSA_HEADER "ssh-dss"
41 #define dprintf(s...) printf(s)
46 #pragma mark --- commmon code ---
49 const unsigned char *&cp
, // IN/OUT
50 unsigned &len
) // IN/OUT
54 for(unsigned dex
=0; dex
<sizeof(uint32_t); dex
++) {
63 CFMutableDataRef cfOut
,
66 UInt8 buf
[sizeof(uint32_t)];
68 for(int dex
=(sizeof(uint32_t) - 1); dex
>=0; dex
--) {
72 CFDataAppendBytes(cfOut
, buf
, sizeof(uint32_t));
76 /* parse text as decimal, return BIGNUM */
77 static BIGNUM
*parseDecimalBn(
78 const unsigned char *cp
,
81 for(unsigned dex
=0; dex
<len
; dex
++) {
83 if((c
< '0') || (c
> '9')) {
87 char *str
= (char *)malloc(len
+ 1);
88 memmove(str
, cp
, len
);
96 /* write BIGNUM, OpenSSH v2 format (with a 4-byte byte count) */
97 static CSSM_RETURN
appendBigNum2(
98 CFMutableDataRef cfOut
,
102 dprintf("appendBigNum2: NULL bn");
103 return CSSMERR_CSP_INTERNAL_ERROR
;
105 if (BN_is_zero(bn
)) {
106 appendUint32(cfOut
, 0);
110 dprintf("appendBigNum2: negative numbers not supported\n");
111 return CSSMERR_CSP_INTERNAL_ERROR
;
113 int numBytes
= BN_num_bytes(bn
);
114 unsigned char *buf
= (unsigned char*)malloc(numBytes
);
116 dprintf("appendBigNum: Cannot allocate a temp BN buffer\n");
117 return CSSMERR_CSSM_MEMORY_ERROR
;
119 int moved
= BN_bn2bin(bn
, buf
);
120 if(moved
!= numBytes
) {
121 dprintf("appendBigNum: BN_bn2bin() screwup\n");
123 return CSSMERR_CSP_INTERNAL_ERROR
;
125 bool appendZero
= false;
127 /* prepend leading zero to make it positive */
129 numBytes
++; // to encode the correct 4-byte length
131 appendUint32(cfOut
, (uint32_t)numBytes
);
134 CFDataAppendBytes(cfOut
, &z
, 1);
135 numBytes
--; // to append the correct number of bytes
137 CFDataAppendBytes(cfOut
, buf
, numBytes
);
138 memset(buf
, 0, numBytes
);
143 /* read BIGNUM, OpenSSH-2 mpint version */
144 static BIGNUM
*readBigNum2(
145 const unsigned char *&cp
, // IN/OUT
146 unsigned &remLen
) // IN/OUT
149 dprintf("readBigNum2: short record(1)\n");
152 uint32_t bytes
= readUint32(cp
, remLen
);
154 dprintf("readBigNum2: short record(2)\n");
157 BIGNUM
*bn
= BN_bin2bn(cp
, bytes
, NULL
);
159 dprintf("readBigNum2: BN_bin2bn error\n");
167 /* Write BIGNUM, OpenSSH-1 decimal (public key) version */
168 static CSSM_RETURN
appendBigNumDec(
169 CFMutableDataRef cfOut
,
172 char *buf
= BN_bn2dec(bn
);
174 dprintf("appendBigNumDec: BN_bn2dec() error");
175 return CSSMERR_CSP_INTERNAL_ERROR
;
177 CFDataAppendBytes(cfOut
, (const UInt8
*)buf
, strlen(buf
));
182 /* write string, OpenSSH v2 format (with a 4-byte byte count) */
183 static void appendString(
184 CFMutableDataRef cfOut
,
188 appendUint32(cfOut
, (uint32_t)strLen
);
189 CFDataAppendBytes(cfOut
, (UInt8
*)str
, strLen
);
192 /* skip whitespace */
193 static void skipWhite(
194 const unsigned char *&cp
,
197 while(bytesLeft
!= 0) {
198 if(isspace((int)(*cp
))) {
208 /* find next whitespace or EOF - if EOF, rtn pointer points to one past EOF */
209 static const unsigned char *findNextWhite(
210 const unsigned char *cp
,
213 while(bytesLeft
!= 0) {
214 if(isspace((int)(*cp
))) {
225 * Decode components from an SSHv2 public key.
226 * Also verifies the leading header, e.g. "ssh-rsa".
227 * The returned decodedBlob is algorithm-specific.
229 static CSSM_RETURN
parseSSH2PubKey(
230 const unsigned char *key
,
232 const char *header
, // SSH2_RSA_HEADER, SSH2_DSA_HEADER
233 unsigned char **decodedBlob
, // mallocd and RETURNED
234 unsigned *decodedBlobLen
) // RETURNED
236 size_t len
= strlen(header
);
239 /* ID string plus at least one space */
240 if(keyLen
< (len
+ 1)) {
241 dprintf("parseSSH2PubKey: short record(1)\n");
242 return CSSMERR_CSP_INVALID_KEY
;
245 if(memcmp(header
, key
, len
)) {
246 dprintf("parseSSH2PubKey: bad header (1)\n");
247 return CSSMERR_CSP_INVALID_KEY
;
251 dprintf("parseSSH2PubKey: bad header (2)\n");
252 return CSSMERR_CSP_INVALID_KEY
;
256 /* key points to first whitespace after header */
257 skipWhite(key
, keyLen
);
259 dprintf("parseSSH2PubKey: short key\n");
260 return CSSMERR_CSP_INVALID_KEY
;
263 /* key is start of base64 blob */
264 const unsigned char *encodedBlob
= key
;
265 const unsigned char *endBlob
= findNextWhite(key
, keyLen
);
266 unsigned encodedBlobLen
= (unsigned)(endBlob
- encodedBlob
);
269 *decodedBlob
= cuDec64(encodedBlob
, encodedBlobLen
, decodedBlobLen
);
270 if(*decodedBlob
== NULL
) {
271 dprintf("parseSSH2PubKey: base64 decode error\n");
272 return CSSMERR_CSP_INVALID_KEY
;
275 /* skip remainder; it's comment */
281 #pragma mark -- RSA OpenSSHv1 ---
283 CSSM_RETURN
RSAPublicKeyEncodeOpenSSH1(
285 const CssmData
&descData
,
286 CssmOwnedData
&encodedKey
)
288 CFMutableDataRef cfOut
= CFDataCreateMutable(NULL
, 0);
289 CSSM_RETURN ourRtn
= CSSM_OK
;
293 * num_bits in decimal
295 * e, bignum in decimal
297 * n, bignum in decimal
302 unsigned numBits
= BN_num_bits(rsa
->n
);
306 snprintf(bitString
, sizeof(bitString
), "%u ", numBits
);
307 CFDataAppendBytes(cfOut
, (const UInt8
*)bitString
, strlen(bitString
));
308 if((ourRtn
= appendBigNumDec(cfOut
, rsa
->e
))) {
311 CFDataAppendBytes(cfOut
, &c
, 1);
312 if((ourRtn
= appendBigNumDec(cfOut
, rsa
->n
))) {
316 if(descData
.Length
) {
317 /* optional comment */
318 CFDataAppendBytes(cfOut
, &c
, 1);
319 CFDataAppendBytes(cfOut
, (UInt8
*)descData
.Data
, descData
.Length
);
323 CFDataAppendBytes(cfOut
, &c
, 1);
324 encodedKey
.copy(CFDataGetBytePtr(cfOut
), CFDataGetLength(cfOut
));
330 CSSM_RETURN
RSAPublicKeyDecodeOpenSSH1(
335 const unsigned char *cp
= (const unsigned char *)p
;
336 unsigned remLen
= (unsigned)length
;
338 skipWhite(cp
, remLen
);
341 * cp points to start of size_in_bits in ASCII decimal; we really don't care about
342 * this field. Find next space.
344 cp
= findNextWhite(cp
, remLen
);
346 dprintf("RSAPublicKeyDecodeOpenSSH1: short key (1)\n");
347 return CSSMERR_CSP_INVALID_KEY
;
349 skipWhite(cp
, remLen
);
351 dprintf("RSAPublicKeyDecodeOpenSSH1: short key (2)\n");
352 return CSSMERR_CSP_INVALID_KEY
;
356 * cp points to start of e
358 const unsigned char *ep
= findNextWhite(cp
, remLen
);
360 dprintf("RSAPublicKeyDecodeOpenSSH1: short key (3)\n");
361 return CSSMERR_CSP_INVALID_KEY
;
363 unsigned len
= (unsigned)(ep
- cp
);
364 rsa
->e
= parseDecimalBn(cp
, len
);
366 return CSSMERR_CSP_INVALID_KEY
;
370 skipWhite(cp
, remLen
);
372 dprintf("RSAPublicKeyDecodeOpenSSH1: short key (4)\n");
376 /* cp points to start of n */
377 ep
= findNextWhite(cp
, remLen
);
378 len
= (unsigned)(ep
- cp
);
379 rsa
->n
= parseDecimalBn(cp
, len
);
381 return CSSMERR_CSP_INVALID_KEY
;
384 /* remainder is comment, we ignore */
389 CSSM_RETURN
RSAPrivateKeyEncodeOpenSSH1(
391 const CssmData
&descData
,
392 CssmOwnedData
&encodedKey
)
397 ourRtn
= encodeOpenSSHv1PrivKey(rsa
, descData
.Data
, (unsigned)descData
.Length
, NULL
, &cfOut
);
401 encodedKey
.copy(CFDataGetBytePtr(cfOut
), CFDataGetLength(cfOut
));
406 extern CSSM_RETURN
RSAPrivateKeyDecodeOpenSSH1(
411 return decodeOpenSSHv1PrivKey((const unsigned char *)p
, (unsigned)length
,
412 openKey
, NULL
, NULL
, NULL
);
415 #pragma mark -- RSA OpenSSHv2 ---
417 CSSM_RETURN
RSAPublicKeyEncodeOpenSSH2(
419 const CssmData
&descData
,
420 CssmOwnedData
&encodedKey
)
422 unsigned char *b64
= NULL
;
427 * First, the inner base64-encoded blob, consisting of
432 CFMutableDataRef cfOut
= CFDataCreateMutable(NULL
, 0);
433 CSSM_RETURN ourRtn
= CSSM_OK
;
434 appendString(cfOut
, SSH2_RSA_HEADER
, strlen(SSH2_RSA_HEADER
));
435 if((ourRtn
= appendBigNum2(cfOut
, rsa
->e
))) {
438 if((ourRtn
= appendBigNum2(cfOut
, rsa
->n
))) {
442 /* base64 encode that */
443 b64
= cuEnc64((unsigned char *)CFDataGetBytePtr(cfOut
), (unsigned)CFDataGetLength(cfOut
), &b64Len
);
445 /* cuEnc64 added newline and NULL, which we really don't want */
448 /* Now start over, dropping that base64 into a public blob. */
449 CFDataSetLength(cfOut
, 0);
450 CFDataAppendBytes(cfOut
, (UInt8
*)SSH2_RSA_HEADER
, strlen(SSH2_RSA_HEADER
));
452 CFDataAppendBytes(cfOut
, &c
, 1);
453 CFDataAppendBytes(cfOut
, b64
, b64Len
);
455 if(descData
.Length
) {
456 /* optional comment */
457 CFDataAppendBytes(cfOut
, &c
, 1);
458 CFDataAppendBytes(cfOut
, (UInt8
*)descData
.Data
, descData
.Length
);
461 /* finish it with a newline */
463 CFDataAppendBytes(cfOut
, &c
, 1);
465 encodedKey
.copy(CFDataGetBytePtr(cfOut
), CFDataGetLength(cfOut
));
474 CSSM_RETURN
RSAPublicKeyDecodeOpenSSH2(
479 const unsigned char *key
= (const unsigned char *)p
;
480 unsigned keyLen
= (unsigned)length
;
485 * get base64-decoded blob
487 unsigned char *decodedBlob
= NULL
;
488 unsigned decodedBlobLen
= 0;
489 if((ourRtn
= parseSSH2PubKey(key
, keyLen
, SSH2_RSA_HEADER
, &decodedBlob
, &decodedBlobLen
))) {
492 /* subsequent errors to errOut: */
495 * The inner base64-decoded blob, consisting of
504 keyLen
= decodedBlobLen
;
506 /* three length fields at least */
507 dprintf("RSAPublicKeyDecodeOpenSSH2: short record(2)\n");
511 decLen
= readUint32(key
, keyLen
);
512 len
= strlen(SSH2_RSA_HEADER
);
514 dprintf("RSAPublicKeyDecodeOpenSSH2: bad header (2)\n");
515 ourRtn
= CSSMERR_CSP_INVALID_KEY
;
518 if(memcmp(SSH2_RSA_HEADER
, key
, len
)) {
519 dprintf("RSAPublicKeyDecodeOpenSSH2: bad header (1)\n");
520 return CSSMERR_CSP_INVALID_KEY
;
525 rsa
->e
= readBigNum2(key
, keyLen
);
527 ourRtn
= CSSMERR_CSP_INVALID_KEY
;
530 rsa
->n
= readBigNum2(key
, keyLen
);
532 ourRtn
= CSSMERR_CSP_INVALID_KEY
;
541 #pragma mark -- DSA OpenSSHv2 ---
543 CSSM_RETURN
DSAPublicKeyEncodeOpenSSH2(
545 const CssmData
&descData
,
546 CssmOwnedData
&encodedKey
)
548 unsigned char *b64
= NULL
;
553 * First, the inner base64-encoded blob, consisting of
560 CFMutableDataRef cfOut
= CFDataCreateMutable(NULL
, 0);
562 appendString(cfOut
, SSH2_DSA_HEADER
, strlen(SSH2_DSA_HEADER
));
563 if((ourRtn
= appendBigNum2(cfOut
, dsa
->p
))) {
566 if((ourRtn
= appendBigNum2(cfOut
, dsa
->q
))) {
569 if((ourRtn
= appendBigNum2(cfOut
, dsa
->g
))) {
572 if((ourRtn
= appendBigNum2(cfOut
, dsa
->pub_key
))) {
576 /* base64 encode that */
577 b64
= cuEnc64((unsigned char *)CFDataGetBytePtr(cfOut
), (unsigned)CFDataGetLength(cfOut
), &b64Len
);
579 /* cuEnc64 added newline and NULL, which we really don't want */
582 /* Now start over, dropping that base64 into a public blob. */
583 CFDataSetLength(cfOut
, 0);
584 CFDataAppendBytes(cfOut
, (UInt8
*)SSH2_DSA_HEADER
, strlen(SSH2_DSA_HEADER
));
586 CFDataAppendBytes(cfOut
, &c
, 1);
587 CFDataAppendBytes(cfOut
, b64
, b64Len
);
589 if(descData
.Length
) {
590 /* optional comment */
591 CFDataAppendBytes(cfOut
, &c
, 1);
592 CFDataAppendBytes(cfOut
, (UInt8
*)descData
.Data
, descData
.Length
);
595 /* finish it with a newline */
597 CFDataAppendBytes(cfOut
, &c
, 1);
599 encodedKey
.copy(CFDataGetBytePtr(cfOut
), CFDataGetLength(cfOut
));
609 CSSM_RETURN
DSAPublicKeyDecodeOpenSSH2(
614 const unsigned char *key
= (const unsigned char *)p
;
615 unsigned keyLen
= (unsigned)length
;
620 * get base64-decoded blob
622 unsigned char *decodedBlob
= NULL
;
623 unsigned decodedBlobLen
= 0;
624 if((ourRtn
= parseSSH2PubKey(key
, keyLen
, SSH2_DSA_HEADER
, &decodedBlob
, &decodedBlobLen
))) {
627 /* subsequent errors to errOut: */
630 * The inner base64-decoded blob, consisting of
641 keyLen
= decodedBlobLen
;
643 /* five length fields at least */
644 dprintf("DSAPublicKeyDecodeOpenSSH2: short record(2)\n");
645 ourRtn
= CSSMERR_CSP_INVALID_KEY
;
648 decLen
= readUint32(key
, keyLen
);
649 len
= strlen(SSH2_DSA_HEADER
);
651 dprintf("DSAPublicKeyDecodeOpenSSH2: bad header (2)\n");
652 ourRtn
= CSSMERR_CSP_INVALID_KEY
;
655 if(memcmp(SSH2_DSA_HEADER
, key
, len
)) {
656 dprintf("DSAPublicKeyDecodeOpenSSH2: bad header (1)\n");
657 return CSSMERR_CSP_INVALID_KEY
;
662 dsa
->p
= readBigNum2(key
, keyLen
);
664 ourRtn
= CSSMERR_CSP_INVALID_KEY
;
667 dsa
->q
= readBigNum2(key
, keyLen
);
669 ourRtn
= CSSMERR_CSP_INVALID_KEY
;
672 dsa
->g
= readBigNum2(key
, keyLen
);
674 ourRtn
= CSSMERR_CSP_INVALID_KEY
;
677 dsa
->pub_key
= readBigNum2(key
, keyLen
);
678 if(dsa
->pub_key
== NULL
) {
679 ourRtn
= CSSMERR_CSP_INVALID_KEY
;