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.h>
33 #include <openssl/crypto.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
[numBytes
];
115 int moved
= BN_bn2bin(bn
, buf
);
116 if(moved
!= numBytes
) {
117 dprintf("appendBigNum: BN_bn2bin() screwup\n");
118 return CSSMERR_CSP_INTERNAL_ERROR
;
120 bool appendZero
= false;
122 /* prepend leading zero to make it positive */
124 numBytes
++; // to encode the correct 4-byte length
126 appendUint32(cfOut
, (uint32_t)numBytes
);
129 CFDataAppendBytes(cfOut
, &z
, 1);
130 numBytes
--; // to append the correct number of bytes
132 CFDataAppendBytes(cfOut
, buf
, numBytes
);
133 memset(buf
, 0, numBytes
);
137 /* read BIGNUM, OpenSSH-2 mpint version */
138 static BIGNUM
*readBigNum2(
139 const unsigned char *&cp
, // IN/OUT
140 unsigned &remLen
) // IN/OUT
143 dprintf("readBigNum2: short record(1)\n");
146 uint32_t bytes
= readUint32(cp
, remLen
);
148 dprintf("readBigNum2: short record(2)\n");
151 BIGNUM
*bn
= BN_bin2bn(cp
, bytes
, NULL
);
153 dprintf("readBigNum2: BN_bin2bn error\n");
161 /* Write BIGNUM, OpenSSH-1 decimal (public key) version */
162 static CSSM_RETURN
appendBigNumDec(
163 CFMutableDataRef cfOut
,
166 char *buf
= BN_bn2dec(bn
);
168 dprintf("appendBigNumDec: BN_bn2dec() error");
169 return CSSMERR_CSP_INTERNAL_ERROR
;
171 CFDataAppendBytes(cfOut
, (const UInt8
*)buf
, strlen(buf
));
176 /* write string, OpenSSH v2 format (with a 4-byte byte count) */
177 static void appendString(
178 CFMutableDataRef cfOut
,
182 appendUint32(cfOut
, (uint32_t)strLen
);
183 CFDataAppendBytes(cfOut
, (UInt8
*)str
, strLen
);
186 /* skip whitespace */
187 static void skipWhite(
188 const unsigned char *&cp
,
191 while(bytesLeft
!= 0) {
192 if(isspace((int)(*cp
))) {
202 /* find next whitespace or EOF - if EOF, rtn pointer points to one past EOF */
203 static const unsigned char *findNextWhite(
204 const unsigned char *cp
,
207 while(bytesLeft
!= 0) {
208 if(isspace((int)(*cp
))) {
219 * Decode components from an SSHv2 public key.
220 * Also verifies the leading header, e.g. "ssh-rsa".
221 * The returned decodedBlob is algorithm-specific.
223 static CSSM_RETURN
parseSSH2PubKey(
224 const unsigned char *key
,
226 const char *header
, // SSH2_RSA_HEADER, SSH2_DSA_HEADER
227 unsigned char **decodedBlob
, // mallocd and RETURNED
228 unsigned *decodedBlobLen
) // RETURNED
230 size_t len
= strlen(header
);
233 /* ID string plus at least one space */
234 if(keyLen
< (len
+ 1)) {
235 dprintf("parseSSH2PubKey: short record(1)\n");
236 return CSSMERR_CSP_INVALID_KEY
;
239 if(memcmp(header
, key
, len
)) {
240 dprintf("parseSSH2PubKey: bad header (1)\n");
241 return CSSMERR_CSP_INVALID_KEY
;
245 dprintf("parseSSH2PubKey: bad header (2)\n");
246 return CSSMERR_CSP_INVALID_KEY
;
250 /* key points to first whitespace after header */
251 skipWhite(key
, keyLen
);
253 dprintf("parseSSH2PubKey: short key\n");
254 return CSSMERR_CSP_INVALID_KEY
;
257 /* key is start of base64 blob */
258 const unsigned char *encodedBlob
= key
;
259 const unsigned char *endBlob
= findNextWhite(key
, keyLen
);
260 unsigned encodedBlobLen
= (unsigned)(endBlob
- encodedBlob
);
263 *decodedBlob
= cuDec64(encodedBlob
, encodedBlobLen
, decodedBlobLen
);
264 if(*decodedBlob
== NULL
) {
265 dprintf("parseSSH2PubKey: base64 decode error\n");
266 return CSSMERR_CSP_INVALID_KEY
;
269 /* skip remainder; it's comment */
275 #pragma mark -- RSA OpenSSHv1 ---
277 CSSM_RETURN
RSAPublicKeyEncodeOpenSSH1(
279 const CssmData
&descData
,
280 CssmOwnedData
&encodedKey
)
282 CFMutableDataRef cfOut
= CFDataCreateMutable(NULL
, 0);
283 CSSM_RETURN ourRtn
= CSSM_OK
;
287 * num_bits in decimal
289 * e, bignum in decimal
291 * n, bignum in decimal
296 unsigned numBits
= BN_num_bits(rsa
->n
);
300 snprintf(bitString
, sizeof(bitString
), "%u ", numBits
);
301 CFDataAppendBytes(cfOut
, (const UInt8
*)bitString
, strlen(bitString
));
302 if((ourRtn
= appendBigNumDec(cfOut
, rsa
->e
))) {
305 CFDataAppendBytes(cfOut
, &c
, 1);
306 if((ourRtn
= appendBigNumDec(cfOut
, rsa
->n
))) {
310 if(descData
.Length
) {
311 /* optional comment */
312 CFDataAppendBytes(cfOut
, &c
, 1);
313 CFDataAppendBytes(cfOut
, (UInt8
*)descData
.Data
, descData
.Length
);
317 CFDataAppendBytes(cfOut
, &c
, 1);
318 encodedKey
.copy(CFDataGetBytePtr(cfOut
), CFDataGetLength(cfOut
));
324 CSSM_RETURN
RSAPublicKeyDecodeOpenSSH1(
329 const unsigned char *cp
= (const unsigned char *)p
;
330 unsigned remLen
= (unsigned)length
;
332 skipWhite(cp
, remLen
);
335 * cp points to start of size_in_bits in ASCII decimal; we really don't care about
336 * this field. Find next space.
338 cp
= findNextWhite(cp
, remLen
);
340 dprintf("RSAPublicKeyDecodeOpenSSH1: short key (1)\n");
341 return CSSMERR_CSP_INVALID_KEY
;
343 skipWhite(cp
, remLen
);
345 dprintf("RSAPublicKeyDecodeOpenSSH1: short key (2)\n");
346 return CSSMERR_CSP_INVALID_KEY
;
350 * cp points to start of e
352 const unsigned char *ep
= findNextWhite(cp
, remLen
);
354 dprintf("RSAPublicKeyDecodeOpenSSH1: short key (3)\n");
355 return CSSMERR_CSP_INVALID_KEY
;
357 unsigned len
= (unsigned)(ep
- cp
);
358 rsa
->e
= parseDecimalBn(cp
, len
);
360 return CSSMERR_CSP_INVALID_KEY
;
364 skipWhite(cp
, remLen
);
366 dprintf("RSAPublicKeyDecodeOpenSSH1: short key (4)\n");
370 /* cp points to start of n */
371 ep
= findNextWhite(cp
, remLen
);
372 len
= (unsigned)(ep
- cp
);
373 rsa
->n
= parseDecimalBn(cp
, len
);
375 return CSSMERR_CSP_INVALID_KEY
;
378 /* remainder is comment, we ignore */
383 CSSM_RETURN
RSAPrivateKeyEncodeOpenSSH1(
385 const CssmData
&descData
,
386 CssmOwnedData
&encodedKey
)
391 ourRtn
= encodeOpenSSHv1PrivKey(rsa
, descData
.Data
, (unsigned)descData
.Length
, NULL
, &cfOut
);
395 encodedKey
.copy(CFDataGetBytePtr(cfOut
), CFDataGetLength(cfOut
));
400 extern CSSM_RETURN
RSAPrivateKeyDecodeOpenSSH1(
405 return decodeOpenSSHv1PrivKey((const unsigned char *)p
, (unsigned)length
,
406 openKey
, NULL
, NULL
, NULL
);
409 #pragma mark -- RSA OpenSSHv2 ---
411 CSSM_RETURN
RSAPublicKeyEncodeOpenSSH2(
413 const CssmData
&descData
,
414 CssmOwnedData
&encodedKey
)
416 unsigned char *b64
= NULL
;
421 * First, the inner base64-encoded blob, consisting of
426 CFMutableDataRef cfOut
= CFDataCreateMutable(NULL
, 0);
427 CSSM_RETURN ourRtn
= CSSM_OK
;
428 appendString(cfOut
, SSH2_RSA_HEADER
, strlen(SSH2_RSA_HEADER
));
429 if((ourRtn
= appendBigNum2(cfOut
, rsa
->e
))) {
432 if((ourRtn
= appendBigNum2(cfOut
, rsa
->n
))) {
436 /* base64 encode that */
437 b64
= cuEnc64((unsigned char *)CFDataGetBytePtr(cfOut
), (unsigned)CFDataGetLength(cfOut
), &b64Len
);
439 /* cuEnc64 added newline and NULL, which we really don't want */
442 /* Now start over, dropping that base64 into a public blob. */
443 CFDataSetLength(cfOut
, 0);
444 CFDataAppendBytes(cfOut
, (UInt8
*)SSH2_RSA_HEADER
, strlen(SSH2_RSA_HEADER
));
446 CFDataAppendBytes(cfOut
, &c
, 1);
447 CFDataAppendBytes(cfOut
, b64
, b64Len
);
449 if(descData
.Length
) {
450 /* optional comment */
451 CFDataAppendBytes(cfOut
, &c
, 1);
452 CFDataAppendBytes(cfOut
, (UInt8
*)descData
.Data
, descData
.Length
);
455 /* finish it with a newline */
457 CFDataAppendBytes(cfOut
, &c
, 1);
459 encodedKey
.copy(CFDataGetBytePtr(cfOut
), CFDataGetLength(cfOut
));
468 CSSM_RETURN
RSAPublicKeyDecodeOpenSSH2(
473 const unsigned char *key
= (const unsigned char *)p
;
474 unsigned keyLen
= (unsigned)length
;
479 * get base64-decoded blob
481 unsigned char *decodedBlob
= NULL
;
482 unsigned decodedBlobLen
= 0;
483 if((ourRtn
= parseSSH2PubKey(key
, keyLen
, SSH2_RSA_HEADER
, &decodedBlob
, &decodedBlobLen
))) {
486 /* subsequent errors to errOut: */
489 * The inner base64-decoded blob, consisting of
498 keyLen
= decodedBlobLen
;
500 /* three length fields at least */
501 dprintf("RSAPublicKeyDecodeOpenSSH2: short record(2)\n");
505 decLen
= readUint32(key
, keyLen
);
506 len
= strlen(SSH2_RSA_HEADER
);
508 dprintf("RSAPublicKeyDecodeOpenSSH2: bad header (2)\n");
509 ourRtn
= CSSMERR_CSP_INVALID_KEY
;
512 if(memcmp(SSH2_RSA_HEADER
, key
, len
)) {
513 dprintf("RSAPublicKeyDecodeOpenSSH2: bad header (1)\n");
514 return CSSMERR_CSP_INVALID_KEY
;
519 rsa
->e
= readBigNum2(key
, keyLen
);
521 ourRtn
= CSSMERR_CSP_INVALID_KEY
;
524 rsa
->n
= readBigNum2(key
, keyLen
);
526 ourRtn
= CSSMERR_CSP_INVALID_KEY
;
535 #pragma mark -- DSA OpenSSHv2 ---
537 CSSM_RETURN
DSAPublicKeyEncodeOpenSSH2(
539 const CssmData
&descData
,
540 CssmOwnedData
&encodedKey
)
542 unsigned char *b64
= NULL
;
547 * First, the inner base64-encoded blob, consisting of
554 CFMutableDataRef cfOut
= CFDataCreateMutable(NULL
, 0);
556 appendString(cfOut
, SSH2_DSA_HEADER
, strlen(SSH2_DSA_HEADER
));
557 if((ourRtn
= appendBigNum2(cfOut
, dsa
->p
))) {
560 if((ourRtn
= appendBigNum2(cfOut
, dsa
->q
))) {
563 if((ourRtn
= appendBigNum2(cfOut
, dsa
->g
))) {
566 if((ourRtn
= appendBigNum2(cfOut
, dsa
->pub_key
))) {
570 /* base64 encode that */
571 b64
= cuEnc64((unsigned char *)CFDataGetBytePtr(cfOut
), (unsigned)CFDataGetLength(cfOut
), &b64Len
);
573 /* cuEnc64 added newline and NULL, which we really don't want */
576 /* Now start over, dropping that base64 into a public blob. */
577 CFDataSetLength(cfOut
, 0);
578 CFDataAppendBytes(cfOut
, (UInt8
*)SSH2_DSA_HEADER
, strlen(SSH2_DSA_HEADER
));
580 CFDataAppendBytes(cfOut
, &c
, 1);
581 CFDataAppendBytes(cfOut
, b64
, b64Len
);
583 if(descData
.Length
) {
584 /* optional comment */
585 CFDataAppendBytes(cfOut
, &c
, 1);
586 CFDataAppendBytes(cfOut
, (UInt8
*)descData
.Data
, descData
.Length
);
589 /* finish it with a newline */
591 CFDataAppendBytes(cfOut
, &c
, 1);
593 encodedKey
.copy(CFDataGetBytePtr(cfOut
), CFDataGetLength(cfOut
));
603 CSSM_RETURN
DSAPublicKeyDecodeOpenSSH2(
608 const unsigned char *key
= (const unsigned char *)p
;
609 unsigned keyLen
= (unsigned)length
;
614 * get base64-decoded blob
616 unsigned char *decodedBlob
= NULL
;
617 unsigned decodedBlobLen
= 0;
618 if((ourRtn
= parseSSH2PubKey(key
, keyLen
, SSH2_DSA_HEADER
, &decodedBlob
, &decodedBlobLen
))) {
621 /* subsequent errors to errOut: */
624 * The inner base64-decoded blob, consisting of
635 keyLen
= decodedBlobLen
;
637 /* five length fields at least */
638 dprintf("DSAPublicKeyDecodeOpenSSH2: short record(2)\n");
639 ourRtn
= CSSMERR_CSP_INVALID_KEY
;
642 decLen
= readUint32(key
, keyLen
);
643 len
= strlen(SSH2_DSA_HEADER
);
645 dprintf("DSAPublicKeyDecodeOpenSSH2: bad header (2)\n");
646 ourRtn
= CSSMERR_CSP_INVALID_KEY
;
649 if(memcmp(SSH2_DSA_HEADER
, key
, len
)) {
650 dprintf("DSAPublicKeyDecodeOpenSSH2: bad header (1)\n");
651 return CSSMERR_CSP_INVALID_KEY
;
656 dsa
->p
= readBigNum2(key
, keyLen
);
658 ourRtn
= CSSMERR_CSP_INVALID_KEY
;
661 dsa
->q
= readBigNum2(key
, keyLen
);
663 ourRtn
= CSSMERR_CSP_INVALID_KEY
;
666 dsa
->g
= readBigNum2(key
, keyLen
);
668 ourRtn
= CSSMERR_CSP_INVALID_KEY
;
671 dsa
->pub_key
= readBigNum2(key
, keyLen
);
672 if(dsa
->pub_key
== NULL
) {
673 ourRtn
= CSSMERR_CSP_INVALID_KEY
;