2 * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
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:
12 * The above copyright notice and this permission notice shall be
13 * included in all copies or substantial portions of the Software.
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
26 using System.Collections.Generic;
34 * Basic implementation of IServerPolicy: it uses a single certificate
35 * chain and a software private key.
38 public class SSLServerPolicyBasic : IServerPolicy {
42 bool canSign, canEncrypt;
45 * Create the policy instance, with the provided certificate chain,
46 * private key, and allowed key usages.
48 public SSLServerPolicyBasic(byte[][] chain,
49 IPrivateKey skey, KeyUsage usage)
56 case KeyUsage.EncryptOnly:
59 case KeyUsage.SignOnly:
62 case KeyUsage.EncryptAndSign:
69 public IServerChoices Apply(SSLServer server)
72 * Conditions for selecting a cipher suite:
74 * RSA canEncrypt, key is RSA
76 * ECDH canEncrypt, key is EC, curve supported
78 * ECDHE_RSA canSign, key is RSA, have hash+RSA algo
80 * ECDHE_ECDSA canSign, key is EC, have hash+ECDSA algo,
83 * The engine already filtered things, so we know that:
85 * - if an ECDHE suite is present, then there is a common
88 * - if an ECDHE_RSA suite is present, then there is a
89 * common hash+RSA algorithm;
91 * - if an ECDHE_ECDSA suite is present, then there is a
92 * common hash+ECDSA algorithm.
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.
99 if (skey is ECPrivateKey) {
100 curveID = SSL.CurveToID(((ECPrivateKey)skey).Curve);
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);
108 foreach (int cs in server.CommonCipherSuites) {
113 return new ChoicesRSA(server.ClientVersionMax,
114 cs, chain, skey as RSAPrivateKey);
115 } else if (SSL.IsECDH(cs)) {
119 return new ChoicesECDH(cs, chain,
120 skey as ECPrivateKey);
121 } else if (SSL.IsECDHE_RSA(cs)) {
126 if (server.Version <= SSL.TLS11) {
127 hashAlgo = SSL.MD5SHA1;
129 hashAlgo = SelectHash(
130 server.ClientHashAndSign,
133 return new ChoicesSign(cs, chain,
135 } else if (SSL.IsECDHE_ECDSA(cs)) {
136 if (!canECDHE_ECDSA) {
140 if (server.Version <= SSL.TLS11) {
143 hashAlgo = SelectHash(
144 server.ClientHashAndSign,
147 return new ChoicesSign(cs, chain,
152 throw new SSLException("No suitable cipher suite");
155 static int SelectHash(List<int> hsl, int sigAlg)
157 foreach (int x in hsl) {
158 if ((x & 0xFF) == sigAlg) {
164 * This should never happen, because the offending
165 * cipher suites would have been filtered by the engine.
167 throw new Exception();
176 internal ChoicesBase(int cipherSuite, byte[][] chain)
178 this.cipherSuite = cipherSuite;
182 public int GetCipherSuite()
187 public byte[][] GetCertificateChain()
192 public virtual byte[] DoKeyExchange(byte[] cke)
194 throw new Exception();
197 public virtual byte[] DoSign(byte[] ske,
198 out int hashAlgo, out int sigAlgo)
200 throw new Exception();
204 class ChoicesRSA : ChoicesBase, IServerChoices {
206 int clientVersionMax;
209 internal ChoicesRSA(int clientVersionMax, int cipherSuite,
210 byte[][] chain, RSAPrivateKey rk)
211 : base(cipherSuite, chain)
213 this.clientVersionMax = clientVersionMax;
217 public override byte[] DoKeyExchange(byte[] cke)
219 if (cke.Length < 59) {
220 throw new CryptoException(
221 "Invalid ClientKeyExchange (too short)");
223 RSA.DoPrivate(rk, cke);
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).
235 for (int i = 2; i < cke.Length - 49; i ++) {
237 z |= ~((y | -y) >> 31);
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);
245 * Get a random premaster, then overwrite it with the
246 * decrypted value, but only if the padding was correct.
248 byte[] pms = new byte[48];
250 for (int i = 0; i < 48; i ++) {
252 int y = cke[cke.Length - 48 + i];
253 pms[i] = (byte)(x ^ (z & (x ^ y)));
260 class ChoicesECDH : ChoicesBase, IServerChoices {
264 internal ChoicesECDH(int cipherSuite, byte[][] chain, ECPrivateKey ek)
265 : base(cipherSuite, chain)
270 public override byte[] DoKeyExchange(byte[] cke)
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");
279 int xoff = curve.GetXoff(out xlen);
280 byte[] pms = new byte[xlen];
281 Array.Copy(tmp, xoff, pms, 0, xlen);
286 class ChoicesSign : ChoicesBase, IServerChoices {
291 internal ChoicesSign(int cipherSuite, byte[][] chain,
292 int hashAlgo, IPrivateKey skey)
293 : base(cipherSuite, chain)
295 this.hashAlgo = hashAlgo;
299 public override byte[] DoSign(byte[] ske,
300 out int hashAlgo, out int signAlgo)
302 hashAlgo = this.hashAlgo;
303 byte[] hv = Hash(hashAlgo, ske);
304 if (skey is RSAPrivateKey) {
305 RSAPrivateKey rk = skey as RSAPrivateKey;
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;
316 throw new Exception();
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);
324 throw new Exception("NYI");
328 static byte[] Hash(int hashAlgo, byte[] data)
332 byte[] hv = new byte[36];
334 SHA1 sha1 = new SHA1();
338 sha1.DoFinal(hv, 16);
341 return new MD5().Hash(data);
343 return new SHA1().Hash(data);
345 return new SHA224().Hash(data);
347 return new SHA256().Hash(data);
349 return new SHA384().Hash(data);
351 return new SHA512().Hash(data);
353 throw new Exception("NYI");