Initial commit.
[BoarSSL] / SSLTLS / SSLServerPolicyBasic.cs
1 /*
2 * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining
5 * a copy of this software and associated documentation files (the
6 * "Software"), to deal in the Software without restriction, including
7 * without limitation the rights to use, copy, modify, merge, publish,
8 * distribute, sublicense, and/or sell copies of the Software, and to
9 * permit persons to whom the Software is furnished to do so, subject to
10 * the following conditions:
11 *
12 * The above copyright notice and this permission notice shall be
13 * included in all copies or substantial portions of the Software.
14 *
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
19 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
20 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
21 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 * SOFTWARE.
23 */
24
25 using System;
26 using System.Collections.Generic;
27 using System.IO;
28
29 using Crypto;
30
31 namespace SSLTLS {
32
33 /*
34 * Basic implementation of IServerPolicy: it uses a single certificate
35 * chain and a software private key.
36 */
37
38 public class SSLServerPolicyBasic : IServerPolicy {
39
40 byte[][] chain;
41 IPrivateKey skey;
42 bool canSign, canEncrypt;
43
44 /*
45 * Create the policy instance, with the provided certificate chain,
46 * private key, and allowed key usages.
47 */
48 public SSLServerPolicyBasic(byte[][] chain,
49 IPrivateKey skey, KeyUsage usage)
50 {
51 this.chain = chain;
52 this.skey = skey;
53 canEncrypt = false;
54 canSign = false;
55 switch (usage) {
56 case KeyUsage.EncryptOnly:
57 canEncrypt = true;
58 break;
59 case KeyUsage.SignOnly:
60 canSign = true;
61 break;
62 case KeyUsage.EncryptAndSign:
63 canEncrypt = true;
64 canSign = true;
65 break;
66 }
67 }
68
69 public IServerChoices Apply(SSLServer server)
70 {
71 /*
72 * Conditions for selecting a cipher suite:
73 *
74 * RSA canEncrypt, key is RSA
75 *
76 * ECDH canEncrypt, key is EC, curve supported
77 *
78 * ECDHE_RSA canSign, key is RSA, have hash+RSA algo
79 *
80 * ECDHE_ECDSA canSign, key is EC, have hash+ECDSA algo,
81 * curve supported
82 *
83 * The engine already filtered things, so we know that:
84 *
85 * - if an ECDHE suite is present, then there is a common
86 * supported curve;
87 *
88 * - if an ECDHE_RSA suite is present, then there is a
89 * common hash+RSA algorithm;
90 *
91 * - if an ECDHE_ECDSA suite is present, then there is a
92 * common hash+ECDSA algorithm.
93 *
94 * We must still walk the list of algorithm to determine the
95 * proper hash to use for signatures; we also need to check
96 * that our EC curve is supported by the client.
97 */
98 int curveID = -1;
99 if (skey is ECPrivateKey) {
100 curveID = SSL.CurveToID(((ECPrivateKey)skey).Curve);
101 }
102 bool canRSA = canEncrypt && (skey is RSAPrivateKey);
103 bool canECDH = canEncrypt && (skey is ECPrivateKey)
104 && server.ClientCurves.Contains(curveID);
105 bool canECDHE_RSA = canSign && (skey is RSAPrivateKey);
106 bool canECDHE_ECDSA = canSign && (skey is ECPrivateKey);
107
108 foreach (int cs in server.CommonCipherSuites) {
109 if (SSL.IsRSA(cs)) {
110 if (!canRSA) {
111 continue;
112 }
113 return new ChoicesRSA(server.ClientVersionMax,
114 cs, chain, skey as RSAPrivateKey);
115 } else if (SSL.IsECDH(cs)) {
116 if (!canECDH) {
117 continue;
118 }
119 return new ChoicesECDH(cs, chain,
120 skey as ECPrivateKey);
121 } else if (SSL.IsECDHE_RSA(cs)) {
122 if (!canECDHE_RSA) {
123 continue;
124 }
125 int hashAlgo;
126 if (server.Version <= SSL.TLS11) {
127 hashAlgo = SSL.MD5SHA1;
128 } else {
129 hashAlgo = SelectHash(
130 server.ClientHashAndSign,
131 SSL.RSA);
132 }
133 return new ChoicesSign(cs, chain,
134 hashAlgo, skey);
135 } else if (SSL.IsECDHE_ECDSA(cs)) {
136 if (!canECDHE_ECDSA) {
137 continue;
138 }
139 int hashAlgo;
140 if (server.Version <= SSL.TLS11) {
141 hashAlgo = SSL.SHA1;
142 } else {
143 hashAlgo = SelectHash(
144 server.ClientHashAndSign,
145 SSL.ECDSA);
146 }
147 return new ChoicesSign(cs, chain,
148 hashAlgo, skey);
149 }
150 }
151
152 throw new SSLException("No suitable cipher suite");
153 }
154
155 static int SelectHash(List<int> hsl, int sigAlg)
156 {
157 foreach (int x in hsl) {
158 if ((x & 0xFF) == sigAlg) {
159 return x >> 8;
160 }
161 }
162
163 /*
164 * This should never happen, because the offending
165 * cipher suites would have been filtered by the engine.
166 */
167 throw new Exception();
168 }
169 }
170
171 class ChoicesBase {
172
173 int cipherSuite;
174 byte[][] chain;
175
176 internal ChoicesBase(int cipherSuite, byte[][] chain)
177 {
178 this.cipherSuite = cipherSuite;
179 this.chain = chain;
180 }
181
182 public int GetCipherSuite()
183 {
184 return cipherSuite;
185 }
186
187 public byte[][] GetCertificateChain()
188 {
189 return chain;
190 }
191
192 public virtual byte[] DoKeyExchange(byte[] cke)
193 {
194 throw new Exception();
195 }
196
197 public virtual byte[] DoSign(byte[] ske,
198 out int hashAlgo, out int sigAlgo)
199 {
200 throw new Exception();
201 }
202 }
203
204 class ChoicesRSA : ChoicesBase, IServerChoices {
205
206 int clientVersionMax;
207 RSAPrivateKey rk;
208
209 internal ChoicesRSA(int clientVersionMax, int cipherSuite,
210 byte[][] chain, RSAPrivateKey rk)
211 : base(cipherSuite, chain)
212 {
213 this.clientVersionMax = clientVersionMax;
214 this.rk = rk;
215 }
216
217 public override byte[] DoKeyExchange(byte[] cke)
218 {
219 if (cke.Length < 59) {
220 throw new CryptoException(
221 "Invalid ClientKeyExchange (too short)");
222 }
223 RSA.DoPrivate(rk, cke);
224
225 /*
226 * Constant-time check for PKCS#1 v1.5 padding. z is set
227 * to -1 if the padding is correct, 0 otherwise. We also
228 * check the two first PMS byte (they should be equal to
229 * the maximum protocol version announced by the client
230 * in its ClientHello).
231 */
232 int z = 0;
233 z |= cke[0];
234 z |= cke[1] ^ 0x02;
235 for (int i = 2; i < cke.Length - 49; i ++) {
236 int y = cke[i];
237 z |= ~((y | -y) >> 31);
238 }
239 z |= cke[cke.Length - 49];
240 z |= cke[cke.Length - 48] ^ (clientVersionMax >> 8);
241 z |= cke[cke.Length - 47] ^ (clientVersionMax & 0xFF);
242 z = ~((z | -z) >> 31);
243
244 /*
245 * Get a random premaster, then overwrite it with the
246 * decrypted value, but only if the padding was correct.
247 */
248 byte[] pms = new byte[48];
249 RNG.GetBytes(pms);
250 for (int i = 0; i < 48; i ++) {
251 int x = pms[i];
252 int y = cke[cke.Length - 48 + i];
253 pms[i] = (byte)(x ^ (z & (x ^ y)));
254 }
255
256 return pms;
257 }
258 }
259
260 class ChoicesECDH : ChoicesBase, IServerChoices {
261
262 ECPrivateKey ek;
263
264 internal ChoicesECDH(int cipherSuite, byte[][] chain, ECPrivateKey ek)
265 : base(cipherSuite, chain)
266 {
267 this.ek = ek;
268 }
269
270 public override byte[] DoKeyExchange(byte[] cke)
271 {
272 ECCurve curve = ek.Curve;
273 byte[] tmp = new byte[curve.EncodedLength];
274 if (curve.Mul(cke, ek.X, tmp, false) == 0) {
275 throw new SSLException(
276 "Invalid ClientKeyExchange EC point value");
277 }
278 int xlen;
279 int xoff = curve.GetXoff(out xlen);
280 byte[] pms = new byte[xlen];
281 Array.Copy(tmp, xoff, pms, 0, xlen);
282 return pms;
283 }
284 }
285
286 class ChoicesSign : ChoicesBase, IServerChoices {
287
288 int hashAlgo;
289 IPrivateKey skey;
290
291 internal ChoicesSign(int cipherSuite, byte[][] chain,
292 int hashAlgo, IPrivateKey skey)
293 : base(cipherSuite, chain)
294 {
295 this.hashAlgo = hashAlgo;
296 this.skey = skey;
297 }
298
299 public override byte[] DoSign(byte[] ske,
300 out int hashAlgo, out int signAlgo)
301 {
302 hashAlgo = this.hashAlgo;
303 byte[] hv = Hash(hashAlgo, ske);
304 if (skey is RSAPrivateKey) {
305 RSAPrivateKey rk = skey as RSAPrivateKey;
306 signAlgo = SSL.RSA;
307 byte[] head;
308 switch (hashAlgo) {
309 case SSL.MD5SHA1: head = null; break;
310 case SSL.SHA1: head = RSA.PKCS1_SHA1; break;
311 case SSL.SHA224: head = RSA.PKCS1_SHA224; break;
312 case SSL.SHA256: head = RSA.PKCS1_SHA256; break;
313 case SSL.SHA384: head = RSA.PKCS1_SHA384; break;
314 case SSL.SHA512: head = RSA.PKCS1_SHA512; break;
315 default:
316 throw new Exception();
317 }
318 return RSA.Sign(rk, head, hv);
319 } else if (skey is ECPrivateKey) {
320 ECPrivateKey ek = skey as ECPrivateKey;
321 signAlgo = SSL.ECDSA;
322 return ECDSA.Sign(ek, null, hv);
323 } else {
324 throw new Exception("NYI");
325 }
326 }
327
328 static byte[] Hash(int hashAlgo, byte[] data)
329 {
330 switch (hashAlgo) {
331 case SSL.MD5SHA1:
332 byte[] hv = new byte[36];
333 MD5 md5 = new MD5();
334 SHA1 sha1 = new SHA1();
335 md5.Update(data);
336 md5.DoFinal(hv, 0);
337 sha1.Update(data);
338 sha1.DoFinal(hv, 16);
339 return hv;
340 case SSL.MD5:
341 return new MD5().Hash(data);
342 case SSL.SHA1:
343 return new SHA1().Hash(data);
344 case SSL.SHA224:
345 return new SHA224().Hash(data);
346 case SSL.SHA256:
347 return new SHA256().Hash(data);
348 case SSL.SHA384:
349 return new SHA384().Hash(data);
350 case SSL.SHA512:
351 return new SHA512().Hash(data);
352 default:
353 throw new Exception("NYI");
354 }
355 }
356 }
357
358 }