3eca8453 |
1 | /* |
a52f067e |
2 | * Generic SSH public-key handling operations. In particular, |
3 | * reading of SSH public-key files, and also the generic `sign' |
4 | * operation for ssh2 (which checks the type of the key and |
5 | * dispatches to the appropriate key-type specific function). |
3eca8453 |
6 | */ |
7 | |
8 | #include <stdio.h> |
a52f067e |
9 | #include <stdlib.h> |
3eca8453 |
10 | |
3eca8453 |
11 | #include "ssh.h" |
12 | |
6e522441 |
13 | #define PUT_32BIT(cp, value) do { \ |
14 | (cp)[3] = (value); \ |
15 | (cp)[2] = (value) >> 8; \ |
16 | (cp)[1] = (value) >> 16; \ |
17 | (cp)[0] = (value) >> 24; } while (0) |
18 | |
3eca8453 |
19 | #define GET_32BIT(cp) \ |
20 | (((unsigned long)(unsigned char)(cp)[0] << 24) | \ |
21 | ((unsigned long)(unsigned char)(cp)[1] << 16) | \ |
22 | ((unsigned long)(unsigned char)(cp)[2] << 8) | \ |
23 | ((unsigned long)(unsigned char)(cp)[3])) |
24 | |
25 | #define rsa_signature "SSH PRIVATE KEY FILE FORMAT 1.1\n" |
26 | |
a52f067e |
27 | #define BASE64_TOINT(x) ( (x)-'A'<26 ? (x)-'A'+0 :\ |
28 | (x)-'a'<26 ? (x)-'a'+26 :\ |
29 | (x)-'0'<10 ? (x)-'0'+52 :\ |
30 | (x)=='+' ? 62 : \ |
31 | (x)=='/' ? 63 : 0 ) |
32 | |
6e522441 |
33 | static int loadrsakey_main(FILE *fp, struct RSAKey *key, struct RSAAux *aux, |
a52f067e |
34 | char **commentptr, char *passphrase) { |
3eca8453 |
35 | unsigned char buf[16384]; |
36 | unsigned char keybuf[16]; |
37 | int len; |
38 | int i, j, ciphertype; |
39 | int ret = 0; |
40 | struct MD5Context md5c; |
a52f067e |
41 | char *comment; |
3eca8453 |
42 | |
a52f067e |
43 | /* Slurp the whole file (minus the header) into a buffer. */ |
3eca8453 |
44 | len = fread(buf, 1, sizeof(buf), fp); |
45 | fclose(fp); |
46 | if (len < 0 || len == sizeof(buf)) |
47 | goto end; /* file too big or not read */ |
48 | |
a52f067e |
49 | i = 0; |
3eca8453 |
50 | |
a52f067e |
51 | /* |
52 | * A zero byte. (The signature includes a terminating NUL.) |
53 | */ |
54 | if (len-i < 1 || buf[i] != 0) |
55 | goto end; |
56 | i++; |
3eca8453 |
57 | |
a52f067e |
58 | /* One byte giving encryption type, and one reserved uint32. */ |
3eca8453 |
59 | if (len-i < 1) |
60 | goto end; |
61 | ciphertype = buf[i]; |
62 | if (ciphertype != 0 && ciphertype != SSH_CIPHER_3DES) |
63 | goto end; |
64 | i++; |
65 | if (len-i < 4) |
66 | goto end; /* reserved field not present */ |
67 | if (buf[i] != 0 || buf[i+1] != 0 || buf[i+2] != 0 || buf[i+3] != 0) |
68 | goto end; /* reserved field nonzero, panic! */ |
69 | i += 4; |
70 | |
71 | /* Now the serious stuff. An ordinary SSH 1 public key. */ |
72 | i += makekey(buf+i, key, NULL, 1); |
73 | if (len-i < 0) |
74 | goto end; /* overran */ |
75 | |
76 | /* Next, the comment field. */ |
77 | j = GET_32BIT(buf+i); |
5c58ad2d |
78 | i += 4; |
79 | if (len-i < j) goto end; |
a52f067e |
80 | comment = malloc(j+1); |
81 | if (comment) { |
82 | memcpy(comment, buf+i, j); |
83 | comment[j] = '\0'; |
5c58ad2d |
84 | } |
85 | i += j; |
a52f067e |
86 | if (commentptr) |
87 | *commentptr = comment; |
88 | if (key) |
89 | key->comment = comment; |
90 | if (!key) { |
91 | return ciphertype != 0; |
92 | } |
3eca8453 |
93 | |
94 | /* |
95 | * Decrypt remainder of buffer. |
96 | */ |
97 | if (ciphertype) { |
98 | MD5Init(&md5c); |
99 | MD5Update(&md5c, passphrase, strlen(passphrase)); |
100 | MD5Final(keybuf, &md5c); |
101 | des3_decrypt_pubkey(keybuf, buf+i, (len-i+7)&~7); |
5c58ad2d |
102 | memset(keybuf, 0, sizeof(keybuf)); /* burn the evidence */ |
3eca8453 |
103 | } |
104 | |
105 | /* |
106 | * We are now in the secret part of the key. The first four |
107 | * bytes should be of the form a, b, a, b. |
108 | */ |
109 | if (len-i < 4) goto end; |
110 | if (buf[i] != buf[i+2] || buf[i+1] != buf[i+3]) { ret = -1; goto end; } |
111 | i += 4; |
112 | |
113 | /* |
114 | * After that, we have one further bignum which is our |
6e522441 |
115 | * decryption exponent, and then the three auxiliary values |
116 | * (iqmp, q, p). |
3eca8453 |
117 | */ |
118 | i += makeprivate(buf+i, key); |
119 | if (len-i < 0) goto end; |
6e522441 |
120 | if (aux) { |
121 | i += ssh1_read_bignum(buf+i, &aux->iqmp); |
122 | if (len-i < 0) goto end; |
123 | i += ssh1_read_bignum(buf+i, &aux->q); |
124 | if (len-i < 0) goto end; |
125 | i += ssh1_read_bignum(buf+i, &aux->p); |
126 | if (len-i < 0) goto end; |
127 | } |
3eca8453 |
128 | |
129 | ret = 1; |
130 | end: |
131 | memset(buf, 0, sizeof(buf)); /* burn the evidence */ |
132 | return ret; |
133 | } |
134 | |
6e522441 |
135 | int loadrsakey(char *filename, struct RSAKey *key, struct RSAAux *aux, |
136 | char *passphrase) { |
3eca8453 |
137 | FILE *fp; |
a52f067e |
138 | unsigned char buf[64]; |
3eca8453 |
139 | |
140 | fp = fopen(filename, "rb"); |
141 | if (!fp) |
142 | return 0; /* doesn't even exist */ |
143 | |
a52f067e |
144 | /* |
145 | * Read the first line of the file and see if it's a v1 private |
146 | * key file. |
147 | */ |
148 | if (fgets(buf, sizeof(buf), fp) && |
149 | !strcmp(buf, rsa_signature)) { |
6e522441 |
150 | return loadrsakey_main(fp, key, aux, NULL, passphrase); |
a52f067e |
151 | } |
152 | |
153 | /* |
154 | * Otherwise, we have nothing. Return empty-handed. |
155 | */ |
3eca8453 |
156 | fclose(fp); |
3eca8453 |
157 | return 0; |
158 | } |
a52f067e |
159 | |
160 | /* |
161 | * See whether an RSA key is encrypted. Return its comment field as |
162 | * well. |
163 | */ |
164 | int rsakey_encrypted(char *filename, char **comment) { |
165 | FILE *fp; |
166 | unsigned char buf[64]; |
167 | |
168 | fp = fopen(filename, "rb"); |
169 | if (!fp) |
170 | return 0; /* doesn't even exist */ |
171 | |
172 | /* |
173 | * Read the first line of the file and see if it's a v1 private |
174 | * key file. |
175 | */ |
176 | if (fgets(buf, sizeof(buf), fp) && |
177 | !strcmp(buf, rsa_signature)) { |
6e522441 |
178 | return loadrsakey_main(fp, NULL, NULL, comment, NULL); |
a52f067e |
179 | } |
180 | return 0; /* wasn't the right kind of file */ |
181 | } |
6e522441 |
182 | |
183 | /* |
184 | * Save an RSA key file. Return nonzero on success. |
185 | */ |
186 | int saversakey(char *filename, struct RSAKey *key, struct RSAAux *aux, |
187 | char *passphrase) { |
188 | unsigned char buf[16384]; |
189 | unsigned char keybuf[16]; |
190 | struct MD5Context md5c; |
191 | char *p, *estart; |
192 | FILE *fp; |
193 | |
194 | /* |
195 | * Write the initial signature. |
196 | */ |
197 | p = buf; |
198 | memcpy(p, rsa_signature, sizeof(rsa_signature)); |
199 | p += sizeof(rsa_signature); |
200 | |
201 | /* |
202 | * One byte giving encryption type, and one reserved (zero) |
203 | * uint32. |
204 | */ |
205 | *p++ = (passphrase ? SSH_CIPHER_3DES : 0); |
206 | PUT_32BIT(p, 0); p += 4; |
207 | |
208 | /* |
209 | * An ordinary SSH 1 public key consists of: a uint32 |
210 | * containing the bit count, then two bignums containing the |
211 | * modulus and exponent respectively. |
212 | */ |
213 | PUT_32BIT(p, ssh1_bignum_bitcount(key->modulus)); p += 4; |
214 | p += ssh1_write_bignum(p, key->modulus); |
215 | p += ssh1_write_bignum(p, key->exponent); |
216 | |
217 | /* |
218 | * A string containing the comment field. |
219 | */ |
220 | if (key->comment) { |
221 | PUT_32BIT(p, strlen(key->comment)); p += 4; |
222 | memcpy(p, key->comment, strlen(key->comment)); |
223 | p += strlen(key->comment); |
224 | } else { |
225 | PUT_32BIT(p, 0); p += 4; |
226 | } |
227 | |
228 | /* |
229 | * The encrypted portion starts here. |
230 | */ |
231 | estart = p; |
232 | |
233 | /* |
234 | * Two bytes, then the same two bytes repeated. |
235 | */ |
236 | *p++ = random_byte(); |
237 | *p++ = random_byte(); |
238 | p[0] = p[-2]; p[1] = p[-1]; p += 2; |
239 | |
240 | /* |
241 | * Four more bignums: the decryption exponent, then iqmp, then |
242 | * q, then p. |
243 | */ |
244 | p += ssh1_write_bignum(p, key->private_exponent); |
245 | p += ssh1_write_bignum(p, aux->iqmp); |
246 | p += ssh1_write_bignum(p, aux->q); |
247 | p += ssh1_write_bignum(p, aux->p); |
248 | |
249 | /* |
250 | * Now write zeros until the encrypted portion is a multiple of |
251 | * 8 bytes. |
252 | */ |
253 | while ((p-estart) % 8) |
254 | *p++ = '\0'; |
255 | |
256 | /* |
257 | * Now encrypt the encrypted portion. |
258 | */ |
259 | if (passphrase) { |
260 | MD5Init(&md5c); |
261 | MD5Update(&md5c, passphrase, strlen(passphrase)); |
262 | MD5Final(keybuf, &md5c); |
263 | des3_encrypt_pubkey(keybuf, estart, p-estart); |
264 | memset(keybuf, 0, sizeof(keybuf)); /* burn the evidence */ |
265 | } |
266 | |
267 | /* |
268 | * Done. Write the result to the file. |
269 | */ |
270 | fp = fopen(filename, "wb"); |
271 | if (fp) { |
272 | int ret = (fwrite(buf, 1, p-buf, fp) == (size_t)(p-buf)); |
273 | ret = ret && (fclose(fp) == 0); |
274 | return ret; |
275 | } else |
276 | return 0; |
277 | } |